Cash Generator - Fast, Undetected.
Keyless Verified

Cash Generator - Fast, Undetected.

Blox FruitsTomatoTomato 1.2M views 254.8K executesUpdated Dec 30, 2024
4.7/ 5(164 votes)
155995% Positive
Lua Script•2267 lines (104573 chars)
-- Scriptblox Analytics
pcall(function() loadstring(game:HttpGet("https://scriptblox.com/ingest/clientv2.lua"))("proj_90a9005b5684", "1.0.0", false) end)
-- Dynamic CanTouch farmer with a discovery pass and persistent streaming memory.
local SPEED = 190 -- Fallback horizontal speed on foot in studs per second.
local SPEED_MIN = 250 -- Minimum sinusoidal speed in studs per second.
local SPEED_MAX = 259.9 -- Maximum sinusoidal speed in studs per second.
local SPEED_PERIOD = 0.2 -- Complete sinusoidal cycle period in seconds (5 Hz).
local EFFECTIVE_AVG_SPEED = (SPEED_MAX + SPEED_MIN) * 0.5 -- 254.95 studs/s nominal average

local BOAT_PURCHASE_RANGE = 450
local BOAT_APPROACH_RANGE = 420 -- Margin for dealer position offsets.
local MIN_ROOT_Y = 2.0 -- Strict altitude floor (keeps boat above waterline at all times).
local MAX_TRAVEL_SECONDS = 180
local LOAD_UI = true
local SHOW_ROUTE_TRACERS = true
local SHOW_HITBOX_VISUALIZER = true -- Gold outlines for chest targets, including the discovery pass.
local REWARD_SEA = 1 -- Set to 1, 2, or 3 for your current Sea.
local ESTIMATED_PICKUP_SECONDS = 0.03
local LOOKAHEAD_STOPS = 4
local MAP_OUTLIER_DISTANCE = 10000 -- Ignore very distant spawns when fitting the chest map.
local DISCOVERY_RUN = true -- Discover through known chest locations once; dealers are only for getting a boat.
local DISCOVERY_RADIUS = 200 -- Nearby discovery seeds share one streaming stop.
local STREAM_TIMEOUT = 2 -- Bound each streaming request; no parallel request flood.

-- Chest trigger geometry
local HITBOX_SIZE = Vector3.new(100, 100, 100)
local HITBOX_HALF_EXTENT = 50 -- Start at the face of the 100x100x100 trigger, with no 5-stud inset.

local Players = game:GetService("Players")
local RunService = game:GetService("RunService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local player = Players.LocalPlayer
while not player do
    task.wait()
    player = Players.LocalPlayer
end
print("[Chest Farmer] Script started")

-- Sinusoidal 3D Speed Generator (250 - 259.9 studs/s) Synchronized with Server Clock
local function getSinusoidalSpeed(serverTime)
    local mid = (SPEED_MAX + SPEED_MIN) * 0.5 -- 254.95
    local amp = (SPEED_MAX - SPEED_MIN) * 0.5 -- 4.95
    local omega = (2 * math.pi) / SPEED_PERIOD
    local serverTimeCycle = serverTime % SPEED_PERIOD
    return mid + amp * math.sin(serverTimeCycle * omega)
end

local C = {
    Speed = SPEED,
    BoatSpeed = EFFECTIVE_AVG_SPEED,
    MinRootY = MIN_ROOT_Y,
    PollSeconds = 0.05,
    SweepMargin = 5, -- Continue a short distance past the hitbox face for touch registration.
    MinimumTouchDepth = 4,
    ArrivalDistance = 4,
    PendingConfirmSeconds = 3,
    CharacterTimeout = 15,
    ChestFailureCooldown = 10,
    MaxChestCooldown = 120,
    MatchRadius = 1,
    ObserveSeconds = 0.5, -- Fallback polling; CanTouch changes also update state immediately.
    ReplanSeconds = 1,
    SwitchGain = 1.25,
    MinimumCommitSeconds = 1.5,
    FinishDistance = 30,
    BeamWidth = 8,
    BranchWidth = 8,
    PlanBudgetSeconds = 0.0015,
    MapRefreshSeconds = 0.5,
    Debug = true,
}

-- Loaded CanTouch is authoritative. Timers predict only known, unloaded chests.
local gradeBySpawnName = {Chest1 = "Silver", Chest2 = "Gold", Chest3 = "Diamond"}
local chestEconomy = {
    Silver = {respawn = 180, rewards = {{250, 420}, {500, 1000}, {1000, 1695}}},
    Gold = {respawn = 360, rewards = {{600, 1100}, {2300, 3120}, {3875, 5200}}},
    Diamond = {respawn = 720, rewards = {{1500, 3000}, {5000, 7250}, {8000, 12250}}},
}
local function gradeOf(record) return gradeBySpawnName[record.name] or "Gold" end
local function economyOf(record) return chestEconomy[gradeOf(record)] end
local function respawnOf(record) return record.respawnEstimate or (gradeBySpawnName[record.name] and economyOf(record).respawn) end
local function enforceAltitudeFloor(position)
    if position.Y >= C.MinRootY then return position end
    return Vector3.new(position.X, C.MinRootY, position.Z)
end

-- CALCULATED LEG SPEED TRACKER
local legHistory = {}
local maxLegHistory = 15
local avgLegSpeed = 0
local legHistoryMode = nil
local measuredMotion = {}

local function recordLegPerformance(distance, timeTaken, mode)
    if timeTaken <= 0.02 or distance <= 4.0 then return end
    if legHistoryMode ~= mode then
        table.clear(legHistory)
        legHistoryMode = mode
    end
    table.insert(legHistory, {distance = distance, time = timeTaken})
    if #legHistory > maxLegHistory then
        table.remove(legHistory, 1)
    end
    local totalDistance, totalTime = 0, 0
    for _, leg in ipairs(legHistory) do
        totalDistance += leg.distance
        totalTime += leg.time
    end
    avgLegSpeed = totalDistance / totalTime
    measuredMotion[mode] = {speed = avgLegSpeed, samples = #legHistory}
end

-- ROUTE PLANNER (Direct 3D Euclidean Distance)
local Planner = {}
function Planner.travelSeconds(a, b, speed3D)
    local dist3D = (b - a).Magnitude
    local mode = speed3D == C.Speed and "foot" or "boat"
    local observed = measuredMotion[mode]
    local calibrated = speed3D
    if observed and observed.samples >= 3 then
        calibrated = math.clamp(observed.speed, speed3D * 0.7, speed3D * 1.1)
    end
    return dist3D / calibrated
end

function Planner.sweepPath(node, fromPosition, margin)
    local center = enforceAltitudeFloor(node.position)
    local half = node.halfSize or Vector3.new(HITBOX_HALF_EXTENT, HITBOX_HALF_EXTENT, HITBOX_HALF_EXTENT)
    -- Coordinate clamping gives the nearest point on the shared trigger box,
    -- rather than intersecting a longer ray aimed at the chest's center.
    local entryPoint = Vector3.new(
        math.clamp(fromPosition.X, center.X - half.X, center.X + half.X),
        math.clamp(fromPosition.Y, center.Y - half.Y, center.Y + half.Y),
        math.clamp(fromPosition.Z, center.Z - half.Z, center.Z + half.Z))
    local inward = center - entryPoint
    local direction = inward.Magnitude > 0.001 and inward.Unit or Vector3.new(0, 0, -1)
    local exitPoint = entryPoint + direction * (margin + 6)
    -- Include the arrival tolerance so stopping short still leaves four studs inside.
    -- Thin shared regions use their center, the deepest point common to all members.
    local depth = C.MinimumTouchDepth + C.ArrivalDistance
    local inset = Vector3.new(math.min(depth, half.X), math.min(depth, half.Y), math.min(depth, half.Z))
    exitPoint = Vector3.new(
        math.clamp(exitPoint.X, center.X - half.X + inset.X, center.X + half.X - inset.X),
        math.clamp(exitPoint.Y, center.Y - half.Y + inset.Y, center.Y + half.Y - inset.Y),
        math.clamp(exitPoint.Z, center.Z - half.Z + inset.Z, center.Z + half.Z - inset.Z))

    return enforceAltitudeFloor(entryPoint), center, enforceAltitudeFloor(exitPoint)
end

function Planner.leg(position, node, startAt, now, options)
    local entry, _, exit = Planner.sweepPath(node, position, options.sweepMargin)
    local travel = options.travelEstimator and options.travelEstimator(position, entry)
        or Planner.travelSeconds(position, entry, options.speed)
    if travel > options.maxTravel then return nil end
    if node.readyAt and node.readyAt > startAt + travel then return nil end
    local sweepSeconds = options.travelEstimator(entry, exit)
    return {seconds = math.max(0.01, travel + sweepSeconds + options.service),
        probability = node.reliability, exitPosition = exit}
end

local function better(a, b)
    if math.abs(a.score - b.score) > 1e-9 then return a.score > b.score end
    if math.abs(a.elapsed - b.elapsed) > 1e-9 then return a.elapsed < b.elapsed end
    return a.first.id < b.first.id
end

local function keepBest(list, entry, limit)
    local index = #list + 1
    for i, old in ipairs(list) do if better(entry, old) then index = i; break end end
    if index <= limit then
        table.insert(list, index, entry)
        if #list > limit then table.remove(list) end
    end
end

function Planner.plan(position, nodes, now, options, forcedFirstId)
    local beam = {{position = position, elapsed = 0, reward = 0, visited = {}, route = {}}}
    local best
    local deadline = options.budget and (os.clock() + options.budget)
    -- Examine every first stop once, then bound deeper expansion by the CPU budget.
    local roots = {}
    for _, node in ipairs(nodes) do
        if not forcedFirstId or node.id == forcedFirstId then
            local leg = Planner.leg(position, node, now, now, options)
            if leg then
                local reward = node.value * leg.probability
                keepBest(roots, {position = leg.exitPosition, first = node, elapsed = leg.seconds,
                    reward = reward, score = reward / (leg.seconds + options.smoothing),
                    visited = {[node.id] = true}, route = {node}}, options.width)
            end
        end
    end
    if #roots == 0 then return nil end
    beam, best = roots, roots[1]
    for depth = 2, options.depth do
        local expanded = {}
        for _, state in ipairs(beam) do
            if depth > 1 and deadline and os.clock() >= deadline then return best end
            local branches = {}
            local exhausted = false
            for nodeIndex, node in ipairs(nodes) do
                if not state.visited[node.id] and (depth > 1 or not forcedFirstId or node.id == forcedFirstId) then
                    local leg = Planner.leg(state.position, node, now + state.elapsed, now, options)
                    if leg then
                        local elapsed = state.elapsed + leg.seconds
                        local reward = state.reward + node.value * leg.probability
                        local entry = {
                            position = leg.exitPosition, first = state.first or node,
                            elapsed = elapsed, reward = reward,
                            score = reward / (elapsed + options.smoothing),
                            parent = state, node = node,
                        }
                        keepBest(branches, entry, options.branch)
                    end
                end
                if nodeIndex % 16 == 0 and deadline and os.clock() >= deadline then
                    exhausted = true
                    break
                end
            end
            for _, entry in ipairs(branches) do
                entry.visited = table.clone(entry.parent.visited)
                entry.visited[entry.node.id] = true
                entry.route = table.clone(entry.parent.route)
                table.insert(entry.route, entry.node)
                entry.parent, entry.node = nil, nil
                keepBest(expanded, entry, options.width)
                if not best or better(entry, best) then best = entry end
            end
            if exhausted then return best end
        end
        if #expanded == 0 then break end
        beam = expanded
    end
    return best
end

-- DEALER & BOAT CORE
local dealerList, lastDealerScan = {}, -math.huge
local ownedBoat = nil

local function rememberDealer(npc)
    if not (npc:IsA("Model") or npc:IsA("BasePart"))
        or not npc.Name:lower():gsub("%s+", ""):find("boatdealer", 1, true) then return end
    local folder = workspace:FindFirstChild("NPCs")
    if not folder or not npc:IsDescendantOf(folder) then return end
    local position = npc:IsA("Model") and npc:GetPivot().Position or npc.Position
    for _, dealer in ipairs(dealerList) do
        if (dealer.position - position).Magnitude <= 5 then
            dealer.instance, dealer.position, dealer.name = npc, position, npc.Name
            return
        end
    end
    table.insert(dealerList, {instance = npc, position = position, name = npc.Name})
end

local function scanDealers(force)
    local now = workspace:GetServerTimeNow()
    if not force and now - lastDealerScan < 3 then return end
    lastDealerScan = now
    for i = #dealerList, 1, -1 do
        local dealer = dealerList[i]
        if dealer.instance and not dealer.instance:IsDescendantOf(workspace) then
            dealer.instance = nil
        end
    end
end

local function boatSeat(boat)
    if not boat or not boat.Parent then return nil end
    local seat = boat:FindFirstChild("VehicleSeat", true)
    return seat and seat:IsA("VehicleSeat") and seat or nil
end

local function boatBelongsToPlayer(boat)
    local seat = boatSeat(boat)
    if seat and seat.Occupant and seat.Occupant.Parent == player.Character then return true end
    local owner = boat:FindFirstChild("Owner", true)
    if not owner or not owner:IsA("ValueBase") then return false end
    local value = owner.Value
    return value == player or value == player.Name or value == player.UserId
        or tostring(value) == tostring(player.UserId)
end

-- CHARACTER & VELOCITY HELPERS
local function parts()
    local char = player.Character
    local root = char and char:FindFirstChild("HumanoidRootPart")
    local hum = char and char:FindFirstChildOfClass("Humanoid")
    if char and char:IsDescendantOf(workspace) and root and hum and hum.Health > 0 then
        return char, root, hum
    end
end

local function getLiveVelocity()
    local char, root, hum = parts()
    if not char or not root then return 0 end
    if hum and hum.SeatPart and hum.SeatPart:IsA("VehicleSeat") then
        local motionPart = hum.SeatPart.AssemblyRootPart or hum.SeatPart
        local vel = motionPart.AssemblyLinearVelocity or motionPart.Velocity
        return vel.Magnitude
    else
        local vel = root.AssemblyLinearVelocity or root.Velocity
        return vel.Magnitude
    end
end

-- UI CREATION
local playerGui = player:FindFirstChildOfClass("PlayerGui")
local GUI_NAME = "ChestFarmerBoat"
local globalEnvironment = getgenv and getgenv() or _G
if globalEnvironment.ChestFarmerBoatStop then pcall(globalEnvironment.ChestFarmerBoatStop) end
local oldGui = playerGui and playerGui:FindFirstChild(GUI_NAME)
if oldGui then oldGui:Destroy() end

local gui = Instance.new("ScreenGui")
gui.Name, gui.ResetOnSpawn = GUI_NAME, false
if LOAD_UI then
    if playerGui then
        gui.Parent = playerGui
    else
        task.spawn(function()
            local latePlayerGui = player:WaitForChild("PlayerGui")
            if not gui.Parent then gui.Parent = latePlayerGui end
        end)
    end
end

local panel = Instance.new("Frame")
panel.Size = UDim2.fromOffset(300, 224)
panel.Position = UDim2.fromOffset(15, 100)
panel.BackgroundColor3 = Color3.fromRGB(25, 28, 34)
panel.BorderSizePixel = 0
panel.Parent = gui

local corner = Instance.new("UICorner")
corner.CornerRadius = UDim.new(0, 10)
corner.Parent = panel

local panelScale = Instance.new("UIScale")
panelScale.Parent = panel

local function label(text, x, y, width, height, size, color)
    local item = Instance.new("TextLabel")
    item.BackgroundTransparency = 1
    item.Position = UDim2.fromOffset(x, y)
    item.Size = UDim2.fromOffset(width, height)
    item.Font = Enum.Font.Gotham
    item.TextSize = size
    item.TextColor3 = color
    item.TextXAlignment = Enum.TextXAlignment.Left
    item.TextTruncate = Enum.TextTruncate.AtEnd
    item.Text = text
    item.Parent = panel
    return item
end

local light = Color3.fromRGB(240, 242, 245)
local muted = Color3.fromRGB(160, 167, 178)
local titleLabel = label("Chest Farm · Initializing", 14, 10, 150, 26, 16, light)
label("Money gained", 14, 46, 130, 18, 11, muted)
label("Money / min", 157, 46, 130, 18, 11, muted)
local gainedLabel = label("—", 14, 65, 130, 24, 18, Color3.fromRGB(125, 225, 163))
local rateLabel = label("—", 157, 65, 130, 24, 18, light)

label("Live velocity", 14, 95, 130, 18, 11, muted)
label("Avg leg speed", 157, 95, 130, 18, 11, muted)
local speedLabel = label("0.0 studs/s", 14, 114, 130, 22, 14, Color3.fromRGB(80, 200, 255))
local avgSpeedLabel = label("—", 157, 114, 130, 22, 14, Color3.fromRGB(125, 225, 163))

local nextPassLabel = label("Watching for active chests…", 14, 142, 272, 17, 11, light)
local availabilityLabel = label("Finding chests…", 14, 160, 272, 17, 11, muted)
local plannerLabel = label("Live planner starting…", 14, 178, 272, 17, 10, muted)
local statusLabel = label("Starting…", 14, 198, 272, 17, 11, muted)
local updateSmallDetails

local stopButton = Instance.new("TextButton")
stopButton.Size = UDim2.fromOffset(60, 26)
stopButton.Position = UDim2.fromOffset(226, 10)
stopButton.Text = "Stop"
stopButton.Font = Enum.Font.Gotham
stopButton.TextSize = 12
stopButton.TextColor3 = light
stopButton.BackgroundColor3 = Color3.fromRGB(55, 60, 70)
stopButton.BorderSizePixel = 0
stopButton.Parent = panel

local buttonCorner = Instance.new("UICorner")
buttonCorner.CornerRadius = UDim.new(0, 6)
buttonCorner.Parent = stopButton

local mapButton = Instance.new("TextButton")
mapButton.Size = UDim2.fromOffset(54, 26)
mapButton.Position = UDim2.fromOffset(166, 10)
mapButton.Text = "Map"
mapButton.Font = Enum.Font.Gotham
mapButton.TextSize = 12
mapButton.TextColor3 = light
mapButton.BackgroundColor3 = Color3.fromRGB(55, 60, 70)
mapButton.BorderSizePixel = 0
mapButton.Parent = panel
local mapButtonCorner = Instance.new("UICorner")
mapButtonCorner.CornerRadius = UDim.new(0, 6)
mapButtonCorner.Parent = mapButton

local stopped = false
local connections = {}

local uiInput = game:GetService("UserInputService")
local uiLastViewport = Vector2.zero
local uiLastTouch = nil
local uiPanelPosition
local function positionStats(position)
    local camera = workspace.CurrentCamera
    if not camera then return end
    local topLeft, bottomRight = game:GetService("GuiService"):GetGuiInset()
    local usable = camera.ViewportSize - topLeft - bottomRight
    local size = Vector2.new(panel.Size.X.Offset, panel.Size.Y.Offset) * panelScale.Scale
    uiPanelPosition = Vector2.new(
        math.clamp(position.X, 0, math.max(0, usable.X - size.X)),
        math.clamp(position.Y, 0, math.max(0, usable.Y - size.Y)))
    panel.Position = UDim2.fromOffset(uiPanelPosition.X, uiPanelPosition.Y)
end
local function layoutStats()
    local camera = workspace.CurrentCamera
    if not camera then return end
    local viewport = camera.ViewportSize
    local touch = uiInput.TouchEnabled
    if viewport == uiLastViewport and touch == uiLastTouch then return end
    uiLastViewport, uiLastTouch = viewport, touch
    local topLeft, bottomRight = game:GetService("GuiService"):GetGuiInset()
    local usable = viewport - topLeft - bottomRight
    panelScale.Scale = math.min(1, math.max(0.5, (usable.X - 16) / 300))
    panel.Size = UDim2.fromOffset(300, touch and 240 or 224)
    positionStats(uiPanelPosition or Vector2.new(touch and 8 or 15, touch and 8 or math.min(100, math.max(8, usable.Y - 232))))
    stopButton.Size = UDim2.fromOffset(60, touch and 42 or 26)
    mapButton.Size = UDim2.fromOffset(54, touch and 42 or 26)
    stopButton.Position = UDim2.fromOffset(226, touch and 4 or 10)
    mapButton.Position = UDim2.fromOffset(166, touch and 4 or 10)
    for _, child in ipairs(panel:GetChildren()) do
        if child:IsA("TextLabel") then
            local originalY = child:GetAttribute("LayoutY")
            if not originalY then originalY = child.Position.Y.Offset; child:SetAttribute("LayoutY", originalY) end
            child.Position = UDim2.fromOffset(child.Position.X.Offset, originalY + (touch and originalY >= 46 and 16 or 0))
        end
    end
end
layoutStats()

-- Drag from the title area; Map and Stop remain independent tap targets.
local dragHandle = Instance.new("Frame")
dragHandle.Name = "DragHandle"
dragHandle.BackgroundTransparency = 1
dragHandle.Size = UDim2.fromOffset(158, 44)
dragHandle.Active = true
dragHandle.ZIndex = 5
dragHandle.Parent = panel
local statsDragInput, statsDragStart, statsDragPosition
table.insert(connections, dragHandle.InputBegan:Connect(function(input)
    if stopped or statsDragInput then return end
    if input.UserInputType ~= Enum.UserInputType.MouseButton1
        and input.UserInputType ~= Enum.UserInputType.Touch then return end
    statsDragInput = input
    statsDragStart = Vector2.new(input.Position.X, input.Position.Y)
    statsDragPosition = Vector2.new(panel.Position.X.Offset, panel.Position.Y.Offset)
end))
table.insert(connections, uiInput.InputChanged:Connect(function(input)
    if not statsDragInput or stopped or not panel.Visible then return end
    if input == statsDragInput or (statsDragInput.UserInputType == Enum.UserInputType.MouseButton1
        and input.UserInputType == Enum.UserInputType.MouseMovement) then
        local position = Vector2.new(input.Position.X, input.Position.Y)
        positionStats(statsDragPosition + position - statsDragStart)
    end
end))
table.insert(connections, uiInput.InputEnded:Connect(function(input)
    if input == statsDragInput then statsDragInput = nil end
end))

local virtualUser = game:GetService("VirtualUser")
local antiAfkConnection = player.Idled:Connect(function()
    virtualUser:CaptureController()
    virtualUser:ClickButton2(Vector2.new())
end)
table.insert(connections, antiAfkConnection)

local candidates, records = {}, {}
local chestOriginals = setmetatable({}, {__mode = "k"})
local currentRouteDestination
local currentTargetBoxCenter = nil
local currentTargetRecords = nil
local currentNextRoutePosition = nil
local choose, eligible, observeRecord
local liveNavigation = {plan = nil, nodeCount = 0, planMilliseconds = 0, speed = C.BoatSpeed}
local discovery = {active = DISCOVERY_RUN, regions = {}, completed = 0, skipped = 0,
    seededRecords = {}, lastSeedScan = -math.huge}
local streamingJob
local function requestStream(position)
    if not workspace.StreamingEnabled or stopped then return nil end
    if streamingJob and not streamingJob.done then
        return (streamingJob.position - position).Magnitude <= DISCOVERY_RADIUS and streamingJob or nil
    end
    local job = {position = position, done = false}
    streamingJob = job
    task.spawn(function()
        job.ok = pcall(function() player:RequestStreamAroundAsync(position, STREAM_TIMEOUT) end)
        job.done = true
    end)
    return job
end
local lastObserved = -math.huge

local function log(message)
    if C.Debug then print("[Chest Farmer] " .. message) end
end
local function display(message)
    if gui.Parent then statusLabel.Text = message end
end

local startingBeli, moneyStarted
local function formatMoney(value)
    local rounded = math.floor(math.abs(value) + 0.5)
    local digits = string.format("%.0f", rounded)
    local grouped = digits:reverse():gsub("(%d%d%d)", "%1,"):reverse():gsub("^,", "")
    return (value < 0 and rounded > 0 and "-$" or "$") .. grouped
end

local function readBeli()
    local data = player:FindFirstChild("Data")
    local value = data and data:FindFirstChild("Beli")
    if value and (value:IsA("IntValue") or value:IsA("NumberValue")) then return value.Value end
end

local function updateMoney()
    if not gui.Parent then return end
    local balance = readBeli()
    if not balance then return end
    local now = workspace:GetServerTimeNow()
    if startingBeli == nil then startingBeli, moneyStarted = balance, now end
    local gained = balance - startingBeli
    local elapsed = now - moneyStarted
    gainedLabel.Text = formatMoney(gained)
    rateLabel.Text = formatMoney(elapsed > 0 and gained * 60 / elapsed or 0)
end

local moneyRefreshAt = 0
table.insert(connections, RunService.Heartbeat:Connect(function()
    if not gui.Parent then return end
    layoutStats()
    local velocityText = string.format("%.1f studs/s", getLiveVelocity())
    if speedLabel.Text ~= velocityText then speedLabel.Text = velocityText end

    local now = os.clock()
    if now < moneyRefreshAt then return end
    moneyRefreshAt = now + 0.25
    updateMoney()
    if updateSmallDetails then updateSmallDetails() end
    if avgLegSpeed > 0 then
        avgSpeedLabel.Text = string.format("%.1f studs/s", avgLegSpeed)
    else
        avgSpeedLabel.Text = "—"
    end
end))

local function stop()
    if stopped then return end
    stopped = true
    display("Stopping farm…")
end
globalEnvironment.ChestFarmerBoatStop = stop
table.insert(connections, stopButton.Activated:Connect(stop))
table.insert(connections, gui.Destroying:Connect(stop))

local function waitCharacter(timeout)
    local deadline = workspace:GetServerTimeNow() + timeout
    repeat
        local char, root, hum = parts()
        if char then return char, root, hum end
        task.wait(C.PollSeconds)
    until stopped or workspace:GetServerTimeNow() >= deadline
end

local function available(part)
    return part and part:IsDescendantOf(workspace) and part.CanTouch == true
end
local function loadedTrigger(record)
    local trigger = record.spawnPart or record.part
    return trigger and trigger:IsDescendantOf(workspace) and trigger or nil
end
local function rememberedReadyAt(record, now)
    local trigger = loadedTrigger(record)
    if trigger then return trigger.CanTouch and now or math.huge end
    if not record.geometry or record.lastCanTouch == nil then return math.huge end
    if record.lastCanTouch then return now end
    return record.readyAt or math.huge
end
local function chestStatus(record, now)
    local trigger = loadedTrigger(record)
    if trigger and trigger.CanTouch then return "ready" end
    if not trigger and rememberedReadyAt(record, now) <= now then return "remembered" end
    if record.readyAt and record.readyAt < math.huge and record.readyAt > now then return "predicted" end
    return record.lastCanTouch == nil and "unknown" or "empty"
end

local function enforceChestHitbox(part)
    if stopped or not part:IsDescendantOf(workspace) then return end
    pcall(function()
        if part.Size ~= HITBOX_SIZE then part.Size = HITBOX_SIZE end
        if part.CanCollide then part.CanCollide = false end
    end)
end

local function track(obj)
    rememberDealer(obj)
    if not obj:IsA("BasePart") or not obj.Name:lower():find("chest", 1, true) then return end
    local visualFolder = workspace:FindFirstChild("ChestModels")
    if visualFolder and obj:IsDescendantOf(visualFolder) then return end

    if not chestOriginals[obj] then
        chestOriginals[obj] = {size = obj.Size, canCollide = obj.CanCollide}
        for _, property in ipairs({"Size", "CanCollide"}) do
            table.insert(connections, obj:GetPropertyChangedSignal(property):Connect(function()
                enforceChestHitbox(obj)
            end))
        end
        table.insert(connections, obj:GetPropertyChangedSignal("CanTouch"):Connect(function()
            if stopped or not obj:IsDescendantOf(workspace) then return end
            local record = candidates[obj]
            if type(record) ~= "table" then return end
            local attempt = record.pendingAttempt
            if not obj.CanTouch and attempt and not attempt.done and attempt.activeBefore and attempt.crossed
                and attempt.touchPart == obj then
                attempt.deactivatedAt = workspace:GetServerTimeNow()
            end
            if observeRecord then observeRecord(record, obj, workspace:GetServerTimeNow()) end
        end))
    end
    enforceChestHitbox(obj)

    if candidates[obj] == nil then candidates[obj] = true end
end
table.insert(connections, workspace.DescendantAdded:Connect(track))
local initialScanComplete = false
task.spawn(function()
    local pending = {workspace}
    while not stopped and #pending > 0 do
        for _ = 1, 200 do
            local item = table.remove(pending)
            if not item then break end
            track(item)
            for _, child in ipairs(item:GetChildren()) do table.insert(pending, child) end
        end
        task.wait()
    end
    initialScanComplete = not stopped
end)

-- REWARDS & COOLDOWNS (Server-Synchronized)
local function markEmpty(record, now, ours)
    if record.state == "empty" and record.emptyAt and now - record.emptyAt < 5 then
        if ours and record.lastLossCounted then
            record.otherLosses = math.max(0, (record.otherLosses or 0) - 1)
            record.lastLossCounted = false
        end
        return
    end
    record.emptyAt = now
    record.readyAt = now + (respawnOf(record) or economyOf(record).respawn)
    record.observationGap = false
    record.state = "empty"
    record.lastLossCounted = not ours
    if not ours then record.otherLosses = (record.otherLosses or 0) + 1 end
end

observeRecord = function(record, trigger, now)
    if not trigger or not trigger:IsDescendantOf(workspace) then
        record.loaded, record.observationGap = false, true
        return
    end
    local priorTouch = record.lastCanTouch
    local gap = record.observationGap
    record.spawnPart = trigger
    record.loaded = true
    if not record.geometry or record.geometry.cframe ~= trigger.CFrame or record.geometry.size ~= trigger.Size then
        record.geometry = {cframe = trigger.CFrame, size = trigger.Size}
    end
    record.position, record.lastObservedAt = trigger.Position, now
    if trigger.CanTouch then
        if priorTouch == false and record.emptyAt and not gap then
            local measured = now - record.emptyAt
            local nominal = economyOf(record).respawn
            if measured > nominal * 0.5 and measured < nominal * 1.5 then
                record.respawnEstimate = (record.respawnEstimate or nominal) * 0.8 + measured * 0.2
            end
        end
        record.part, record.state, record.collected = trigger, "ready", false
        record.emptyAt, record.readyAt = nil, now
        record.initiallyEmptyAt = nil
    else
        record.part = nil
        if priorTouch == true then
            local attempt = record.pendingAttempt
            markEmpty(record, now, attempt and attempt.deactivatedAt ~= nil)
        end
        record.state = "empty"
        if not record.emptyAt and not record.initiallyEmptyAt then
            -- Unknown startup collection time: a full respawn interval is a conservative bound.
            record.initiallyEmptyAt = now
            record.readyAt = now + (respawnOf(record) or economyOf(record).respawn)
        end
    end
    record.lastCanTouch, record.observationGap = trigger.CanTouch, false
end

local pendingAttempts = {}
local startupMissReports = 0
local lastPickupBalance = readBeli()
local function beginPending(record, sweepSeconds)
    if record.pendingAttempt then return record.pendingAttempt end
    local now = workspace:GetServerTimeNow()
    local trigger = record.spawnPart or record.part
    if not available(trigger) then return nil end
    local attempt = {record = record, touchPart = trigger, activeBefore = available(trigger) == true,
        expires = now + sweepSeconds + C.PendingConfirmSeconds + 1, crossed = false}
    record.pendingAttempt = attempt
    record.pendingUntil = attempt.expires
    table.insert(pendingAttempts, attempt)
    return attempt
end

local function confirmPickup(attempt, now)
    if attempt.done then return end
    attempt.done = true
    local record = attempt.record
    markEmpty(record, now, true)
    record.collected = true
    record.successes = (record.successes or 0) + 1
    record.failures = 0
    record.cooldown = now + 1
end

table.insert(connections, RunService.Heartbeat:Connect(function()
    local now, balance = workspace:GetServerTimeNow(), readBeli()
    if balance and lastPickupBalance and balance > lastPickupBalance then
        local gain = balance - lastPickupBalance
        -- Beli can combine several pickups or come from another source.
        -- Retain it as supporting evidence; it cannot identify a chest by itself.
        for _, attempt in ipairs(pendingAttempts) do
            if not attempt.done and attempt.crossed and now <= attempt.expires then
                attempt.beliEvidence = (attempt.beliEvidence or 0) + gain
            end
        end
    end
    if balance then lastPickupBalance = balance end
    for i = #pendingAttempts, 1, -1 do
        local attempt = pendingAttempts[i]
        -- A pass alone is not a pickup: require contact, deactivation and reward evidence.
        local observedPart = attempt.touchPart
        if not attempt.done and attempt.crossed and attempt.activeBefore and now <= attempt.expires
            and (attempt.beliEvidence or 0) > 0
            and (attempt.deactivatedAt or (observedPart and observedPart:IsDescendantOf(workspace) and observedPart.CanTouch == false)) then
            confirmPickup(attempt, attempt.deactivatedAt or now)
        end
        if attempt.done or now >= attempt.expires then
            local record = attempt.record
            if record.pendingAttempt == attempt then
                record.pendingAttempt, record.pendingUntil = nil, nil
                if not attempt.done and attempt.crossed and attempt.activeBefore
                    and observedPart and observedPart:IsDescendantOf(workspace) and observedPart.CanTouch then
                    if startupMissReports < 5 then
                        startupMissReports += 1
                        local char, root, hum = parts()
                        local touchParts, anchoredParts = 0, 0
                        if char then
                            for _, part in ipairs(char:GetDescendants()) do
                                if part:IsA("BasePart") then
                                    if part.CanTouch then touchParts += 1 end
                                    if part.Anchored then anchoredParts += 1 end
                                end
                            end
                        end
                        local trigger = record.part or record.spawnPart
                        log(string.format("Miss %d: %s | active trigger=%s | trigger CanTouch=%s | character touch parts=%d | anchored parts=%d | seated=%s | scan complete=%s",
                            startupMissReports, record.name, tostring(available(trigger) == true),
                            tostring(trigger and trigger.CanTouch), touchParts, anchoredParts,
                            tostring(hum and hum.SeatPart ~= nil), tostring(initialScanComplete)))
                    end
                    record.misses = (record.misses or 0) + 1
                    record.failures = (record.failures or 0) + 1
                    record.cooldown = now + math.min(C.MaxChestCooldown, C.ChestFailureCooldown * 2 ^ math.min(record.failures - 1, 4))
                end
            end
            table.remove(pendingAttempts, i)
        end
    end
end))

local function refreshChests(force)
    local now = workspace:GetServerTimeNow()
    if not force and now - lastObserved < C.ObserveSeconds then return end
    local observedSeconds = math.min(2, math.max(0, now - lastObserved))
    lastObserved = now
    for _, r in ipairs(records) do r.spawnPart = nil; r.part = nil end
    for part in pairs(candidates) do
        if not part:IsDescendantOf(workspace) then
            candidates[part] = nil
        else
            enforceChestHitbox(part)

            local r = candidates[part]
            if r == true then
                r = nil
                for _, existing in ipairs(records) do
                    if existing.name == part.Name and (existing.position - part.Position).Magnitude <= C.MatchRadius then
                        r = existing
                        break
                    end
                end
                if not r then
                    r = {id = #records + 1, name = part.Name, position = part.Position,
                        cooldown = 0, failures = 0, successes = 0, misses = 0,
                        exposure = 0, otherLosses = 0, state = "unknown",
                        readyAt = math.huge}
                    table.insert(records, r)
                end
                candidates[part] = r
            end
            r.spawnPart = part
            if available(part) then r.part = part end
            r.position = part.Position
        end
    end
    for _, r in ipairs(records) do
        if r.state == "ready" then r.exposure = (r.exposure or 0) + observedSeconds end
        observeRecord(r, r.spawnPart, now)
    end
end

-- FALLBACK WALKING MOVEMENT (Only used when acquiring initial boat)
local function approachPosition(target, stopRadius)
    local char, root, hum = parts()
    if not char then return false end
    target = enforceAltitudeFloor(target)
    local previousFlight = root:FindFirstChild("ContinuousFlightVelocity")
    local flightWasEnabled = previousFlight and previousFlight.Enabled
    if previousFlight then previousFlight.Enabled = false end
    local attachment = Instance.new("Attachment", root)
    local drive = Instance.new("LinearVelocity", root)
    drive.Attachment0 = attachment
    drive.RelativeTo = Enum.ActuatorRelativeTo.World
    drive.ForceLimitsEnabled = false

    local originals = {}
    local function disableCollision(part)
        if not part:IsA("BasePart") then return end
        if originals[part] == nil then originals[part] = part.CanCollide end
        part.CanCollide = false
    end
    for _, part in ipairs(char:GetDescendants()) do disableCollision(part) end
    local added = char.DescendantAdded:Connect(disableCollision)
    local noclipConn = RunService.PreSimulation:Connect(function()
        for part in pairs(originals) do
            if part.Parent and part.CanCollide then part.CanCollide = false end
        end
    end)

    local estimate = Planner.travelSeconds(root.Position, target, C.Speed)
    local began = workspace:GetServerTimeNow()
    local arrived = false
    while not stopped and player.Character == char and hum.Health > 0 and workspace:GetServerTimeNow() - began <= estimate * 2 + 3 do
        RunService.Heartbeat:Wait()
        local delta = target - root.Position
        if delta.Magnitude <= stopRadius then arrived = true; break end
        drive.VectorVelocity = delta.Unit * C.Speed
    end
    noclipConn:Disconnect()
    added:Disconnect()
    for part, canCollide in pairs(originals) do
        if part.Parent then part.CanCollide = canCollide end
    end
    drive:Destroy()
    attachment:Destroy()
    if previousFlight and previousFlight.Parent then
        previousFlight.Enabled = flightWasEnabled
    end
    return arrived
end

local preparedBoat, boatSeatCleanupConnection
local function prepareDinghySeats(boat)
    if not boat or not boat.Parent or preparedBoat == boat then return end
    if boatSeatCleanupConnection then boatSeatCleanupConnection:Disconnect() end
    preparedBoat = boat
    local function removePassengerSeat(part)
        if part:IsA("Seat") or (part:IsA("VehicleSeat") and part.Name ~= "VehicleSeat") then
            part:Destroy()
        end
    end
    -- Watch before scanning so seats arriving with streamed boat parts are covered too.
    boatSeatCleanupConnection = boat.DescendantAdded:Connect(function(part)
        if not stopped then removePassengerSeat(part) end
    end)
    for _, part in ipairs(boat:GetDescendants()) do removePassengerSeat(part) end
end

local function buyDinghy()
    local boats = workspace:FindFirstChild("Boats") or workspace:WaitForChild("Boats", 3)
    local remoteFolder = ReplicatedStorage:FindFirstChild("Remotes")
    local event = remoteFolder and remoteFolder:FindFirstChild("CommF_")
    if not boats or not event then return nil end
    local _, root = parts()
    for _, existing in ipairs(boats:GetChildren()) do
        local seat = boatSeat(existing)
        if seat and root and boatBelongsToPlayer(existing) and (root.Position - seat.Position).Magnitude <= 500 then
            prepareDinghySeats(existing)
            return existing
        end
    end
    local seen = {}
    for _, child in ipairs(boats:GetChildren()) do seen[child] = true end
    local arrivals = {}
    local added = boats.ChildAdded:Connect(function(child) table.insert(arrivals, child) end)
    task.spawn(function() pcall(function() event:InvokeServer("BuyBoat", "Dinghy") end) end)
    local deadline = workspace:GetServerTimeNow() + 8
    local found
    repeat
        for _, boat in ipairs(arrivals) do
            if boat.Parent == boats and not seen[boat] and boatBelongsToPlayer(boat) and boatSeat(boat) then
                found = boat
                break
            end
        end
        if found then break end
        task.wait(0.1)
    until stopped or workspace:GetServerTimeNow() >= deadline
    added:Disconnect()
    if found then prepareDinghySeats(found) end
    return found
end

local function boardBoat(boat)
    prepareDinghySeats(boat)
    local char, root, hum = parts()
    local seat = boatSeat(boat)
    if not char or not seat then return false end
    seat:Sit(hum)
    local deadline = workspace:GetServerTimeNow() + 2
    while not stopped and workspace:GetServerTimeNow() < deadline and seat.Occupant ~= hum do task.wait(0.05) end
    return seat.Occupant == hum
end

-- SMART VEHICLE/FALLBACK RESOLVER
local function getActiveVehicleOrCharacter()
    local char, root, hum = parts()
    if not char then return nil, nil end

    if hum.SeatPart and hum.SeatPart:IsA("VehicleSeat") then
        local boat = hum.SeatPart:FindFirstAncestorOfClass("Model")
        if boat then
            ownedBoat = boat
            prepareDinghySeats(boat)
            local seat = boatSeat(boat)
            if seat and seat.Occupant == hum then
                titleLabel.Text = "Chest Farm · 100% Boat"
                return boat, seat
            end
        end
    end

    if ownedBoat and ownedBoat.Parent then
        prepareDinghySeats(ownedBoat)
        local seat = boatSeat(ownedBoat)
        if seat and (root.Position - seat.Position).Magnitude <= 350 then
            display("Boarding owned Dinghy…")
            approachPosition(seat.Position + Vector3.new(0, 3, 0), 10)
            if boardBoat(ownedBoat) then
                titleLabel.Text = "Chest Farm · 100% Boat"
                return ownedBoat, seat
            end
        end
    end

    scanDealers(true)
    local nearestDealer, nearestDist = nil, math.huge
    for _, dealer in ipairs(dealerList) do
        local dist = (root.Position - dealer.position).Magnitude
        if dist < nearestDist then
            nearestDealer, nearestDist = dealer, dist
        end
    end

    if nearestDealer and nearestDist <= 1200 then
        display("Boat Dealer found! Getting Dinghy…")
        if (root.Position - nearestDealer.position).Magnitude > BOAT_APPROACH_RANGE then
            approachPosition(nearestDealer.position, BOAT_APPROACH_RANGE)
        end
        local boat = buyDinghy()
        if boat then
            ownedBoat = boat
            local seat = boatSeat(boat)
            if seat then
                approachPosition(seat.Position + Vector3.new(0, 3, 0), 8)
                if boardBoat(boat) then
                    titleLabel.Text = "Chest Farm · 100% Boat"
                    return boat, seat
                end
            end
        end
    end

    titleLabel.Text = "Chest Farm · On Foot (Scanning)"
    return nil, nil
end

-- 3D CONTINUOUS FLIGHT CONTROLLER
local persistentLinearVelocity = nil
local persistentAttachment = nil
local persistentNoclipConn = nil
local noclipChar, noclipBoat = nil, nil
local noclipOriginals = {}
local noclipAdded = {}

local function restoreFlightCollision()
    for _, connection in ipairs(noclipAdded) do connection:Disconnect() end
    table.clear(noclipAdded)
    for part, canCollide in pairs(noclipOriginals) do
        if part.Parent then part.CanCollide = canCollide end
    end
    table.clear(noclipOriginals)
    noclipChar, noclipBoat = nil, nil
end

local function setFlightCollisionTargets(char, boat, motionPart)
    if char == noclipChar and boat == noclipBoat then return end
    restoreFlightCollision()
    noclipChar, noclipBoat = char, boat
    local function disable(part)
        if not part:IsA("BasePart") then return end
        if noclipOriginals[part] == nil then noclipOriginals[part] = part.CanCollide end
        part.CanCollide = false
    end
    local function watch(container)
        if not container then return end
        for _, part in ipairs(container:GetDescendants()) do disable(part) end
        table.insert(noclipAdded, container.DescendantAdded:Connect(disable))
    end
    watch(char)
    watch(boat)
    if motionPart then
        disable(motionPart)
        local ok, connected = pcall(function() return motionPart:GetConnectedParts(true) end)
        if ok then for _, part in ipairs(connected) do disable(part) end end
    end
    if not persistentNoclipConn then
        persistentNoclipConn = RunService.PreSimulation:Connect(function()
            for part in pairs(noclipOriginals) do
                if not part.Parent then
                    noclipOriginals[part] = nil
                elseif part.CanCollide then
                    part.CanCollide = false
                end
            end
        end)
    end
end

local activeFlightCommand
local function applyFlightCommand(command, now)
    local motionPart, lv, dir = command.part, command.constraint, command.direction
    if stopped or not motionPart.Parent or not lv.Parent then return end
    local speed = command.isBoat and math.clamp(getSinusoidalSpeed(now), SPEED_MIN, SPEED_MAX) or C.Speed
    local velocity = dir * speed
    if command.seat and command.seat.Parent then
        -- Native propulsion must not compete with the world-space flight velocity.
        command.seat.ThrottleFloat = 0
        command.seat.SteerFloat = 0
    end
    local up = math.abs(dir.Y) > 0.999 and Vector3.new(0, 0, -1) or Vector3.yAxis
    motionPart.CFrame = CFrame.lookAt(motionPart.Position, motionPart.Position + dir, up)
    motionPart.AssemblyAngularVelocity = Vector3.zero
    lv.VectorVelocity = velocity
    motionPart.AssemblyLinearVelocity = velocity
end
-- Persistent across adjacent legs; update before physics without stopping at boundaries.
table.insert(connections, RunService.PreSimulation:Connect(function()
    local command = activeFlightCommand
    if command then
        if command.character ~= player.Character or not command.part.Parent or not command.constraint.Parent then
            activeFlightCommand = nil
            return
        end
        applyFlightCommand(command, workspace:GetServerTimeNow())
    end
end))

local function getFlightConstraint(boat, seat)
    local char, root = parts()
    local motionPart = seat and (seat.AssemblyRootPart or seat) or root
    if not motionPart then return nil, nil end

    if not persistentLinearVelocity or persistentLinearVelocity.Parent ~= motionPart then
        if persistentLinearVelocity then persistentLinearVelocity:Destroy() end
        if persistentAttachment then persistentAttachment:Destroy() end

        persistentAttachment = Instance.new("Attachment")
        persistentAttachment.Name = "ContinuousFlightAttachment"
        persistentAttachment.Parent = motionPart

        persistentLinearVelocity = Instance.new("LinearVelocity")
        persistentLinearVelocity.Name = "ContinuousFlightVelocity"
        persistentLinearVelocity.Attachment0 = persistentAttachment
        persistentLinearVelocity.RelativeTo = Enum.ActuatorRelativeTo.World
        persistentLinearVelocity.ForceLimitsEnabled = false
        persistentLinearVelocity.MaxForce = math.huge
        persistentLinearVelocity.VectorVelocity = Vector3.zero
        persistentLinearVelocity.Parent = motionPart
    end

    setFlightCollisionTargets(char, boat, motionPart)

    return persistentLinearVelocity, motionPart
end

local function pauseFlight()
    activeFlightCommand = nil
    if persistentLinearVelocity and persistentLinearVelocity.Parent then
        persistentLinearVelocity.VectorVelocity = Vector3.zero
        local motionPart = persistentLinearVelocity.Parent
        if motionPart:IsA("BasePart") then
            motionPart.AssemblyLinearVelocity = Vector3.zero
            motionPart.AssemblyAngularVelocity = Vector3.zero
        end
    end
end

-- Moves in a single straight 3D vector directly to the destination with INSTANT acceleration and FULL 3D PITCH
local function move3D(boat, seat, destination, recordsToArm, guard)
    local char, root, hum = parts()
    if not char then return "interrupted" end

    local lv, motionPart = getFlightConstraint(boat, seat)
    if not lv or not motionPart then return "interrupted" end

    local began = workspace:GetServerTimeNow()
    local startPos = root.Position
    local delta = destination - startPos
    local totalDist = delta.Magnitude
    local function armNearby(position)
        if not recordsToArm then return end
        for _, rec in ipairs(recordsToArm) do
            local trigger = rec.spawnPart or rec.part
            local offset = trigger and trigger.CFrame:PointToObjectSpace(position)
            local half = trigger and trigger.Size * 0.5
            if available(trigger) and math.abs(offset.X) <= half.X + 12
                and math.abs(offset.Y) <= half.Y + 12 and math.abs(offset.Z) <= half.Z + 12 then
                local attempt = beginPending(rec, 0.05)
                if attempt and math.abs(offset.X) <= half.X and math.abs(offset.Y) <= half.Y
                    and math.abs(offset.Z) <= half.Z then attempt.crossed = true end
            end
        end
    end

    armNearby(startPos)
    if totalDist <= C.ArrivalDistance then return "arrived" end

    local initialDir = delta.Unit
    local isBoat = (boat ~= nil and seat ~= nil)
    local minSpeed = isBoat and SPEED_MIN or C.Speed
    local timeout = (totalDist / minSpeed) + 3.0
    local outcome = "arrived"
    local movingDistance, movingTime = 0, 0
    local previousPosition, previousTime = motionPart.Position, os.clock()

    local command = {part = motionPart, constraint = lv, direction = initialDir,
        seat = seat, isBoat = isBoat, character = char}
    local function drive(dir, now)
        command.direction = dir
        activeFlightCommand = command
        applyFlightCommand(command, now)
    end

    drive(initialDir, began)

    while not stopped and player.Character == char and hum.Health > 0 do
        RunService.Heartbeat:Wait()
        local frameTime = os.clock()
        local framePosition = motionPart.Position
        movingDistance += (framePosition - previousPosition).Magnitude
        movingTime += frameTime - previousTime
        previousPosition, previousTime = framePosition, frameTime
        if isBoat and seat.Occupant ~= hum then
            seat:Sit(hum)
            if seat.Occupant ~= hum then outcome = "interrupted"; break end
        end

        local now = workspace:GetServerTimeNow()
        if now - began > timeout then outcome = "timeout"; break end

        local currentPos = root.Position
        local currentDelta = destination - currentPos
        local currentDist = currentDelta.Magnitude

        armNearby(currentPos)
        if guard then
            local decision = guard(now, currentPos, currentDist)
            if decision then outcome = decision; break end
        end

        -- Cutoff check
        local passed = (currentPos - startPos):Dot(initialDir) >= totalDist
        if currentDist <= C.ArrivalDistance or passed then
            outcome = "arrived"
            break
        end

        -- Each leg is a straight velocity vector; do not chase an overshot endpoint.
        drive(initialDir, now)
    end

    if stopped or player.Character ~= char or hum.Health <= 0 then
        outcome = "interrupted"
    end
    if outcome == "arrived" then
        recordLegPerformance(movingDistance, movingTime, isBoat and "boat" or "foot")
    elseif outcome == "timeout" or outcome == "interrupted" then
        pauseFlight()
    end

    return outcome
end

-- One short pass collects every chest in an overlapping trigger cluster.
local function flyThroughTarget(boat, seat, node)
    local char, root = parts()
    if not char then return "interrupted" end
    local recordsList = node.records
    local entryPoint, centerPoint, exitPoint = Planner.sweepPath(node, root.Position, C.SweepMargin)
    currentTargetBoxCenter, currentTargetRecords = centerPoint, recordsList
    local function finish(outcome)
        currentRouteDestination, currentTargetBoxCenter = nil, nil
        currentTargetRecords, currentNextRoutePosition = nil, nil
        return outcome
    end
    local function hasActive()
        for _, record in ipairs(recordsList) do
            if available(record.spawnPart or record.part) then return true end
        end
        return false
    end
    local function hasMissing()
        for _, record in ipairs(recordsList) do if not loadedTrigger(record) then return true end end
        return false
    end
    local function hasPotential(now, seconds)
        for _, record in ipairs(recordsList) do
            if rememberedReadyAt(record, now) <= now + seconds then return true end
        end
        return false
    end
    local approachSeconds = Planner.travelSeconds(root.Position, entryPoint, liveNavigation.speed)
    if not hasPotential(workspace:GetServerTimeNow(), approachSeconds) then return finish("unavailable") end
    local verificationJob
    local began, lastReplan = workspace:GetServerTimeNow(), workspace:GetServerTimeNow()
    local targetName = node.isCluster and ("Pass · " .. #recordsList .. " chests") or node.record.name
    local function guard(now, position, remaining)
        if not hasPotential(now, Planner.travelSeconds(position, entryPoint, liveNavigation.speed)) then return "unavailable" end
        if hasMissing() and not verificationJob and remaining <= liveNavigation.speed * STREAM_TIMEOUT + DISCOVERY_RADIUS then
            verificationJob = requestStream(centerPoint)
        end
        if remaining <= C.FinishDistance or now - began < C.MinimumCommitSeconds
            or now - lastReplan < C.ReplanSeconds then return end
        -- Finish the short pass once contact is possible; never reroute during confirmation.
        for _, record in ipairs(recordsList) do
            if record.pendingAttempt then return end
        end
        lastReplan = now
        refreshChests()
        local _, currentRoot = parts()
        if not currentRoot then return "interrupted" end
        local best = choose(currentRoot, nil, liveNavigation.speed)
        if not best then return "unavailable" end
        local sameTarget = best.first.id == node.id
        local retained = sameTarget and best or choose(currentRoot, node.record, liveNavigation.speed)
        if not sameTarget and (not retained or best.score > retained.score * C.SwitchGain) then
            return "reroute"
        end
        -- Preserve the displayed committed route when a marginal alternative is rejected.
        if retained then
            liveNavigation.plan = retained
            currentNextRoutePosition = retained.route[2] and retained.route[2].position or nil
        end
    end
    display("Approach · " .. targetName)
    currentRouteDestination = entryPoint
    local approach = move3D(boat, seat, entryPoint, recordsList, guard)
    if approach ~= "arrived" then return finish(approach) end
    refreshChests(true)
    if not hasActive() and hasMissing() then
        if workspace.StreamingEnabled then
            verificationJob = verificationJob or requestStream(centerPoint)
            pauseFlight()
            local deadline = workspace:GetServerTimeNow() + STREAM_TIMEOUT
            while not stopped and not hasActive() and workspace:GetServerTimeNow() < deadline do
                RunService.Heartbeat:Wait()
                refreshChests()
                local allLoaded = true
                for _, record in ipairs(recordsList) do if not loadedTrigger(record) then allLoaded = false; break end end
                if allLoaded then break end
                if not verificationJob then verificationJob = requestStream(centerPoint) end
            end
            refreshChests(true)
        end
    end
    if stopped then return finish("interrupted") end
    if not hasActive() then
        for _, record in ipairs(recordsList) do
            if not loadedTrigger(record) then record.cooldown = workspace:GetServerTimeNow() + C.ChestFailureCooldown end
        end
        return finish("unavailable")
    end
    display("Passing through · " .. targetName)
    currentRouteDestination = exitPoint
    -- No timer wait and no artificial confirmation pause: continue through the hitboxes.
    return finish(move3D(boat, seat, exitPoint, recordsList))
end
-- Loaded inactivity wins. Remembered data is usable only when its hitbox is unloaded.
eligible = function(record, now)
    local pending = record.pendingAttempt
    return rememberedReadyAt(record, now) < math.huge
        and not (pending and not pending.done)
        and now >= (record.cooldown or 0)
end

-- Conservatively inscribe an axis-aligned cube in a rotated hitbox.
local function triggerBounds(record)
    local geometry = record.geometry
    if geometry.low then return geometry.low, geometry.high end
    local half = geometry.size * 0.5
    local rotation = geometry.cframe
    local right, up, look = rotation.RightVector, rotation.UpVector, rotation.LookVector
    local radius = math.min(
        half.X / (math.abs(right.X) + math.abs(right.Y) + math.abs(right.Z)),
        half.Y / (math.abs(up.X) + math.abs(up.Y) + math.abs(up.Z)),
        half.Z / (math.abs(look.X) + math.abs(look.Y) + math.abs(look.Z)))
    local extent = Vector3.new(radius, radius, radius)
    local low, high = rotation.Position - extent, rotation.Position + extent
    low = Vector3.new(low.X, math.max(low.Y, C.MinRootY), low.Z)
    geometry.low, geometry.high = low, high
    return low, high
end
local function overlapBounds(aLow, aHigh, bLow, bHigh)
    local low = Vector3.new(math.max(aLow.X, bLow.X), math.max(aLow.Y, bLow.Y), math.max(aLow.Z, bLow.Z))
    local high = Vector3.new(math.min(aHigh.X, bHigh.X), math.min(aHigh.Y, bHigh.Y), math.min(aHigh.Z, bHigh.Z))
    return low, high
end
local function buildFusedClusters(rawNodes)
    local nodes, used = {}, {}
    for index, anchor in ipairs(rawNodes) do
        if used[index] then continue end
        local group, low, high = {anchor.record}, anchor.low, anchor.high
        local value, expected, readyAt = anchor.value, anchor.value * anchor.reliability, anchor.readyAt
        used[index] = true
        for other = index + 1, #rawNodes do
            if used[other] then continue end
            local candidate = rawNodes[other]
            if anchor.inferred ~= candidate.inferred or math.abs(readyAt - candidate.readyAt) > 3 then continue end
            if candidate.low.X > high.X - 4 or candidate.high.X < low.X + 4
                or candidate.low.Y > high.Y - 4 or candidate.high.Y < low.Y + 4
                or candidate.low.Z > high.Z - 4 or candidate.high.Z < low.Z + 4 then continue end
            local nextLow, nextHigh = overlapBounds(low, high, candidate.low, candidate.high)
            local span = nextHigh - nextLow
            -- Every member shares a real touch region; one short pass can collect all.
            if math.min(span.X, span.Y, span.Z) >= 4 then
                low, high = nextLow, nextHigh
                value += candidate.value
                expected += candidate.value * candidate.reliability
                readyAt = math.max(readyAt, candidate.readyAt)
                table.insert(group, candidate.record)
                used[other] = true
            end
        end
        table.insert(nodes, {id = anchor.record.id, record = anchor.record, records = group,
            position = (low + high) * 0.5, halfSize = (high - low) * 0.5,
            value = value, reliability = expected / value,
            readyAt = readyAt, inferred = anchor.inferred,
            isCluster = #group > 1, clusterCount = #group})
    end
    return nodes
end
local function snapshotNodes(now)
    local raw = {}
    for _, record in ipairs(records) do
        if not eligible(record, now) then continue end
        local low, high = triggerBounds(record)
        if high.Y <= low.Y then continue end
        local reward = economyOf(record).rewards[REWARD_SEA]
        local inferred = loadedTrigger(record) == nil
        table.insert(raw, {record = record, low = low, high = high, value = (reward[1] + reward[2]) * 0.5,
            readyAt = rememberedReadyAt(record, now), inferred = inferred,
            reliability = math.clamp((record.successes + 8) / (record.successes + record.misses + 8), 0.3, 1)
                * (inferred and 0.85 or 1)})
    end
    return buildFusedClusters(raw)
end
local function plannerOptions(speed)
    return {speed = speed, maxTravel = MAX_TRAVEL_SECONDS,
        travelEstimator = function(from, dest) return Planner.travelSeconds(from, dest, speed) end,
        service = ESTIMATED_PICKUP_SECONDS, sweepMargin = C.SweepMargin,
        depth = LOOKAHEAD_STOPS, width = C.BeamWidth, branch = C.BranchWidth,
        smoothing = 5, budget = C.PlanBudgetSeconds}
end
choose = function(root, forcedRecord, currentSpeed)
    local now, began = workspace:GetServerTimeNow(), os.clock()
    local nodes = snapshotNodes(now)
    local plan = Planner.plan(root.Position, nodes, now, plannerOptions(currentSpeed or C.BoatSpeed),
        forcedRecord and forcedRecord.id)
    if plan then plan.node, plan.record = plan.first, plan.first.record end
    if not forcedRecord then
        liveNavigation.plan = plan
        liveNavigation.nodeCount, liveNavigation.planMilliseconds = #nodes, (os.clock() - began) * 1000
    end
    return plan
end

-- Discovery visits chest locations only and expands as more chest parts stream in.
-- It is separate from dynamic farming and never treats a visit as collection evidence.
local function addDiscoveryRegion(record)
    local position = enforceAltitudeFloor(record.position)
    for _, region in ipairs(discovery.regions) do
        if (region.position - position).Magnitude <= DISCOVERY_RADIUS then return end
    end
    table.insert(discovery.regions, {position = position, name = record.name, record = record, visits = 0})
end
local function collectDiscoverySeeds(force)
    if not discovery.active then return end
    local now = os.clock()
    if not force and now - discovery.lastSeedScan < 1 then return end
    discovery.lastSeedScan = now
    for _, record in ipairs(records) do
        if not discovery.seededRecords[record.id] then
            discovery.seededRecords[record.id] = true
            addDiscoveryRegion(record)
        end
    end
end
local function discoveryNode(region)
    if region.record.geometry then
        local low, high = triggerBounds(region.record)
        return {position = (low + high) * 0.5, halfSize = (high - low) * 0.5}
    end
    return {position = region.position}
end
local function chooseDiscovery(root, speed)
    collectDiscoverySeeds(true)
    local nearest, distance
    for _, region in ipairs(discovery.regions) do
        local trigger = loadedTrigger(region.record)
        if not region.visited and trigger and not trigger.CanTouch then
            -- Its state is already observed; discovery does not need a trip to an empty chest.
            region.visited = true
            discovery.completed += 1
        end
        if not region.visited then
            local entry = Planner.sweepPath(discoveryNode(region), root.Position, C.SweepMargin)
            local candidate = Planner.travelSeconds(root.Position, entry, speed)
            if candidate <= MAX_TRAVEL_SECONDS and (not distance or candidate < distance) then
                nearest, distance = region, candidate
            end
        end
    end
    if not nearest then
        for _, region in ipairs(discovery.regions) do
            if not region.visited then region.visited = true; discovery.skipped += 1 end
        end
        discovery.active = false
        log(string.format("Discovery complete: %d areas sampled, %d chest locations remembered, %d far areas skipped",
            discovery.completed, #records, discovery.skipped))
    end
    return nearest
end
local function discoverArea(boat, seat, region)
    local _, root = parts()
    if not root then return "interrupted" end
    local entryPoint, centerPoint, exitPoint = Planner.sweepPath(discoveryNode(region), root.Position, C.SweepMargin)
    local nearRecords = {}
    for _, record in ipairs(records) do
        if (record.position - region.position).Magnitude <= DISCOVERY_RADIUS then table.insert(nearRecords, record) end
    end
    liveNavigation.plan = nil
    currentRouteDestination, currentTargetRecords = entryPoint, nearRecords
    currentTargetBoxCenter, currentNextRoutePosition = centerPoint, nil
    display(string.format("Discovering %d/%d · %s", discovery.completed + 1, #discovery.regions, region.name))
    local job
    local knownRecordCount = #records
    local function streamGuard(_, _, remaining)
        refreshChests()
        local trigger = loadedTrigger(region.record)
        if trigger and not trigger.CanTouch then return "unavailable" end
        for index = knownRecordCount + 1, #records do
            local record = records[index]
            if (record.position - region.position).Magnitude <= DISCOVERY_RADIUS then
                table.insert(nearRecords, record)
            end
        end
        knownRecordCount = #records
        if not job and remaining <= liveNavigation.speed * STREAM_TIMEOUT + DISCOVERY_RADIUS then
            job = requestStream(region.position)
        end
    end
    local outcome = move3D(boat, seat, entryPoint, nearRecords, streamGuard)
    if outcome == "arrived" then
        if workspace.StreamingEnabled and not loadedTrigger(region.record) and (not job or not job.done) then
            job = job or requestStream(region.position)
            pauseFlight()
            local deadline = workspace:GetServerTimeNow() + STREAM_TIMEOUT
            repeat
                RunService.Heartbeat:Wait()
                refreshChests()
                if not job then job = requestStream(region.position) end
            until stopped or loadedTrigger(region.record) or (job and job.done) or workspace:GetServerTimeNow() >= deadline
        end
        refreshChests(true)
        collectDiscoverySeeds(true)
        if available(loadedTrigger(region.record)) then
            currentRouteDestination = exitPoint
            outcome = move3D(boat, seat, exitPoint, nearRecords)
        end
        if outcome == "arrived" then
            region.visited = true
            discovery.completed += 1
        elseif outcome == "timeout" then
            region.visits += 1
            if region.visits >= 2 then region.visited = true; discovery.skipped += 1 end
        end
    elseif outcome == "timeout" then
        region.visits += 1
        if region.visits >= 2 then region.visited = true; discovery.skipped += 1 end
    end
    currentRouteDestination, currentTargetBoxCenter = nil, nil
    currentTargetRecords, currentNextRoutePosition = nil, nil
    return outcome
end

updateSmallDetails = function()
    local ready, expected, waiting, confirmed, loaded = 0, 0, 0, 0, 0
    local now = workspace:GetServerTimeNow()
    for _, record in ipairs(records) do
        confirmed += record.successes or 0
        if loadedTrigger(record) then loaded += 1 end
        local state = chestStatus(record, now)
        if state == "ready" then ready += 1
        elseif state == "remembered" then expected += 1
        else waiting += 1 end
    end
    availabilityLabel.Text = string.format("%d live · %d expected · %d cooling/empty", ready, expected, waiting)
    if discovery.active then
        nextPassLabel.Text = string.format("Discovery %d/%d areas · %d known chests",
            discovery.completed + discovery.skipped, #discovery.regions, #records)
        plannerLabel.Text = string.format("%d loaded · %d confirmed · learning locations", loaded, confirmed)
        return
    end
    local plan = liveNavigation.plan
    if plan then
        local _, root = parts()
        local seconds = root and Planner.travelSeconds(root.Position,
            Planner.sweepPath(plan.first, root.Position, C.SweepMargin), liveNavigation.speed or C.BoatSpeed) or 0
        nextPassLabel.Text = string.format("Next pass ~%s · ETA %.1fs", formatMoney(plan.first.value), seconds)
    else
        nextPassLabel.Text = "Watching known chest respawns…"
    end
    plannerLabel.Text = string.format("%d confirmed · %d candidate stops · %d ahead",
        confirmed, liveNavigation.nodeCount, plan and #plan.route or 0)
end

local function installChestMap()
    if not LOAD_UI then return end
    local inputService = game:GetService("UserInputService")
    local guiService = game:GetService("GuiService")
    local colors = {
        ready = Color3.fromRGB(99, 225, 160), predicted = Color3.fromRGB(255, 204, 111),
        remembered = Color3.fromRGB(125, 190, 255),
        unknown = Color3.fromRGB(126, 166, 231), empty = Color3.fromRGB(113, 125, 143),
        target = Color3.fromRGB(255, 113, 132), dealer = Color3.fromRGB(186, 147, 247),
    }
    local function rounded(object, radius)
        Instance.new("UICorner", object).CornerRadius = UDim.new(0, radius)
    end
    local function text(parent, value, size, color)
        local item = Instance.new("TextLabel")
        item.BackgroundTransparency = 1
        item.Font = Enum.Font.Gotham
        item.TextSize = size
        item.TextColor3 = color or light
        item.TextXAlignment = Enum.TextXAlignment.Left
        item.TextTruncate = Enum.TextTruncate.AtEnd
        item.Text = value
        item.Parent = parent
        return item
    end
    local frame = Instance.new("Frame")
    frame.Name = "ChestMap"
    frame.Visible = false
    frame.AnchorPoint = Vector2.new(1, 0)
    frame.BackgroundColor3 = Color3.fromRGB(19, 24, 33)
    frame.BorderSizePixel = 0
    frame.Parent = gui
    rounded(frame, 12)
    local border = Instance.new("UIStroke", frame)
    border.Color = Color3.fromRGB(57, 69, 87)
    local heading = text(frame, "CHEST MAP · LIVE ROUTE", 16)
    heading.Font = Enum.Font.GothamBold
    heading.Position, heading.Size = UDim2.fromOffset(14, 10), UDim2.new(1, -64, 0, 24)
    local subtitle = text(frame, "Finding nearby spawns…", 11, muted)
    subtitle.Position, subtitle.Size = UDim2.fromOffset(14, 36), UDim2.new(1, -28, 0, 18)
    local function button(value, x, width, y)
        local item = Instance.new("TextButton")
        item.Text, item.Font, item.TextSize = value, Enum.Font.GothamMedium, 12
        item.TextColor3 = light
        item.BackgroundColor3 = Color3.fromRGB(38, 47, 62)
        item.BorderSizePixel = 0
        item.Position, item.Size = UDim2.fromOffset(x, y or 62), UDim2.fromOffset(width, 32)
        item.Parent = frame
        rounded(item, 6)
        return item
    end
    local fitButton = button("Fit", 12, 42)
    local followButton = button("Follow", 60, 62)
    local timersButton = button("Timers Off", 128, 88)
    local minusButton = button("−", 222, 32)
    local plusButton = button("+", 260, 32)
    local closeButton = button("×", 0, 30, 8)
    closeButton.Position = UDim2.new(1, -42, 0, 8)
    local canvas = Instance.new("Frame")
    canvas.Position, canvas.Size = UDim2.fromOffset(12, 104), UDim2.new(1, -24, 1, -234)
    canvas.BackgroundColor3 = Color3.fromRGB(25, 33, 45)
    canvas.BorderSizePixel = 0
    canvas.ClipsDescendants = true
    canvas.Parent = frame
    local function footer(value, y, height, size)
        local item = text(frame, value, size or 11, muted)
        item.Position, item.Size = UDim2.new(0, 14, 1, y), UDim2.new(1, -28, 0, height)
        return item
    end
    local legend = footer('', -120, 18, 11)
    legend.RichText = true
    legend.Text = '<font color="#63E1A0">●</font> Live   <font color="#7DBEFF">●</font> Expected   <font color="#FFCC6F">●</font> Cooling'
    local legend2 = footer('', -102, 18, 11)
    legend2.RichText = true
    legend2.Text = '<font color="#717D8F">●</font> Empty   <font color="#FF7184">●</font> Target   <font color="#BA93F7">◆</font> Dealer   <font color="#FFFFFF">▲</font> You'
    local help = footer("Drag to pan · Tap a group to zoom · S / G / D = grade", -81, 18, 10)
    local details = footer("Tap a chest for its value, distance and respawn time.", -60, 50, 12)
    details.TextWrapped, details.TextTruncate = true, Enum.TextTruncate.None
    details.TextYAlignment = Enum.TextYAlignment.Top
    details.TextColor3 = light

    local center, pixelsPerStud = Vector2.zero, 0.1
    local initialized, follow, timers = false, false, false
    local autoFitUntil, fittedRecordCount = os.clock() + 2, 0
    local selected, selectedGroup = nil, nil
    local dirty, lastRefresh, suppressClickUntil = true, 0, 0
    local markerPool, dealerPool, gridPool = {}, {}, {}
    local lastViewport = Vector2.zero
    local function flat(position) return Vector2.new(position.X, position.Z) end
    local function project(position)
        local delta = flat(position) - center
        return canvas.AbsoluteSize * 0.5 + Vector2.new(delta.X, -delta.Y) * pixelsPerStud
    end
    local function unproject(point)
        local delta = (point - canvas.AbsoluteSize * 0.5) / pixelsPerStud
        return center + Vector2.new(delta.X, -delta.Y)
    end
    local function isInRange(position, root)
        return not root or (flat(position) - flat(root.Position)).Magnitude <= MAP_OUTLIER_DISTANCE
    end
    local function countdown(seconds)
        local value = math.max(0, math.ceil(seconds))
        return string.format("%d:%02d", math.floor(value / 60), value % 60)
    end
    local function status(record, now)
        return chestStatus(record, now)
    end
    local function setFollow(value)
        follow = value
        followButton.Text = value and "Following" or "Follow"
        followButton.BackgroundColor3 = value and Color3.fromRGB(39, 87, 100) or Color3.fromRGB(38, 47, 62)
    end
    local function fit()
        local _, root = parts()
        if not root then return end
        local low, high = Vector2.new(math.huge, math.huge), Vector2.new(-math.huge, -math.huge)
        local count = 0
        local function include(position)
            local p = flat(position)
            low = Vector2.new(math.min(low.X, p.X), math.min(low.Y, p.Y))
            high = Vector2.new(math.max(high.X, p.X), math.max(high.Y, p.Y))
        end
        for _, record in ipairs(records) do
            if isInRange(record.position, root) then include(record.position); count += 1 end
        end
        if root then include(root.Position) end
        if low.X == math.huge then return end
        center = (low + high) * 0.5
        local size, span = canvas.AbsoluteSize, high - low
        pixelsPerStud = math.clamp(math.min(math.max(40, size.X - 64) / math.max(200, span.X),
            math.max(40, size.Y - 64) / math.max(200, span.Y)), 0.002, 4)
        initialized = count > 0
        setFollow(false)
        dirty = true
    end
    local function zoom(factor, point)
        autoFitUntil = 0
        point = point or canvas.AbsoluteSize * 0.5
        local before = unproject(point)
        pixelsPerStud = math.clamp(pixelsPerStud * factor, 0.002, 4)
        center += before - unproject(point)
        setFollow(false)
        dirty = true
    end
    local function line(color, thickness)
        local item = Instance.new("Frame")
        item.BorderSizePixel, item.BackgroundColor3 = 0, color
        item.AnchorPoint = Vector2.new(0.5, 0.5)
        item.ZIndex = 2
        item.Size = UDim2.fromOffset(0, thickness or 2)
        item.Parent = canvas
        return item
    end
    local routeLine, nextLine = line(Color3.fromRGB(88, 205, 237)), line(colors.predicted)
    routeLine.ZIndex, nextLine.ZIndex = 3, 3
    local lookaheadLines = {}
    local function drawLine(item, a, b)
        item.Visible = a ~= nil and b ~= nil
        if not item.Visible then return end
        -- Clip before rotating so a long route cannot spill across the surrounding UI.
        local travel, enter, leave = b - a, 0, 1
        local function clip(p, q)
            if math.abs(p) < 1e-9 then return q >= 0 end
            local ratio = q / p
            if p < 0 then enter = math.max(enter, ratio) else leave = math.min(leave, ratio) end
            return enter <= leave
        end
        local bounds = canvas.AbsoluteSize
        if not (clip(-travel.X, a.X) and clip(travel.X, bounds.X - a.X)
            and clip(-travel.Y, a.Y) and clip(travel.Y, bounds.Y - a.Y)) then
            item.Visible = false
            return
        end
        b, a = a + travel * leave, a + travel * enter
        local delta, midpoint = b - a, (a + b) * 0.5
        item.Position, item.Size = UDim2.fromOffset(midpoint.X, midpoint.Y),
            UDim2.fromOffset(delta.Magnitude, item.Size.Y.Offset)
        item.Rotation = math.deg(math.atan2(delta.Y, delta.X))
    end
    local playerDot = text(canvas, "▲", 23, Color3.new(1, 1, 1))
    playerDot.AnchorPoint, playerDot.Size = Vector2.new(0.5, 0.5), UDim2.fromOffset(26, 26)
    playerDot.TextXAlignment, playerDot.ZIndex = Enum.TextXAlignment.Center, 7
    playerDot.TextStrokeTransparency = 0.3
    local scaleLabel = text(canvas, "", 10, muted)
    scaleLabel.Position, scaleLabel.Size, scaleLabel.ZIndex = UDim2.fromOffset(8, 6), UDim2.new(1, -16, 0, 16), 8

    local function newMarker()
        local item = {}
        item.button = Instance.new("TextButton")
        local b = item.button
        b.AnchorPoint, b.Size = Vector2.new(0.5, 0.5), UDim2.fromOffset(inputService.TouchEnabled and 34 or 24, inputService.TouchEnabled and 34 or 24)
        b.Font, b.TextSize, b.TextColor3 = Enum.Font.GothamBold, 11, Color3.fromRGB(17, 24, 33)
        b.BorderSizePixel, b.ZIndex, b.Parent = 0, 4, canvas
        rounded(b, 12)
        item.stroke = Instance.new("UIStroke", b)
        item.stroke.ApplyStrokeMode, item.stroke.Thickness = Enum.ApplyStrokeMode.Border, 2
        item.timer = text(canvas, "", 10, colors.predicted)
        item.timer.BackgroundColor3, item.timer.BackgroundTransparency = Color3.fromRGB(15, 21, 30), 0.05
        item.timer.TextXAlignment, item.timer.Size, item.timer.ZIndex = Enum.TextXAlignment.Center, UDim2.fromOffset(52, 18), 6
        rounded(item.timer, 4)
        table.insert(connections, b.Activated:Connect(function()
            if os.clock() < suppressClickUntil or not item.group then return end
            local group = item.group
            selectedGroup, selected = group.records, group.records[1]
            if #group.records > 1 then
                if pixelsPerStud >= 4 then
                    item.inspectIndex = (item.inspectIndex or 0) % #group.records + 1
                    selected, selectedGroup = group.records[item.inspectIndex], nil
                else
                    center = group.world / #group.records
                    pixelsPerStud = math.min(4, pixelsPerStud * 2.2)
                end
                autoFitUntil = 0
                setFollow(false)
            end
            dirty = true
        end))
        return item
    end
    local function layout()
        local viewport = workspace.CurrentCamera and workspace.CurrentCamera.ViewportSize or Vector2.new(1000, 700)
        if not gui.IgnoreGuiInset then
            local topLeft, bottomRight = guiService:GetGuiInset()
            viewport -= topLeft + bottomRight
        end
        if viewport == lastViewport then return end
        lastViewport = viewport
        local touch = inputService.TouchEnabled
        local width = math.max(260, math.min(520, viewport.X - 16))
        local height = math.max(240, math.min(600, viewport.Y - 16))
        frame.Size = UDim2.fromOffset(width, height)
        frame.Position = UDim2.new(1, -8, 0, touch and 8 or math.min(76, math.max(8, (viewport.Y - height) * 0.5)))
        local controls = {fitButton, followButton, timersButton, minusButton, plusButton}
        local controlWidth = (width - 40) / #controls
        for index, control in ipairs(controls) do
            control.Size = UDim2.fromOffset(controlWidth, touch and 42 or 32)
            control.Position = UDim2.fromOffset(12 + (index - 1) * (controlWidth + 4), 60)
            control.TextSize = controlWidth < 65 and 10 or 12
        end
        closeButton.Size = UDim2.fromOffset(touch and 42 or 30, touch and 42 or 32)
        closeButton.Position = UDim2.new(1, touch and -50 or -42, 0, 6)
        local compact = height < 430
        local canvasTop = touch and 110 or 104
        local footerHeight = compact and 108 or 130
        canvas.Position = UDim2.fromOffset(12, canvasTop)
        canvas.Size = UDim2.new(1, -24, 1, -canvasTop - footerHeight)
        legend.Visible = not compact
        legend2.Position = UDim2.new(0, 14, 1, compact and -98 or -102)
        help.Position = UDim2.new(0, 14, 1, compact and -78 or -81)
        details.Position = UDim2.new(0, 14, 1, -58)
        details.TextSize = compact and 11 or 12
        panel.Visible = not (touch and frame.Visible)
        dirty = true
    end
    local function render()
        local _, root = parts()
        local now, size = workspace:GetServerTimeNow(), canvas.AbsoluteSize
        if size.X <= 0 or size.Y <= 0 then return end
        if not initialized or (#records ~= fittedRecordCount and os.clock() < autoFitUntil) then
            fit()
            fittedRecordCount = #records
        end
        if follow and root then center = flat(root.Position) end
        local targets = {}
        for _, record in ipairs(currentTargetRecords or {}) do targets[record] = true end
        local groups, bins = {}, {}
        local ready, expected, respawning, omitted, visible = 0, 0, 0, 0, 0
        for _, record in ipairs(records) do
            if not isInRange(record.position, root) then omitted += 1; continue end
            local state, p = status(record, now), project(record.position)
            if state == "ready" then ready += 1 elseif state == "remembered" then expected += 1
            elseif state == "predicted" then respawning += 1 end
            local markerInset = inputService.TouchEnabled and 19 or 14
            if p.X < markerInset or p.Y < 26 or p.X > size.X - markerInset or p.Y > size.Y - markerInset then continue end
            visible += 1
            local spacing = inputService.TouchEnabled and 42 or 30
            local bx, by = math.floor(p.X / spacing), math.floor(p.Y / spacing)
            local group
            for x = bx - 1, bx + 1 do
                for y = by - 1, by + 1 do
                    for _, candidate in ipairs(bins[x .. ":" .. y] or {}) do
                        if (candidate.point - p).Magnitude < spacing then group = candidate; break end
                    end
                    if group then break end
                end
                if group then break end
            end
            if not group then
                group = {point = p, world = Vector2.zero, records = {}, ready = 0, target = false, state = state}
                table.insert(groups, group)
                local key = bx .. ":" .. by
                bins[key] = bins[key] or {}
                table.insert(bins[key], group)
            end
            table.insert(group.records, record)
            group.world += flat(record.position)
            group.target = group.target or targets[record] == true
            if state == "ready" then group.ready += 1; group.state = "ready" end
            if state == "remembered" and group.ready == 0 then group.state = "remembered" end
            if state == "predicted" then
                group.remaining = math.min(group.remaining or math.huge, record.readyAt - now)
                if group.ready == 0 and group.state ~= "remembered" then group.state = "predicted" end
            end
        end
        -- A world-aligned grid and distance key give scale without inventing terrain.
        local rawStep = 70 / pixelsPerStud
        local power = 10 ^ math.floor(math.log10(rawStep))
        local step = math.ceil(rawStep / power) * power
        local topLeft = unproject(Vector2.zero)
        local gridCount = 0
        local function gridLine(position, dimensions)
            gridCount += 1
            local item = gridPool[gridCount]
            if not item then
                item = Instance.new("Frame")
                item.BackgroundColor3, item.BorderSizePixel, item.ZIndex, item.Parent = Color3.fromRGB(36, 46, 61), 0, 1, canvas
                gridPool[gridCount] = item
            end
            item.Position, item.Size, item.Visible = position, dimensions, true
        end
        local firstX = (math.ceil(topLeft.X / step) * step - topLeft.X) * pixelsPerStud
        local firstY = (topLeft.Y - math.floor(topLeft.Y / step) * step) * pixelsPerStud
        for x = firstX, size.X, step * pixelsPerStud do gridLine(UDim2.fromOffset(x, 0), UDim2.fromOffset(1, size.Y)) end
        for y = firstY, size.Y, step * pixelsPerStud do gridLine(UDim2.fromOffset(0, y), UDim2.fromOffset(size.X, 1)) end
        for i = gridCount + 1, #gridPool do gridPool[i].Visible = false end
        local route = liveNavigation.plan and liveNavigation.plan.route or {}
        scaleLabel.Text = string.format("+Z ↑ · grid %s · purple: lookahead · cyan: current · gold: next", tostring(step))
        local segmentCount = math.max(0, #route - 1)
        local createdSegments = 0
        for index = 1, segmentCount do
            local segment = lookaheadLines[index]
            if not segment then
                if createdSegments >= 24 then dirty = true; break end
                segment = line(Color3.fromRGB(180, 115, 255), 2)
                segment.BackgroundTransparency = 0.25
                local direction = text(segment, "▶", 12, Color3.fromRGB(216, 174, 255))
                direction.Name = "Direction"
                direction.AnchorPoint = Vector2.new(0.5, 0.5)
                direction.Position = UDim2.new(0.62, 0, 0.5, 0)
                direction.Size = UDim2.fromOffset(14, 14)
                direction.TextXAlignment = Enum.TextXAlignment.Center
                direction.TextStrokeTransparency = 0.35
                direction.ZIndex = 2
                lookaheadLines[index] = segment
                createdSegments += 1
            end
            local fromStop, toStop = route[index], route[index + 1]
            if isInRange(fromStop.position, root) and isInRange(toStop.position, root) then
                drawLine(segment, project(fromStop.position), project(toStop.position))
            else
                segment.Visible = false
            end
            local direction = segment:FindFirstChild("Direction")
            if direction then direction.Visible = segment.Visible and segment.Size.X.Offset >= 26 end
        end
        for index = segmentCount + 1, #lookaheadLines do lookaheadLines[index].Visible = false end
        -- Place timer labels only in free space; the selected chest always has a detailed countdown below.
        local occupied = {}
        for _, group in ipairs(groups) do
            table.insert(occupied, {group.point.X - 14, group.point.Y - 14, group.point.X + 14, group.point.Y + 14})
        end
        for i, group in ipairs(groups) do
            local item = markerPool[i]
            if not item then item = newMarker(); markerPool[i] = item end
            item.group = group
            local b, p = item.button, group.point
            b.Visible, b.Position = true, UDim2.fromOffset(p.X, p.Y)
            b.BackgroundColor3 = colors[group.state]
            b.Text = #group.records > 1 and tostring(#group.records) or gradeOf(group.records[1]):sub(1, 1)
            item.stroke.Color = group.target and colors.target or (selected and table.find(group.records, selected) and light or Color3.fromRGB(15, 22, 32))
            item.stroke.Thickness = group.target and 3 or 2
            item.timer.Visible = false
            if timers and group.remaining then
                for _, offset in ipairs({Vector2.new(-26, -34), Vector2.new(-26, 16), Vector2.new(16, -9), Vector2.new(-68, -9)}) do
                    local origin = p + offset
                    local rect = {origin.X, origin.Y, origin.X + 52, origin.Y + 18}
                    local free = rect[1] >= 0 and rect[2] >= 24 and rect[3] <= size.X and rect[4] <= size.Y
                    if free then
                        for _, other in ipairs(occupied) do
                            if rect[1] < other[3] and rect[3] > other[1] and rect[2] < other[4] and rect[4] > other[2] then free = false; break end
                        end
                    end
                    if free then
                        item.timer.Position = UDim2.fromOffset(origin.X, origin.Y)
                        item.timer.Text = "~" .. countdown(group.remaining)
                        item.timer.Visible = true
                        table.insert(occupied, rect)
                        break
                    end
                end
            end
        end
        for i = #groups + 1, #markerPool do markerPool[i].button.Visible = false; markerPool[i].timer.Visible = false end
        local dealerCount = 0
        for _, dealer in ipairs(dealerList) do
            if dealer.instance and isInRange(dealer.position, root) then
                local p = project(dealer.position)
                if p.X >= 12 and p.Y >= 24 and p.X <= size.X - 12 and p.Y <= size.Y - 12 then
                    dealerCount += 1
                    local item = dealerPool[dealerCount]
                    if not item then
                        item = text(canvas, "◆", 19, colors.dealer)
                        item.AnchorPoint, item.Size, item.ZIndex = Vector2.new(0.5, 0.5), UDim2.fromOffset(20, 20), 3
                        item.TextXAlignment = Enum.TextXAlignment.Center
                        dealerPool[dealerCount] = item
                    end
                    item.Position, item.Visible = UDim2.fromOffset(p.X, p.Y), true
                end
            end
        end
        for i = dealerCount + 1, #dealerPool do dealerPool[i].Visible = false end
        playerDot.Visible = root ~= nil
        if root then
            local p, look = project(root.Position), root.CFrame.LookVector
            playerDot.Position = UDim2.fromOffset(p.X, p.Y)
            playerDot.Rotation = math.deg(math.atan2(look.X, look.Z))
        end
        drawLine(routeLine, root and project(root.Position), currentRouteDestination and project(currentRouteDestination))
        drawLine(nextLine, currentTargetBoxCenter and project(currentTargetBoxCenter), currentNextRoutePosition and project(currentNextRoutePosition))
        subtitle.Text = string.format("%d live · %d expected · %d cooling · %d far hidden", ready, expected, respawning, omitted)
        help.Text = string.format("%d in view · Drag to pan · Tap groups to zoom · S/G/D = grade", visible)
        if selected then
            local state = status(selected, now)
            local reward = economyOf(selected).rewards[REWARD_SEA]
            local distance = root and (selected.position - root.Position).Magnitude or 0
            if selectedGroup and #selectedGroup > 1 then
                local groupReady, value = 0, 0
                for _, record in ipairs(selectedGroup) do
                    if status(record, now) == "ready" then groupReady += 1 end
                    local range = economyOf(record).rewards[REWARD_SEA]
                    value += (range[1] + range[2]) * 0.5
                end
                local hint = pixelsPerStud >= 4 and "Tap the group again to inspect each overlapping chest."
                    or "Zoom in, then tap a letter to inspect a single chest."
                details.Text = string.format("%d chests · %d ready · ~%s total value\n%s", #selectedGroup, groupReady, formatMoney(value), hint)
            else
                local stateText = state == "predicted" and ((selected.initiallyEmptyAt and "Ready by ~" or "Respawn ~") .. countdown(selected.readyAt - now))
                    or state == "remembered" and "Expected ready · unloaded"
                    or state == "unknown" and "Unverified / streamed out"
                    or state == "ready" and "Ready to collect" or "Empty"
                details.Text = string.format("%s · %s · %.0f studs\n%s · %s–%s · %d confirmed pickups",
                    gradeOf(selected), selected.name, distance,
                    stateText, formatMoney(reward[1]), formatMoney(reward[2]), selected.successes or 0)
            end
        else
            local plan = liveNavigation.plan
            if plan then
                details.Text = string.format("Next pass: %d chests · ~%s value\n%d stops ahead · blue = expected while unloaded",
                    #plan.first.records, formatMoney(plan.first.value), #plan.route)
            else
                details.Text = "Locations and states stay in memory when unloaded.\nTap a chest for value, distance and respawn time."
            end
        end
    end

    table.insert(connections, fitButton.Activated:Connect(fit))
    table.insert(connections, followButton.Activated:Connect(function() setFollow(not follow); dirty = true end))
    table.insert(connections, plusButton.Activated:Connect(function() zoom(1.6) end))
    table.insert(connections, minusButton.Activated:Connect(function() zoom(1 / 1.6) end))
    table.insert(connections, timersButton.Activated:Connect(function()
        timers = not timers
        timersButton.Text = timers and "Timers On" or "Timers Off"
        timersButton.BackgroundColor3 = timers and Color3.fromRGB(87, 71, 38) or Color3.fromRGB(38, 47, 62)
        dirty = true
    end))
    local function toggle()
        frame.Visible = not frame.Visible
        panel.Visible = not (inputService.TouchEnabled and frame.Visible)
        mapButton.BackgroundColor3 = frame.Visible and Color3.fromRGB(45, 75, 95) or Color3.fromRGB(55, 60, 70)
        dirty = true
    end
    table.insert(connections, closeButton.Activated:Connect(toggle))
    table.insert(connections, mapButton.Activated:Connect(toggle))
    local dragInput, dragStart, dragCenter, dragged
    local function inside(position)
        local inset = not gui.IgnoreGuiInset and guiService:GetGuiInset() or Vector2.zero
        local p = Vector2.new(position.X, position.Y) - inset - canvas.AbsolutePosition
        return p.X >= 0 and p.Y >= 0 and p.X <= canvas.AbsoluteSize.X and p.Y <= canvas.AbsoluteSize.Y, p
    end
    table.insert(connections, inputService.InputBegan:Connect(function(input)
        if not frame.Visible or not gui.Parent or dragInput then return end
        if input.UserInputType ~= Enum.UserInputType.MouseButton1 and input.UserInputType ~= Enum.UserInputType.Touch then return end
        if not inside(input.Position) then return end
        dragInput, dragStart, dragCenter, dragged = input, Vector2.new(input.Position.X, input.Position.Y), center, false
    end))
    table.insert(connections, inputService.InputChanged:Connect(function(input)
        if not frame.Visible then return end
        if input.UserInputType == Enum.UserInputType.MouseWheel then
            local hit, p = inside(inputService:GetMouseLocation())
            if hit then zoom(input.Position.Z > 0 and 1.3 or 1 / 1.3, p) end
        elseif dragInput and (input == dragInput or (dragInput.UserInputType == Enum.UserInputType.MouseButton1 and input.UserInputType == Enum.UserInputType.MouseMovement)) then
            local delta = Vector2.new(input.Position.X, input.Position.Y) - dragStart
            if delta.Magnitude > 4 then dragged = true end
            if dragged then
                autoFitUntil = 0
                center = dragCenter + Vector2.new(-delta.X, delta.Y) / pixelsPerStud
                suppressClickUntil = os.clock() + 0.25
                setFollow(false)
                dirty = true
            end
        end
    end))
    table.insert(connections, inputService.InputEnded:Connect(function(input)
        if input == dragInput then
            if dragged then suppressClickUntil = os.clock() + 0.25 end
            dragInput = nil
        end
    end))
    layout()
    table.insert(connections, RunService.Heartbeat:Connect(function()
        if stopped or not gui.Parent or not frame.Visible then return end
        layout()
        local now = os.clock()
        if now - lastRefresh < (dirty and 1 / 30 or C.MapRefreshSeconds) then return end
        lastRefresh, dirty = now, false
        render()
    end))
end
installChestMap()


-- TRACERS & GOLD 100x100x100 HITBOX VISUALIZER
local traceParts = {}
if SHOW_ROUTE_TRACERS or SHOW_HITBOX_VISUALIZER then
    local hitboxes = {}
    local function getHitbox(index)
        if hitboxes[index] then return hitboxes[index] end
        local part = Instance.new("Part")
        part.Name = "Hitbox100VisualizerPart"
        part.Size, part.Transparency = HITBOX_SIZE, 1
        part.Anchored = true -- Visual marker only; never anchors the character or boat.
        part.CanCollide, part.CanTouch, part.CanQuery = false, false, false
        part.Parent = workspace.Terrain
        local outline = Instance.new("SelectionBox")
        outline.Name = "Hitbox100GoldOutline"
        outline.Color3 = Color3.fromRGB(255, 215, 0)
        outline.LineThickness, outline.SurfaceTransparency = 0.25, 1
        outline.Adornee, outline.Visible = part, false
        outline.Parent = workspace.Terrain
        local item = {part = part, outline = outline}
        hitboxes[index] = item
        table.insert(traceParts, outline)
        table.insert(traceParts, part)
        return item
    end
    local function makeTracer(name, color)
        local from = Instance.new("Attachment", workspace.Terrain)
        local dest = Instance.new("Attachment", workspace.Terrain)
        local beam = Instance.new("Beam", workspace.Terrain)
        beam.Name = name
        beam.Attachment0, beam.Attachment1 = from, dest
        beam.FaceCamera, beam.Segments = true, 1
        beam.Width0, beam.Width1 = 0.35, 0.35
        beam.Color, beam.Transparency = ColorSequence.new(color), NumberSequence.new(0.2)
        beam.Enabled = false
        table.insert(traceParts, beam)
        table.insert(traceParts, from)
        table.insert(traceParts, dest)
        return {from = from, dest = dest, beam = beam}
    end
    local currentTrace = SHOW_ROUTE_TRACERS and makeTracer("FarmRouteCurrent", Color3.fromRGB(75, 215, 255))
    local nextTrace = SHOW_ROUTE_TRACERS and makeTracer("FarmRouteNext", Color3.fromRGB(255, 210, 95))
    local function updateTracer(tracer, from, dest)
        if not tracer then return end
        tracer.beam.Enabled = from ~= nil and dest ~= nil
        if tracer.beam.Enabled then
            tracer.from.WorldPosition, tracer.dest.WorldPosition = from, dest
        end
    end
    table.insert(connections, RunService.RenderStepped:Connect(function()
        local _, root = parts()
        local count = 0
        local function showHitbox(frame)
            count += 1
            local item = getHitbox(count)
            if item.part.CFrame ~= frame then item.part.CFrame = frame end
            item.outline.Visible = true
        end
        if not stopped and root and SHOW_HITBOX_VISUALIZER then
            -- Draw each real chest in the pass, including cached geometry when unloaded.
            for _, record in ipairs(currentTargetRecords or {}) do
                local trigger = loadedTrigger(record)
                local frame = trigger and trigger.CFrame or (record.geometry and record.geometry.cframe)
                if frame then showHitbox(frame) end
            end
            if count == 0 and currentTargetBoxCenter then showHitbox(CFrame.new(currentTargetBoxCenter)) end
        end
        for index = count + 1, #hitboxes do hitboxes[index].outline.Visible = false end
        local enabled = not stopped and root ~= nil
        updateTracer(currentTrace, enabled and root.Position or nil, enabled and currentRouteDestination or nil)
        updateTracer(nextTrace, enabled and currentTargetBoxCenter or nil, enabled and currentNextRoutePosition or nil)
    end))
end

-- MAIN FARM LOOP
-- An immediately completed/rejected leg must not restart planning indefinitely in one frame.
local farmFrame = 0
table.insert(connections, RunService.Heartbeat:Connect(function() farmFrame += 1 end))
task.spawn(function()
    local ok, err = xpcall(function()
        display("Preparing chest locations…")
        local startupDeadline = os.clock() + C.CharacterTimeout
        while not stopped and (not initialScanComplete or not game:IsLoaded()) and os.clock() < startupDeadline do
            RunService.Heartbeat:Wait()
        end
        if stopped then return end
        refreshChests(true)
        local startupBalance = readBeli()
        if startupBalance then
            lastPickupBalance = startupBalance
            if startingBeli == nil then
                startingBeli, moneyStarted = startupBalance, workspace:GetServerTimeNow()
            end
        end
        if not initialScanComplete then log("Initial discovery still running; continuing with known spawns") end
        local lastPlanningFrame = -1
        while not stopped do
            if farmFrame == lastPlanningFrame then RunService.Heartbeat:Wait() end
            if stopped then break end
            lastPlanningFrame = farmFrame
            scanDealers()
            refreshChests()
            local _, root = waitCharacter(C.CharacterTimeout)
            if stopped then break end
            if not root then task.wait(0.5); continue end

            -- Resolve Boat or seamless Foot mode
            local boat, seat = getActiveVehicleOrCharacter()
            local currentSpeed = boat and C.BoatSpeed or C.Speed

            liveNavigation.speed = currentSpeed
            if discovery.active then
                local region = chooseDiscovery(root, currentSpeed)
                if region then
                    discoverArea(boat, seat, region)
                    continue
                end
            end
            local plan = choose(root, nil, currentSpeed)
            if not plan then
                liveNavigation.plan = nil
                currentRouteDestination, currentTargetBoxCenter = nil, nil
                currentTargetRecords, currentNextRoutePosition = nil, nil
                display("Watching known chest respawns…")
                pauseFlight()
                task.wait(C.PollSeconds)
                continue
            end
            local node = plan.node
            currentNextRoutePosition = plan.route and plan.route[2] and plan.route[2].position or nil
            display(string.format("%s · %s", boat and "Boat" or "Foot",
                node.isCluster and ("Cluster (" .. node.clusterCount .. " chests)") or node.record.name))

            -- Execute direct 3D flight (auto-adjusts speed and physics based on boat or foot)
            local outcome = flyThroughTarget(boat, seat, node)

            if outcome ~= "arrived" and outcome ~= "unavailable" and outcome ~= "interrupted" and outcome ~= "reroute" then
                for _, record in ipairs(node.records or {node.record}) do
                    record.failures = (record.failures or 0) + 1
                    record.cooldown = workspace:GetServerTimeNow() + 10
                end
            end
        end
    end, debug.traceback)

    stopped = true
    pauseFlight()
    if boatSeatCleanupConnection then boatSeatCleanupConnection:Disconnect() end
    preparedBoat = nil
    if persistentNoclipConn then persistentNoclipConn:Disconnect() end
    restoreFlightCollision()
    if persistentLinearVelocity then persistentLinearVelocity:Destroy() end
    if persistentAttachment then persistentAttachment:Destroy() end
    for _, conn in ipairs(connections) do conn:Disconnect() end
    for _, part in ipairs(traceParts) do part:Destroy() end
    for part, original in pairs(chestOriginals) do
        if part.Parent then
            pcall(function()
                part.Size = original.size
                part.CanCollide = original.canCollide
            end)
        end
    end
    if ok then
        gui:Destroy()
    else
        titleLabel.Text = "Chest Farm · Error"
        statusLabel.Text = "Stopped: check developer console"
        statusLabel.TextColor3 = Color3.fromRGB(255, 130, 130)
    end
    if globalEnvironment.ChestFarmerBoatStop == stop then globalEnvironment.ChestFarmerBoatStop = nil end
    if not ok then warn("[Chest Farmer] " .. tostring(err)) end
end)

Features & Information

HOW TO USE: Just execute it inside of Blox Fruits and it will start. - Collects Chests at fastest rate possible - NO LAG - Variable Speed (Lower it if you experience rubber banding probably wont tho) - No overhead (Option to not load UI at all at top of script in variables. - ~5,000 $/min in FIRST sea. Second and Third should be significantly more. ANTI-AFK now included.
#auto#bloxfruits#beli farm#money farm#keyless

Community Comments (94)

kamalasukharia81kamalasukharia81Dec 30, 2024(edited)

Where should I put it?

ProBaconHubProBaconHubDec 30, 2024(edited)

@kamalasukharia81 in your executor duh

Replying to @ProBaconHub
thatoneboyddtthatoneboyddtOct 25, 2025

@ProBaconHub how do you get that

yvette9292yvette9292Dec 30, 2024(edited)

How do you execute im new

Replying to @yvette9292
Roman7771923Roman7771923Apr 2, 2026

@yvette9292 you install an executer like Wave (not free) Xeno (free)

Replying to @yvette9292
dagolo666dagolo666Oct 3, 2026

@yvette9292 avec xeno

SimplyxKenjiSimplyxKenjiDec 30, 2024(edited)

can i get this script but with serverhop included?

chipnotregxpcchipnotregxpcDec 30, 2024(edited)

AIzaSyDwk6pr6eY8iqkuS-Clv94qGvIaVXnqXbI

lolszalajlolszalajDec 30, 2024(edited)

rlly?

flamexzl777flamexzl777Dec 30, 2024(edited)

Como faço para executar ele?

amooraawad9amooraawad9Dec 30, 2024(edited)

how and where should i get the executioner

Replying to @amooraawad9
jneminj35jneminj35Aug 12, 2025

@amooraawad9 Official site. Sorry my * is 7 months late Imao

aserhesham78aserhesham78Dec 31, 2024(edited)

how to execute its my first time

aserhesham78aserhesham78Dec 31, 2024(edited)

how to make it

salmaanswat1salmaanswat1Jan 1, 2025(edited)

Wow nice

MenoobofallgamesMenoobofallgamesJan 1, 2025(edited)

Bro this helped so much to grind, Now i can easily earn money and enjoy my new buddha fruit. Thanks to this script

walkercarl14317walkercarl14317Jan 1, 2025(edited)

Guys were do I get an executer and we're do I put it

tyirWrighttyirWrightJan 2, 2025(edited)

How do I use the scrip

tyirWrighttyirWrightJan 2, 2025(edited)

Script

akandabu370akandabu370Jan 2, 2025(edited)

where is execution

tejanoreynald490tejanoreynald490Jan 2, 2025(edited)

+Vouch

Christoperpayne2011Christoperpayne2011Jan 2, 2025(edited)

whats a good executor

Addai2012Addai2012Jan 3, 2025(edited)

Where is the executor

onkardhillon6979onkardhillon6979Jan 3, 2025(edited)

how do i change the speed

jamalnettinghamjamalnettinghamJan 3, 2025(edited)

how do i turn it off

kfcchickenbuttkfcchickenbuttJan 3, 2025(edited)

What are executors and does this work on mobile

guydanielchoumessiguydanielchoumessiJan 3, 2025(edited)

comment sa marche

onkardhillon6979onkardhillon6979Jan 4, 2025(edited)

it gets stuck somtimes

yountt2017yountt2017Jan 4, 2025(edited)

How do I download

andresarenas1125andresarenas1125Jan 5, 2025(edited)

Nosé como descargarlo alguien me da una explicación?

herrerachristianzagherrerachristianzagJan 6, 2025(edited)

Cómo le hago para poner los scripst

gojo12345gojo12345Jan 6, 2025(edited)

ah

gojo12345gojo12345Jan 6, 2025(edited)

guys i know where to put it join sky wars and you will se a something put the link and done

isaacidelgado91isaacidelgado91Jan 6, 2025(edited)

How do I activate it?

armong4012armong4012Jan 7, 2025(edited)

how do i server hop

bautistajoseangel20bautistajoseangel20Jan 7, 2025(edited)

Como ejecutó el script

junaid123_2junaid123_2Jan 8, 2025(edited)

como faco para executar ele

junaid123_2junaid123_2Jan 8, 2025(edited)

como faco para executar ele

king_glenn1king_glenn1Jan 9, 2025(edited)

How do I do this

RastexzRastexzJan 10, 2025(edited)

W!

FREAKY_INDRAFREAKY_INDRAJan 11, 2025(edited)

how tf do I get an exacuter on a Acer lapttop

FREAKY_INDRAFREAKY_INDRAJan 11, 2025(edited)

@proBacenHub how do u get an executer on Acer laptop

zhuy8912zhuy8912Jan 19, 2025(edited)

Is there any script for automatic waiting without using a key?

maisonste4maisonste4Jan 19, 2025(edited)

fake not function

mthandwagass13mthandwagass13Jan 28, 2025(edited)

Something

nsnash28nsnash28Feb 11, 2025(edited)

How do I active I'm still confused on how to activeing in game pls help

charliethomas2010charliethomas2010Feb 11, 2025(edited)

how do i use this i never scripted before any help appreciated

helloQhelloQFeb 12, 2025(edited)

works, only chest grab. well made

helloQhelloQFeb 25, 2025(edited)

works, and is quite good way to get a bit of money for the random fruit dealer

modguy44modguy44Mar 3, 2025(edited)

Supports Solara?

criccoisprocriccoisproApr 17, 2025

It works, thanks!

abraoearnicabraoearnicMay 3, 2025

how to get the executer

benjaminfoltyn66benjaminfoltyn66May 18, 2025

How do i use it in roblox im new to using scripts

crustyr313crustyr313May 18, 2025

where is my exutor

jadeosaphirjadeosaphirMay 18, 2025

Can someone explain it to me step by step

jadeosaphirjadeosaphirMay 18, 2025

how tf do I get an exacuter on a iPad

Laynerayne0629Laynerayne0629May 19, 2025

How do i do this

darrenmembadarrenmembaMay 27, 2025

Hoy do you use it somebody explain plz

jaydesuyo9jaydesuyo9Jun 2, 2025

Tysm

The_BoiThe_BoiJun 4, 2025

Can it work on mobile

Harmonpeter75Harmonpeter75Jun 6, 2025

guys in on an Mac how to I get the executor

wadejonathan29wadejonathan29Jun 9, 2025

How do I use the script

lilyhypanlilyhypanJun 10, 2025

how do i get a executor???

azanahmad123485azanahmad123485Jun 15, 2025

How to execute in Roblox

IAmSteveIAmSteveJun 17, 2025

The amount of kids trying to get this is insane lol

mangalnikhielmangalnikhielJul 7, 2025

how too get it wat too doo

tehilachimobitehilachimobiJul 28, 2025

se feels good

tehilachimobitehilachimobiJul 28, 2025

* feels good

andrasicdaninandrasicdaninJul 30, 2025

send me the link

gichukiryan51gichukiryan51Aug 4, 2025

guys how so yall put the script button on pc

gichukiryan51gichukiryan51Aug 4, 2025

i cant find it

nandobomb*einandobomb*eiAug 6, 2025

mt bom

TaplightTaplightAug 13, 2025

How do I execute

NotMarKNotMarKAug 18, 2025

can you juust give me the key i almmost got a virus tryin to complete the get key luarmor

Replying to @NotMarK
TomatoTomatoAug 19, 2025

@NotMarK this script doesn't even use luarmor just join the discord server bro

kadeempalmerqgkadeempalmerqgAug 31, 2025

doesnt work

adrianotriniadrianotriniSep 16, 2025

can somoene help me i dont understand how this works

anvarov124anvarov124Oct 15, 2025

where i put it

AntoniyaAntoniyaOct 22, 2025

Isn't it too long

great! 7/10 (would be 9 if there was an ui with a switch for on/off)

Replying to @averagechromeuser
TomatoTomatoSep 10, 2026

@averagechromeuser added

Gugs_VLlGugs_VLlSep 8, 2026

Me ensina o basico só que eu preciso

ThunderModsThunderModsownerSep 10, 2026

Sick!

Replying to @ThunderMods
voltexvoltexSep 12, 2026(edited)

@ThunderMods what is the point of only loading like the first twenty lines, like bro

Replying to @voltex
TomatoTomatoSep 12, 2026

@voltex just press view raw to see whole script. but yeah weird it only loads 20 lines

Replying to @ThunderMods
DraigDraigadminOct 3, 2026

@ThunderMods super sick

MinecraftARGLuverMinecraftARGLuverSep 10, 2026

wow keyless too?

FoggyDevFoggyDevSep 10, 2026

cool

Rudi_JagerRudi_JagerSep 11, 2026

This is more like an auto farm chest not cash generator

Replying to @Rudi_Jager
TomatoTomatoSep 11, 2026

@Rudi_Jager yeah but i wanted a unique *le as to stand out

optionalbrooptionalbroSep 21, 2026

AİNT WORKS BRO

Replying to @optionalbro
TomatoTomatoOct 3, 2026

@optionalbro fixed

SK44PSK44POct 2, 2026

Does this teleport to chest

Replying to @SK44P
TomatoTomatoOct 3, 2026

@SK44P yes

More Scripts for Blox Fruits