Hide and Seek Extreme Script (October 2026)

PUBLISHEDUPDATED
1 Available ScriptKeyless

Grinding out credits in Hide and Seek Extreme script sessions can get super repetitive when you just want to grab cool characters from the shop. Running around maps trying to stay hidden or hunting down other players takes way too much active focus if you are busy multitasking.

The basic loop here puts one player in the role of It while everyone else scrambles to hide around the map. It characters come with special abilities you trigger by pressing 1, and you can even press T to taunt your friends. You dodge hazards, collect credits, and try to survive until the timer runs out.

Using the Luna.dev utility menu lets you automate a ton of the busywork so you can chill out. Here is the open-source code you need to get it running.

Hide and Seek Extreme Script – Luna.dev — Hide and Seek Extreme & More

Luna.dev — Hide and Seek Extreme & MoreFree
Luna.dev — Hide and Seek ExtremeSMART AUTO FARMLobby player farmingCaptures remaining hiders during the final 30 secondsAutomatic round and role detectionReplaces farming targets when players leave
if game.PlaceId ~= 205224386 then
    warn('[luna.dev] this script is for hide and seek extreme (205224386).')
    return
end

local players = game:GetService('Players')
local replicatedstorage = game:GetService('ReplicatedStorage')
local runservice = game:GetService('RunService')
local userinputservice = game:GetService('UserInputService')
local lighting = game:GetService('Lighting')
local collectionservice = game:GetService('CollectionService')
local httpservice = game:GetService('HttpService')
local tweenservice = game:GetService('TweenService')
local marketplace = game:GetService('MarketplaceService')
local statsservice = game:GetService('Stats')
local virtualuser = game:GetService('VirtualUser')
local teleportservice = game:GetService('TeleportService')
local localplayer = players.LocalPlayer
local playergui = localplayer:WaitForChild('PlayerGui')
local gamedata = replicatedstorage:WaitForChild('GameData', 12)

if not gamedata then
    warn('[luna.dev] game data is unavailable.')
    return
end

for _, name in ipairs({ 'luna_hide_and_seek_extreme', 'luna_early_it_display' }) do
    local old = playergui:FindFirstChild(name)
    if old then old:Destroy() end
end

local palette = {
    background = Color3.fromRGB(13, 11, 21),
    side = Color3.fromRGB(17, 14, 28),
    surface = Color3.fromRGB(20, 17, 32),
    card = Color3.fromRGB(26, 22, 41),
    raised = Color3.fromRGB(36, 29, 55),
    accent = Color3.fromRGB(180, 142, 255),
    accentmid = Color3.fromRGB(112, 82, 190),
    accentdark = Color3.fromRGB(62, 42, 100),
    border = Color3.fromRGB(50, 42, 70),
    text = Color3.fromRGB(238, 233, 248),
    muted = Color3.fromRGB(172, 162, 192),
    dim = Color3.fromRGB(125, 112, 150),
    lilac = Color3.fromRGB(214, 188, 255),
    success = Color3.fromRGB(130, 209, 177),
    danger = Color3.fromRGB(243, 159, 173),
    gold = Color3.fromRGB(255, 211, 102),
    portal = Color3.fromRGB(87, 218, 245),
    jump = Color3.fromRGB(107, 233, 169),
    glue = Color3.fromRGB(247, 163, 88),
    camera = Color3.fromRGB(239, 148, 237)
}

local alive = true
local connections, cleanups = {}, {}
local started_at = os.clock()
local S = {
    selected = nil, spectating = nil, return_cframe = nil,
    minimized = false, afk_pulses = 0, last_pulse = os.clock(), fling_active = false
}

local settings = {
    esp = true, labels = true, distance = true, credit_esp = true, teleporter_esp = true,
    fullbright = false, speed = true, jump = true, noclip = false, infinite_jump = false,
    auto_seek = false, auto_credits = false, auto_evade = false,
    pull_credits = false, pull_lobby = false, it_no_cooldown = false, it_keep_active = false,
    early_it = true, jump_pad_esp = true, seeker_radius_esp = true, closest_credit_esp = true,
    camera_esp = true, glue_esp = true, seeker_warning_distance = 45,
    auto_buy = false, auto_farm = false, anti_afk = true, auto_server_hop = true
}

local initial_humanoid = localplayer.Character and localplayer.Character:FindFirstChildOfClass('Humanoid')
local move = {
    speed = math.floor(((initial_humanoid and initial_humanoid.WalkSpeed or 16) * 1.06) * 10 + 0.5) / 10,
    jump = math.floor(((initial_humanoid and initial_humanoid.JumpPower or 50) * 1.06) * 10 + 0.5) / 10,
    subtle_speed = true,
    subtle_jump = true
}


local V = { overlays = {}, credits = {}, tele = {}, hz = { jump = {}, glue = {}, camera = {} } }
local M = { collisions = {}, baselines = setmetatable({}, { __mode = 'k' }) }
local F = {
    pulled_credits = {}, loop_origin = nil,
    credit_attempts = 0, pull_credit_attempts = 0, lobby_targets = 0, pull_ticks = 0,
    lobby_cursor = 0, lobby_target = nil, lobby_started = 0, last_touch = 0, credit_inflight = false,
    verified_gains = 0, credit_last = nil,
    safe_positions = {}, last_safe = 0, last_escape = 0, last_notice = 0
}
local A = {
    handler = nil, set_original = nil, mark_original = nil, set_hook = nil, mark_hook = nil,
    active = false, active_known = false, last_clear = 0, intercepts = 0, clears = 0, err = nil,
    attempts = 0, name = 'Unknown', last_result = 'Not tested', last_attempt = 0,
    sprint_first = nil, last_character = nil,
    practice_end = 0, practice_camera = nil, objects = {}
}
local recent_credits = setmetatable({}, { __mode = 'k' })

local storage = { path = 'luna.dev/scripts/hide_and_seek_extreme.json', revision = 0, loaded = false }
storage.state = 'Settings will be saved in ' .. storage.path
do
    local defaults = {}
    for key, value in pairs(settings) do defaults[key] = value end
    storage.defaults = defaults

    local function available()
        return type(readfile) == 'function' and type(writefile) == 'function' and type(makefolder) == 'function'
    end
    local function ensure_directories()
        if not available() then return false end
        for _, directory in ipairs({ 'luna.dev', 'luna.dev/scripts' }) do
            if type(isfolder) ~= 'function' or not isfolder(directory) then
                local ok = pcall(makefolder, directory)
                if not ok and type(isfolder) == 'function' and not isfolder(directory) then
                    return false
                end
            end
        end
        return true
    end
    local function apply(saved)
        if type(saved) ~= 'table' or saved.schema ~= 1 or type(saved.options) ~= 'table' then
            return false
        end
        for key, original in pairs(defaults) do
            local value = saved.options[key]
            if type(value) == type(original) then
                settings[key] = key == 'seeker_warning_distance' and math.clamp(value, 10, 150) or value
            end
        end
        if type(saved.speed_value) == 'number' then move.speed = math.clamp(saved.speed_value, 0, 150) end
        if type(saved.jump_value) == 'number' then move.jump = math.clamp(saved.jump_value, 0, 200) end
        if type(saved.subtle_speed) == 'boolean' then move.subtle_speed = saved.subtle_speed end
        if type(saved.subtle_jump) == 'boolean' then move.subtle_jump = saved.subtle_jump end
        return true
    end
    local function load()
        if not available() then
            storage.state = 'File APIs unavailable in this executor; settings only last for this session.'
            return false
        end
        local ok, content = pcall(readfile, storage.path)
        if not ok then
            storage.state = 'No saved settings found; preferences will be saved automatically.'
            return false
        end
        local decoded, data = pcall(httpservice.JSONDecode, httpservice, content)
        if decoded and apply(data) then
            storage.loaded = true
            storage.state = 'Restored saved preferences from ' .. storage.path
            return true
        end
        storage.state = 'Settings file is invalid; defaults were used.'
        return false
    end
    function storage.save()
        if not ensure_directories() then
            storage.state = 'Could not create settings folder; check executor file access.'
            if storage.label and storage.label.Parent then storage.label.Text = storage.state end
            return false
        end
        local options = {}
        for key in pairs(defaults) do options[key] = settings[key] end
        local encoded, result = pcall(httpservice.JSONEncode, httpservice, {
            schema = 1,
            options = options,
            speed_value = move.speed,
            jump_value = move.jump,
            subtle_speed = move.subtle_speed,
            subtle_jump = move.subtle_jump
        })
        if not encoded then
            storage.state = 'Unable to encode settings.'
            return false
        end
        local ok = pcall(writefile, storage.path, result)
        storage.state = ok and ('Saved preferences to ' .. storage.path) or 'Could not write settings file.'
        if storage.label and storage.label.Parent then storage.label.Text = storage.state end
        return ok
    end
    function storage.schedule()
        storage.revision += 1
        local revision = storage.revision
        task.delay(0.65, function()
            if alive and revision == storage.revision then storage.save() end
        end)
    end
    load()
    settings.auto_buy = false 
end

local original_lighting = {
    Brightness = lighting.Brightness,
    ClockTime = lighting.ClockTime,
    FogEnd = lighting.FogEnd,
    GlobalShadows = lighting.GlobalShadows,
    Ambient = lighting.Ambient,
    OutdoorAmbient = lighting.OutdoorAmbient
}

local function bind(signal, callback)
    local connection = signal:Connect(callback)
    table.insert(connections, connection)
    return connection
end

local function every(interval, callback)
    local acc = 0
    bind(runservice.Heartbeat, function(dt)
        if not alive then return end
        acc += dt
        if acc >= interval then
            local elapsed = acc
            acc = 0
            callback(elapsed)
        end
    end)
end

local function new(class, props, parent)
    local obj = Instance.new(class)
    for property, value in pairs(props or {}) do
        obj[property] = value
    end
    obj.Parent = parent
    return obj
end

local function round(parent, radius)
    return new('UICorner', { CornerRadius = UDim.new(0, radius or 6) }, parent)
end

local function stroke(parent, color, transparency)
    return new('UIStroke', {
        Color = color or palette.border,
        Thickness = 1,
        Transparency = transparency or 0.2,
        ApplyStrokeMode = Enum.ApplyStrokeMode.Border
    }, parent)
end

local function text(parent, content, size, color, props)
    local options = {
        BackgroundTransparency = 1,
        Text = content,
        Font = Enum.Font.Gotham,
        TextSize = size or 12,
        TextColor3 = color or palette.text,
        TextXAlignment = Enum.TextXAlignment.Left,
        TextYAlignment = Enum.TextYAlignment.Center
    }
    for key, value in pairs(props or {}) do
        options[key] = value
    end
    return new('TextLabel', options, parent)
end

local function button(parent, content, pos, size, callback, accent)
    local control = new('TextButton', {
        Position = pos,
        Size = size,
        BackgroundColor3 = accent and palette.accentdark or palette.raised,
        BorderSizePixel = 0,
        Text = content,
        TextSize = 11,
        Font = Enum.Font.GothamMedium,
        TextColor3 = palette.text,
        AutoButtonColor = true
    }, parent)
    round(control, 5)
    stroke(control, accent and palette.accent or palette.border)
    bind(control.MouseButton1Click, callback)
    return control
end

local function nextorder(parent)
    local n = (parent:GetAttribute('order') or 0) + 1
    parent:SetAttribute('order', n)
    return n
end

local renderers = {}
local function register_render(key, fn)
    renderers[key] = renderers[key] or {}
    table.insert(renderers[key], fn)
end
local function update_toggle(key)
    for _, fn in ipairs(renderers[key] or {}) do fn() end
end

local ui = {}
local logs = {}
local function notify(message)
    if not alive then return end
    table.insert(logs, os.date('%H:%M:%S') .. '  ' .. message)
    if #logs > 80 then table.remove(logs, 1) end
    if ui.message then ui.message.Text = message end
    if ui.refresh_log then ui.refresh_log() end
end

local function getdata(player, field)
    local data = player and player:FindFirstChild('PlayerData')
    local entry = data and data:FindFirstChild(field)
    return entry and entry.Value
end

local function ingame(player)
    return getdata(player, 'InGame') == true
end

local function isit(player)
    if getdata(player, 'It') == true then return true end
    local it = gamedata:FindFirstChild('It')
    local obj = it and it:FindFirstChild('PlayerObject')
    return obj and obj.Value == player or false
end

local function getit()
    local it = gamedata:FindFirstChild('It')
    local obj = it and it:FindFirstChild('PlayerObject')
    if obj and obj.Value and obj.Value:IsA('Player') then return obj.Value end
    for _, player in ipairs(players:GetPlayers()) do
        if getdata(player, 'It') == true then return player end
    end
end

local function getroot(player)
    local character = player and player.Character
    if not character then return nil end
    local root = character:FindFirstChild('HumanoidRootPart')
    local humanoid = character:FindFirstChildOfClass('Humanoid')
    if root and humanoid and humanoid.Health > 0 then
        return root, humanoid
    end
end

local function role_of(player)
    return isit(player) and 'IT' or (ingame(player) and 'HIDER' or 'LOBBY')
end

local function color_of(player)
    return isit(player) and palette.danger or (ingame(player) and palette.accent or palette.muted)
end

local function nearest_hider()
    local mine = getroot(localplayer)
    if not mine then return nil end
    local closest, distance
    for _, player in ipairs(players:GetPlayers()) do
        if player ~= localplayer and ingame(player) and not isit(player) then
            local root = getroot(player)
            if root then
                local d = (root.Position - mine.Position).Magnitude
                if not distance or d < distance then closest, distance = player, d end
            end
        end
    end
    return closest
end

local function find_nearest_credit(skip_recent)
    local container = workspace:FindFirstChild('GameObjects')
    local mine = getroot(localplayer)
    if not container or not mine then return nil end
    local chosen, best
    local now = os.clock()
    for _, part in ipairs(container:GetChildren()) do
        if part.Name == 'Credit' and part:IsA('BasePart') and part.Parent then
            local collected = part:FindFirstChild('Collected')
            if (not collected or not collected.Value) and (not skip_recent or now - (recent_credits[part] or -100) > 2.5) then
                local distance = (mine.Position - part.Position).Magnitude
                if not best or distance < best then chosen, best = part, distance end
            end
        end
    end
    return chosen
end

local function next_lobby_target()
    local options = {}
    for _, player in ipairs(players:GetPlayers()) do
        if player ~= localplayer and not isit(player) and not ingame(player) and getroot(player) then
            table.insert(options, player)
        end
    end
    if #options == 0 then return nil end
    table.sort(options, function(a, b) return a.UserId < b.UserId end)
    F.lobby_cursor = F.lobby_cursor % #options + 1
    return options[F.lobby_cursor]
end

local function part_of(object)
    if object:IsA('BasePart') then return object end
    if object:IsA('Model') then return object:FindFirstChildWhichIsA('BasePart', true) end
end

local function restore_camera()
    S.spectating = nil
    local _, humanoid = getroot(localplayer)
    if workspace.CurrentCamera and humanoid then
        workspace.CurrentCamera.CameraSubject = humanoid
    end
end

local function teleport(player)
    if not player then notify('No target selected.'); return false end
    local myroot = getroot(localplayer)
    local targetroot = getroot(player)
    if not myroot or not targetroot then notify('One of the characters is not available.'); return false end
    if isit(player) and not isit(localplayer) then notify('Teleport blocked: target is It.'); return false end
    S.return_cframe = myroot.CFrame
    myroot.AssemblyLinearVelocity = Vector3.zero
    myroot.CFrame = targetroot.CFrame * CFrame.new(0, 1.5, 3.5)
    notify('Teleported to ' .. player.Name .. '.')
    return true
end

local gui = new('ScreenGui', {
    Name = 'luna_hide_and_seek_extreme',
    ResetOnSpawn = false,
    IgnoreGuiInset = true,
    ZIndexBehavior = Enum.ZIndexBehavior.Sibling,
    DisplayOrder = 500
}, playergui)

ui.seeker_warning = text(gui, '', 12, palette.text, {
    Name = 'SeekerWarning',
    AnchorPoint = Vector2.new(1, 0),
    Position = UDim2.new(1, -15, 0, 65),
    Size = UDim2.fromOffset(250, 30),
    TextXAlignment = Enum.TextXAlignment.Right,
    TextStrokeTransparency = 0.15,
    TextStrokeColor3 = Color3.new(0, 0, 0),
    Font = Enum.Font.GothamBold,
    ZIndex = 200,
    Visible = false
})
ui.closest_line = new('Frame', {
    Name = 'ClosestCreditLine',
    BackgroundColor3 = palette.gold,
    BorderSizePixel = 0,
    Size = UDim2.fromOffset(0, 2),
    AnchorPoint = Vector2.new(0.5, 0.5),
    Visible = false,
    ZIndex = 190
}, gui)
ui.closest_label = text(gui, '', 11, palette.gold, {
    Name = 'ClosestCreditLabel',
    Size = UDim2.fromOffset(225, 25),
    TextStrokeTransparency = 0.2,
    TextStrokeColor3 = Color3.new(0, 0, 0),
    Font = Enum.Font.GothamBold,
    TextXAlignment = Enum.TextXAlignment.Center,
    ZIndex = 191,
    Visible = false
})

local early_gui = new('ScreenGui', {
    Name = 'luna_early_it_display',
    ResetOnSpawn = false,
    IgnoreGuiInset = true,
    DisplayOrder = 10000,
    ZIndexBehavior = Enum.ZIndexBehavior.Global
}, playergui)
local early_banner = new('Frame', {
    AnchorPoint = Vector2.new(0.5, 0),
    Position = UDim2.new(0.5, 0, 0.07, 0),
    Size = UDim2.fromOffset(320, 36),
    BackgroundColor3 = palette.background,
    BackgroundTransparency = 0.06,
    Visible = false,
    BorderSizePixel = 0,
    ZIndex = 100
}, early_gui)
round(early_banner, 8)
stroke(early_banner, palette.accent)
local early_text = text(early_banner, 'SELECTING IT...', 12, palette.gold, {
    Size = UDim2.fromScale(1, 1),
    Font = Enum.Font.GothamBold,
    TextXAlignment = Enum.TextXAlignment.Center,
    ZIndex = 101
})

local function clear_entry(entry)
    for _, key in ipairs({ 'highlight', 'box', 'billboard' }) do
        if entry[key] then entry[key]:Destroy() end
    end
end

local function clear_store(store)
    for key, entry in pairs(store) do
        clear_entry(entry)
        store[key] = nil
    end
end

local function mark(store, key, part, o)
    local entry = store[key]
    if entry and (entry.part ~= part or not entry.box or not entry.box.Parent or not entry.billboard or not entry.billboard.Parent) then
        clear_entry(entry)
        entry = nil
    end
    if not entry then
        entry = { part = part }
        if o.highlight then
            entry.highlight = new('Highlight', {
                Name = 'luna_mark_highlight',
                Adornee = part,
                FillColor = o.color,
                OutlineColor = o.color,
                FillTransparency = o.fill or 0.45,
                OutlineTransparency = 0,
                DepthMode = Enum.HighlightDepthMode.AlwaysOnTop
            }, part)
        end
        local pad = o.pad or 0.35
        entry.box = new('BoxHandleAdornment', {
            Name = 'luna_mark_box',
            Adornee = part,
            AlwaysOnTop = true,
            Size = part.Size + Vector3.new(pad, pad, pad),
            Color3 = o.color,
            Transparency = o.box_t or 0.58,
            ZIndex = 6
        }, part)
        entry.billboard = new('BillboardGui', {
            Name = 'luna_mark_label',
            Adornee = part,
            AlwaysOnTop = true,
            Size = UDim2.fromOffset(o.width or 190, o.height or 43),
            StudsOffsetWorldSpace = Vector3.new(0, math.max(2, part.Size.Y * 0.5 + 1.3), 0),
            MaxDistance = 25000,
            LightInfluence = 0
        }, gui)
        entry.label = text(entry.billboard, '', o.text_size or 11, o.color, {
            Size = UDim2.fromScale(1, 1),
            Font = Enum.Font.GothamBold,
            TextXAlignment = Enum.TextXAlignment.Center,
            TextWrapped = true,
            TextStrokeTransparency = 0.15,
            TextStrokeColor3 = Color3.new(0, 0, 0)
        })
        store[key] = entry
    end
    entry.label.Text = o.caption
end

local function sweep(store, visible)
    for key, entry in pairs(store) do
        if not visible[key] or not entry.part or not entry.part:IsDescendantOf(workspace) then
            clear_entry(entry)
            store[key] = nil
        end
    end
end

local function studs(from_root, part)
    return from_root and (math.floor((from_root.Position - part.Position).Magnitude + 0.5) .. ' studs') or nil
end

function V.clear_overlay(player)
    local entry = V.overlays[player]
    if entry then
        clear_entry(entry)
        V.overlays[player] = nil
    end
end

function V.clear_players()
    for player in pairs(V.overlays) do V.clear_overlay(player) end
end

function V.clear_seeker_radius()
    if V.sphere then V.sphere:Destroy(); V.sphere = nil end
    ui.seeker_warning.Visible = false
end

function V.clear_closest_line()
    ui.closest_line.Visible = false
    ui.closest_label.Visible = false
end

function V.restore_lighting()
    for property, value in pairs(original_lighting) do
        lighting[property] = value
    end
end

function V.sync_players()
    local valid = {}
    local mine = getroot(localplayer)
    for _, player in ipairs(players:GetPlayers()) do
        if player ~= localplayer then
            valid[player] = true
            local character = player.Character
            local root = getroot(player)
            if settings.esp and character and root then
                local entry = V.overlays[player]
                if not entry or entry.character ~= character or not entry.highlight or not entry.highlight.Parent then
                    V.clear_overlay(player)
                    entry = { character = character }
                    entry.highlight = new('Highlight', {
                        Name = 'luna_player_esp',
                        Adornee = character,
                        FillTransparency = 0.7,
                        OutlineTransparency = 0,
                        DepthMode = Enum.HighlightDepthMode.AlwaysOnTop
                    }, character)
                    V.overlays[player] = entry
                end
                local color = color_of(player)
                entry.highlight.FillColor = color
                entry.highlight.OutlineColor = color
                if not entry.box or not entry.box.Parent or entry.box.Adornee ~= root then
                    if entry.box then entry.box:Destroy() end
                    entry.box = new('BoxHandleAdornment', {
                        Name = 'luna_player_box',
                        Adornee = root,
                        AlwaysOnTop = true,
                        Size = Vector3.new(3.5, 6, 3.5),
                        Transparency = 0.92,
                        ZIndex = 5
                    }, root)
                end
                entry.box.Color3 = color
                local head = character:FindFirstChild('Head') or root
                if settings.labels then
                    if not entry.billboard or entry.billboard.Adornee ~= head then
                        if entry.billboard then entry.billboard:Destroy() end
                        entry.billboard = new('BillboardGui', {
                            Name = 'luna_player_label',
                            Adornee = head,
                            AlwaysOnTop = true,
                            Size = UDim2.fromOffset(215, 44),
                            StudsOffsetWorldSpace = Vector3.new(0, 3, 0),
                            MaxDistance = 25000
                        }, head)
                        text(entry.billboard, '', 12, color, {
                            Size = UDim2.fromScale(1, 1),
                            Font = Enum.Font.GothamBold,
                            TextXAlignment = Enum.TextXAlignment.Center,
                            TextWrapped = true,
                            TextStrokeTransparency = 0.2,
                            TextStrokeColor3 = Color3.new(0, 0, 0)
                        })
                    end
                    local display = entry.billboard:FindFirstChildOfClass('TextLabel')
                    if display then
                        local suffix = ''
                        if settings.distance and mine then
                            suffix = '  |  ' .. math.floor((mine.Position - root.Position).Magnitude) .. ' studs'
                        end
                        display.Text = player.DisplayName .. '\n' .. role_of(player) .. suffix
                        display.TextColor3 = color
                    end
                elseif entry.billboard then
                    entry.billboard:Destroy()
                    entry.billboard = nil
                end
            else
                V.clear_overlay(player)
            end
        end
    end
    for player in pairs(V.overlays) do
        if not valid[player] then V.clear_overlay(player) end
    end
end

function V.sync_credits()
    if not settings.credit_esp then return end
    local container = workspace:FindFirstChild('GameObjects')
    local visible = {}
    local mine = getroot(localplayer)
    if container then
        for _, part in ipairs(container:GetChildren()) do
            if part.Name == 'Credit' and part:IsA('BasePart') and part.Parent then
                local collected = part:FindFirstChild('Collected')
                if not collected or not collected.Value then
                    visible[part] = true
                    local d = studs(mine, part)
                    mark(V.credits, part, part, {
                        color = palette.gold, caption = 'CREDIT' .. (d and ('\n' .. d) or ''),
                        highlight = true, fill = 0.3, box_t = 0.63, pad = 0.5,
                        text_size = 12, width = 145, height = 38
                    })
                end
            end
        end
    end
    sweep(V.credits, visible)
end

function V.sync_teleporters()
    if not settings.teleporter_esp then return end
    local registry = gamedata:FindFirstChild('Teleporters')
    local visible = {}
    local mine = getroot(localplayer)
    if registry then
        for _, object in ipairs(registry:GetChildren()) do
            local pad = object:IsA('ObjectValue') and object.Value
            if pad and pad:IsA('BasePart') and pad:IsDescendantOf(workspace) then
                local linked = pad:FindFirstChild('LinkedTeleporter')
                local destination = linked and linked:IsA('ObjectValue') and linked.Value
                local name = destination and destination:IsA('BasePart') and destination:IsDescendantOf(workspace) and destination.Name or '?'
                local d = studs(mine, pad)
                visible[pad] = true
                mark(V.tele, pad, pad, {
                    color = palette.portal, caption = 'TELEPORTER  ->  ' .. name .. (d and ('\n' .. d) or ''),
                    highlight = true, box_t = 0.55
                })
            end
        end
    end
    sweep(V.tele, visible)
end

function V.sync_hazards()
    local root = getroot(localplayer)
    if settings.jump_pad_esp then
        local visible = {}
        for _, object in ipairs(collectionservice:GetTagged('JumpPad')) do
            local part = part_of(object)
            if part and part:IsDescendantOf(workspace) then
                visible[object] = true
                local d = studs(root, part)
                mark(V.hz.jump, object, part, { color = palette.jump, caption = 'JUMP PAD' .. (d and ('\n' .. d) or '') })
            end
        end
        sweep(V.hz.jump, visible)
    end
    local objects = workspace:FindFirstChild('GameObjects')
    local glue_visible, camera_visible = {}, {}
    if objects and (settings.glue_esp or settings.camera_esp) then
        for _, object in ipairs(objects:GetChildren()) do
            local name = string.lower(object.Name)
            local part = part_of(object)
            if part and part:IsDescendantOf(workspace) then
                local d = studs(root, part)
                if settings.glue_esp and (name == 'glue' or name == 'glueclient' or name == 'glueplaced' or name == 'glueserver' or name == 'gluepuddle') then
                    glue_visible[object] = true
                    mark(V.hz.glue, object, part, { color = palette.glue, caption = 'GLUE HAZARD' .. (d and ('\n' .. d) or '') })
                end
                if settings.camera_esp and name:find('camera', 1, true) and name ~= 'cameraparticle' then
                    camera_visible[object] = true
                    mark(V.hz.camera, object, part, { color = palette.camera, caption = 'SEEKER CAMERA?' .. (d and ('\n' .. d) or '') })
                end
            end
        end
    end
    if settings.glue_esp then sweep(V.hz.glue, glue_visible) end
    if settings.camera_esp then sweep(V.hz.camera, camera_visible) end
end

function V.update_seeker_radius()
    if not settings.seeker_radius_esp or isit(localplayer) then
        V.clear_seeker_radius()
        return
    end
    local seeker = getit()
    local seeker_root = seeker and getroot(seeker)
    local root = getroot(localplayer)
    if not root or not seeker_root then
        V.clear_seeker_radius()
        return
    end
    if not V.sphere or not V.sphere.Parent or V.sphere.Adornee ~= seeker_root then
        if V.sphere then V.sphere:Destroy() end
        V.sphere = new('SphereHandleAdornment', {
            Name = 'luna_seeker_range',
            Adornee = seeker_root,
            AlwaysOnTop = false,
            Color3 = palette.danger,
            Transparency = 0.93,
            Radius = settings.seeker_warning_distance,
            ZIndex = 0
        }, seeker_root)
    end
    V.sphere.Radius = settings.seeker_warning_distance
    local dist = math.floor((seeker_root.Position - root.Position).Magnitude + 0.5)
    local danger = dist <= settings.seeker_warning_distance
    ui.seeker_warning.Visible = true
    ui.seeker_warning.Text = (danger and 'WARNING  ' or 'IT DISTANCE  ') .. dist .. ' studs'
    ui.seeker_warning.TextColor3 = danger and palette.danger or palette.jump
end

function V.update_credit_direction()
    if not settings.closest_credit_esp then V.clear_closest_line(); return end
    local camera = workspace.CurrentCamera
    local root = getroot(localplayer)
    local credit = find_nearest_credit(false)
    if not camera or not root or not credit then V.clear_closest_line(); return end
    local point, onscreen = camera:WorldToViewportPoint(credit.Position)
    if not onscreen or point.Z <= 0 then V.clear_closest_line(); return end
    local target = Vector2.new(point.X, point.Y)
    local origin = Vector2.new(camera.ViewportSize.X * 0.5, camera.ViewportSize.Y * 0.67)
    local delta = target - origin
    local length = delta.Magnitude
    if length < 12 then V.clear_closest_line(); return end
    ui.closest_line.Size = UDim2.fromOffset(length, 2)
    ui.closest_line.Position = UDim2.fromOffset((origin.X + target.X) / 2, (origin.Y + target.Y) / 2)
    ui.closest_line.Rotation = math.deg(math.atan2(delta.Y, delta.X))
    ui.closest_line.Visible = true
    ui.closest_label.Position = UDim2.fromOffset(
        math.clamp(target.X - 112, 0, math.max(0, camera.ViewportSize.X - 225)),
        math.clamp(target.Y - 32, 0, math.max(0, camera.ViewportSize.Y - 25))
    )
    ui.closest_label.Text = 'NEAREST CREDIT: ' .. math.floor((root.Position - credit.Position).Magnitude + 0.5) .. ' studs'
    ui.closest_label.Visible = true
end

table.insert(cleanups, function()
    V.clear_players()
    clear_store(V.credits)
    clear_store(V.tele)
    for _, store in pairs(V.hz) do clear_store(store) end
    V.clear_seeker_radius()
    V.clear_closest_line()
    if settings.fullbright then V.restore_lighting() end
end)

function M.reset_collisions()
    for part, value in pairs(M.collisions) do
        if part and part.Parent then part.CanCollide = value end
        M.collisions[part] = nil
    end
end

function M.restore(kind)
    for humanoid, baseline in pairs(M.baselines) do
        if humanoid.Parent then
            if kind == 'speed' and baseline.speed then
                humanoid.WalkSpeed = baseline.speed
                baseline.speed = nil
            elseif kind == 'jump' and baseline.jump then
                humanoid.JumpPower = baseline.jump.power
                humanoid.JumpHeight = baseline.jump.height
                baseline.jump = nil
            end
        end
    end
end

function M.step()
    local sprinting = os.clock() < A.practice_end
    if not (settings.speed or settings.jump or sprinting or A.sprint_applied) then return end
    local _, humanoid = getroot(localplayer)
    if not humanoid then return end
    local baseline = M.baselines[humanoid]
    if not baseline then
        baseline = {}
        M.baselines[humanoid] = baseline
    end
    if (settings.speed or sprinting) and not baseline.speed then
        baseline.speed = humanoid.WalkSpeed
    end
    if settings.jump and not baseline.jump then
        baseline.jump = { power = humanoid.JumpPower, height = humanoid.JumpHeight }
    end
    if settings.speed or sprinting then
        local target = baseline.speed
        if settings.speed then
            target = move.subtle_speed and math.floor(baseline.speed * 1.06 * 10 + 0.5) / 10 or move.speed
        end
        if sprinting then target = math.max(target, 29) end
        if humanoid.WalkSpeed ~= target then humanoid.WalkSpeed = target end
    end
    if sprinting then
        A.sprint_applied = true
    elseif A.sprint_applied then
        A.sprint_applied = false
        if not settings.speed then M.restore('speed') end
    end
    if settings.jump then
        local target_jump = move.subtle_jump and math.floor(baseline.jump.power * 1.06 * 10 + 0.5) / 10 or move.jump
        if humanoid.UseJumpPower then
            if humanoid.JumpPower ~= target_jump then humanoid.JumpPower = target_jump end
        else
            local original = math.max(baseline.jump.power, 1)
            local target_height = baseline.jump.height * (target_jump / original) ^ 2
            if math.abs(humanoid.JumpHeight - target_height) > 0.001 then humanoid.JumpHeight = target_height end
        end
    end
end

bind(runservice.Heartbeat, function()
    if alive then M.step() end
end)

bind(userinputservice.JumpRequest, function()
    if settings.infinite_jump then
        local _, humanoid = getroot(localplayer)
        if humanoid then humanoid:ChangeState(Enum.HumanoidStateType.Jumping) end
    end
end)

bind(runservice.Stepped, function()
    if not alive or not settings.noclip then return end
    local character = localplayer.Character
    if not character then return end
    for _, part in ipairs(character:GetDescendants()) do
        if part:IsA('BasePart') and part.CanCollide then
            if M.collisions[part] == nil then M.collisions[part] = true end
            part.CanCollide = false
        end
    end
end)

table.insert(cleanups, function()
    M.reset_collisions()
    M.restore('speed')
    M.restore('jump')
    restore_camera()
end)

function F.teleport_credit(part, automated)
    local root = getroot(localplayer)
    if not root or not part or not part.Parent then return false end
    if not automated then
        S.return_cframe = root.CFrame
    elseif not F.loop_origin then
        F.loop_origin = root.CFrame
        S.return_cframe = F.loop_origin
    end
    recent_credits[part] = os.clock()
    root.AssemblyLinearVelocity = Vector3.zero
    root.CFrame = part.CFrame + Vector3.new(0, 2.5, 0)
    return true
end

function F.restore_pulled()
    for part, original in pairs(F.pulled_credits) do
        if part.Parent then pcall(function() part.CFrame = original end) end
        F.pulled_credits[part] = nil
    end
end

function F.pull_player(target)
    local mine = getroot(localplayer)
    local theirs = getroot(target)
    if not mine or not theirs or target == localplayer then return false end
    local destination = mine.CFrame * CFrame.new(0, 0.05, 0)
    local ok = pcall(function()
        target.Character:PivotTo(destination)
        theirs.CFrame = destination
    end)
    local now = os.clock()
    if ok and type(firetouchinterest) == 'function' and now - F.last_touch >= 0.2 then
        F.last_touch = now
        pcall(function()
            firetouchinterest(mine, theirs, 0)
            firetouchinterest(mine, theirs, 1)
        end)
    end
    if ok then F.pull_ticks += 1 end
    return ok
end

function F.try_pull_credit()
    if F.credit_inflight then return false end
    local part = find_nearest_credit(true)
    local root = getroot(localplayer)
    if not part or not root or F.pulled_credits[part] then return false end
    local original = part.CFrame
    F.pulled_credits[part] = original
    recent_credits[part] = os.clock() + 6
    F.credit_inflight = true
    local data = localplayer:FindFirstChild('PlayerData')
    local balance = data and data:FindFirstChild('Credits')
    local start_balance = balance and balance.Value
    task.spawn(function()
        local started = os.clock()
        while alive and settings.pull_credits and part.Parent and F.pulled_credits[part] == original and os.clock() - started < 1.15 do
            local current = getroot(localplayer)
            local collected = part:FindFirstChild('Collected')
            if not current or (collected and collected.Value) then break end
            pcall(function() part.CFrame = CFrame.new(current.Position) * original.Rotation end)
            task.wait(0.06)
        end
        if F.pulled_credits[part] == original then
            if part.Parent then pcall(function() part.CFrame = original end) end
            F.pulled_credits[part] = nil
        end
        task.wait(0.7)
        F.credit_inflight = false
    end)
    return true
end

function F.try_pull_lobby()
    local root = getroot(localplayer)
    if not root then return false end
    local now = os.clock()
    local target = F.lobby_target
    local target_root = getroot(target)
    if not target or target.Parent ~= players or isit(target) or ingame(target) or not target_root or now - F.lobby_started >= 1.25 then
        target = next_lobby_target()
        F.lobby_target = target
        F.lobby_started = now
        if getroot(target) then F.lobby_targets += 1 end
    end
    if not target then return false end
    return F.pull_player(target)
end

function F.try_evade()
    local running = gamedata:FindFirstChild('GameRunning')
    local seeking = gamedata:FindFirstChild('ItSeeking')
    if not settings.auto_evade or isit(localplayer) or not ingame(localplayer) or not running or not running.Value then
        table.clear(F.safe_positions)
        return
    end
    local mine = getroot(localplayer)
    local seeker = getit()
    local seeker_root = getroot(seeker)
    if not mine or not seeker_root or seeker == localplayer then return end
    local now = os.clock()
    local distance = (mine.Position - seeker_root.Position).Magnitude
    if distance > 90 and now - F.last_safe > 1.2 then
        local last = F.safe_positions[#F.safe_positions]
        if not last or (last.cf.Position - mine.Position).Magnitude > 13 then
            table.insert(F.safe_positions, { cf = mine.CFrame, t = now })
            if #F.safe_positions > 16 then table.remove(F.safe_positions, 1) end
            F.last_safe = now
        end
    end
    if not seeking or not seeking.Value or distance > 38 or now - F.last_escape < 2 then return end
    local destination
    local best = 80
    for _, spot in ipairs(F.safe_positions) do
        local d = (spot.cf.Position - seeker_root.Position).Magnitude
        if now - spot.t < 50 and d > best and (spot.cf.Position - mine.Position).Magnitude > 30 then
            best, destination = d, spot.cf
        end
    end
    for _, player in ipairs(players:GetPlayers()) do
        if player ~= localplayer and player ~= seeker and ingame(player) and not isit(player) then
            local other = getroot(player)
            if other then
                local d = (other.Position - seeker_root.Position).Magnitude
                if d > best and (other.Position - mine.Position).Magnitude > 30 then
                    best, destination = d, other.CFrame * CFrame.new(4, 0, 4)
                end
            end
        end
    end
    if destination then
        F.last_escape = now
        mine.AssemblyLinearVelocity = Vector3.zero
        mine.CFrame = destination
        notify('Auto evade: escaped from It (not guaranteed against server tags).')
    elseif now - F.last_notice > 8 then
        F.last_notice = now
        notify('Auto evade: no known safe destination yet.')
    end
end

table.insert(cleanups, F.restore_pulled)

function A.clear_hooks()
    local handler = A.handler
    if handler then
        if handler.SetCooldown == A.set_hook then handler.SetCooldown = A.set_original end
        if handler.MarkAbilityAsActive == A.mark_hook then handler.MarkAbilityAsActive = A.mark_original end
    end
    A.handler, A.set_original, A.mark_original, A.set_hook, A.mark_hook = nil, nil, nil, nil, nil
    A.active, A.active_known = false, false
end

function A.bridge()
    if A.handler and A.handler.SetCooldown == A.set_hook then return true end
    local scripts = localplayer:FindFirstChild('PlayerScripts')
    local modules = scripts and scripts:FindFirstChild('Modules')
    local module_script = modules and modules:FindFirstChild('AbilityHandler')
    if not module_script then A.last_result = 'AbilityHandler missing'; return false end
    local ok, module = pcall(require, module_script)
    if not ok or type(module) ~= 'table' or type(module.SetCooldown) ~= 'function' then
        A.last_result = 'AbilityHandler unavailable'
        return false
    end
    if A.handler and A.handler ~= module then A.clear_hooks() end
    if A.handler == module then
        if module.SetCooldown == A.set_hook then return true end
        A.clear_hooks()
    end
    A.handler = module
    A.set_original = module.SetCooldown
    A.mark_original = module.MarkAbilityAsActive
    A.set_hook = function(self, duration, ...)
        if alive and settings.it_no_cooldown and isit(localplayer) and type(duration) == 'number' then
            if duration > 0 then A.intercepts += 1 end
            duration = 0
        end
        return A.set_original(self, duration, ...)
    end
    module.SetCooldown = A.set_hook
    if type(A.mark_original) == 'function' then
        A.mark_hook = function(self, active, ...)
            local result = A.mark_original(self, active, ...)
            if alive then
                A.active_known = true
                A.active = active == true
            end
            return result
        end
        module.MarkAbilityAsActive = A.mark_hook
    end
    return true
end

function A.can_use()
    local running = gamedata:FindFirstChild('GameRunning')
    local seeking = gamedata:FindFirstChild('ItSeeking')
    local character = localplayer.Character
    local root, humanoid = getroot(localplayer)
    return running and running.Value and seeking and seeking.Value and isit(localplayer)
        and root ~= nil and humanoid ~= nil and character and character:GetAttribute('IsSeekerCharacter') == true
end

function A.equipped_name()
    local data = localplayer:FindFirstChild('PlayerData')
    local equipped = data and data:FindFirstChild('EquippedCharacter')
    local wanted = equipped and tostring(equipped.Value)
    if not wanted or wanted == '' then return 'Unknown' end
    local main = playergui:FindFirstChild('MainGui')
    local shop = main and main:FindFirstChild('ShopFrame')
    if shop then
        for _, child in ipairs(shop:GetDescendants()) do
            if child.Name == 'Data' then
                local character = child:FindFirstChild('CharacterName')
                if character and tostring(character.Value) == wanted then
                    local ability = child:FindFirstChild('Ability')
                    if ability and ability:IsA('StringValue') then return ability.Value end
                end
            end
        end
    end
    return 'Unknown'
end

function A.press()
    if not A.can_use() then A.last_result = 'Not seeking as It'; return false end
    local main = playergui:FindFirstChild('MainGui')
    local frame = main and main:FindFirstChild('AbilityFrame')
    local control = frame and frame:FindFirstChild('AbilityButton')
    if not control then A.last_result = 'Ability button unavailable'; return false end
    if type(firesignal) == 'function' then
        local ok = pcall(firesignal, control.MouseButton1Click)
        if ok then
            A.last_result = 'Button signal sent'
            A.attempts += 1
            return true
        end
    end
    local ok = pcall(function()
        local virtual_input = game:GetService('VirtualInputManager')
        virtual_input:SendKeyEvent(true, Enum.KeyCode.One, false, game)
        virtual_input:SendKeyEvent(false, Enum.KeyCode.One, false, game)
    end)
    if ok then
        A.last_result = 'Key 1 sent'
        A.attempts += 1
        return true
    end
    A.last_result = 'No supported activation method'
    return false
end

function A.clear_cooldown()
    if not A.bridge() or not A.handler or not A.can_use() then return false end
    local now = os.clock()
    if now - A.last_clear < 0.2 then return true end
    local ok, err = pcall(A.set_original, A.handler, 0)
    if ok then
        A.last_clear = now
        A.clears += 1
        A.err = nil
    else
        A.err = tostring(err)
    end
    return ok
end

local function practice_effect(obj)
    table.insert(A.objects, obj)
    return obj
end

function A.practice_sprint()
    if not getroot(localplayer) then notify('Practice Sprint: character unavailable.'); return end
    A.practice_end = os.clock() + 6
    A.last_result = 'Practice Sprint: local boost for 6s'
    notify('Practice Sprint active (local movement only).')
end

function A.practice_glue()
    local root = getroot(localplayer)
    if not root then return end
    local ground = workspace:Raycast(root.Position, Vector3.new(0, -18, 0))
    local point = ground and ground.Position or root.Position - Vector3.new(0, 3, 0)
    local glue = practice_effect(new('Part', {
        Name = 'luna_practice_glue',
        Anchored = true, CanCollide = false, CanTouch = false,
        Material = Enum.Material.Neon,
        Color = Color3.fromRGB(121, 224, 121),
        Transparency = 0.4,
        Shape = Enum.PartType.Ball,
        Size = Vector3.new(0.22, 10, 10),
        CFrame = CFrame.new(point + Vector3.new(0, 0.13, 0))
    }, workspace))
    task.delay(7, function() if glue.Parent then glue:Destroy() end end)
    A.last_result = 'Practice Glue: visual patch only'
    notify('Practice Glue spawned locally; it cannot trap anyone.')
end

function A.practice_camera_toggle()
    local camera = workspace.CurrentCamera
    local root = getroot(localplayer)
    if not camera or not root then return end
    if A.practice_camera then
        camera.CameraType = A.practice_camera.kind
        camera.CameraSubject = A.practice_camera.subject
        camera.CFrame = A.practice_camera.cf
        A.practice_camera = nil
        A.last_result = 'Practice Camera: exited'
        notify('Practice Camera closed.')
        return
    end
    A.practice_camera = { kind = camera.CameraType, subject = camera.CameraSubject, cf = camera.CFrame }
    camera.CameraType = Enum.CameraType.Scriptable
    camera.CFrame = CFrame.lookAt(root.Position + root.CFrame.LookVector * 10 + Vector3.new(0, 6, 0), root.Position)
    A.last_result = 'Practice Camera: local POV active'
    notify('Practice Camera active. Press again to exit.')
end

function A.practice_smash()
    local root = getroot(localplayer)
    if not root then return end
    local ring = practice_effect(new('Part', {
        Name = 'luna_practice_smash',
        Anchored = true, CanCollide = false, CanTouch = false,
        Shape = Enum.PartType.Ball,
        Size = Vector3.new(0.12, 2, 2),
        CFrame = CFrame.new(root.Position - Vector3.new(0, 2.6, 0)),
        Material = Enum.Material.Neon,
        Color = palette.accent,
        Transparency = 0.15
    }, workspace))
    tweenservice:Create(ring, TweenInfo.new(0.65), { Size = Vector3.new(0.12, 23, 23), Transparency = 1 }):Play()
    task.delay(0.8, function() if ring.Parent then ring:Destroy() end end)
    A.last_result = 'Practice Smash: local ring only'
    notify('Practice Smash visual (no players stunned).')
end

table.insert(cleanups, function()
    A.clear_hooks()
    if A.practice_camera and workspace.CurrentCamera then
        local camera = workspace.CurrentCamera
        camera.CameraType = A.practice_camera.kind
        camera.CameraSubject = A.practice_camera.subject
        camera.CFrame = A.practice_camera.cf
        A.practice_camera = nil
    end
    for _, item in ipairs(A.objects) do
        if item.Parent then item:Destroy() end
    end
end)

local function afk_pulse()
    if not alive or not settings.anti_afk then return false end
    local ok = pcall(function()
        virtualuser:CaptureController()
        virtualuser:ClickButton2(Vector2.new(0, 0))
    end)
    if ok then
        S.afk_pulses += 1
        S.last_pulse = os.clock()
    end
    return ok
end

bind(localplayer.Idled, function() afk_pulse() end)
every(45, function()
    
    if settings.anti_afk and os.clock() - S.last_pulse >= 240 then afk_pulse() end
end)

local viewport = workspace.CurrentCamera and workspace.CurrentCamera.ViewportSize or Vector2.new(1280, 720)
local win_w = math.clamp(viewport.X - 120, 520, 600)
local win_h = math.clamp(viewport.Y - 180, 320, 372)
local HEADER, SIDEBAR = 40, 124

local window = new('Frame', {
    Name = 'Window',
    AnchorPoint = Vector2.new(0.5, 0.5),
    Position = UDim2.fromScale(0.5, 0.5),
    Size = UDim2.fromOffset(win_w, win_h),
    BackgroundColor3 = palette.background,
    BorderSizePixel = 0,
    Active = true,
    ClipsDescendants = true
}, gui)
round(window, 8)
stroke(window, palette.border)
new('UISizeConstraint', { MinSize = Vector2.new(480, HEADER), MaxSize = Vector2.new(980, 780) }, window)

local header = new('Frame', {
    Name = 'Header',
    Size = UDim2.new(1, 0, 0, HEADER),
    BackgroundColor3 = palette.side,
    BorderSizePixel = 0,
    Active = true
}, window)
new('Frame', {
    Position = UDim2.new(0, 0, 1, -1), Size = UDim2.new(1, 0, 0, 1),
    BackgroundColor3 = palette.border, BorderSizePixel = 0
}, header)
local moon = new('Frame', {
    Position = UDim2.fromOffset(12, 10), Size = UDim2.fromOffset(20, 20),
    BackgroundColor3 = palette.lilac, BorderSizePixel = 0
}, header)
round(moon, 10)
local moon_cut = new('Frame', {
    Position = UDim2.fromOffset(7, -3), Size = UDim2.fromOffset(18, 18),
    BackgroundColor3 = palette.side, BorderSizePixel = 0
}, moon)
round(moon_cut, 9)
text(header, 'luna.dev', 14, palette.text, {
    Position = UDim2.fromOffset(40, 0), Size = UDim2.fromOffset(62, HEADER), Font = Enum.Font.GothamBold
})
text(header, '/ hide and seek', 10, palette.dim, {
    Position = UDim2.fromOffset(104, 0), Size = UDim2.fromOffset(110, HEADER)
})

local controls = new('Frame', {
    AnchorPoint = Vector2.new(1, 0.5),
    Position = UDim2.new(1, -10, 0.5, 0),
    Size = UDim2.fromOffset(0, 24),
    AutomaticSize = Enum.AutomaticSize.X,
    BackgroundTransparency = 1
}, header)
new('UIListLayout', {
    FillDirection = Enum.FillDirection.Horizontal,
    HorizontalAlignment = Enum.HorizontalAlignment.Right,
    VerticalAlignment = Enum.VerticalAlignment.Center,
    SortOrder = Enum.SortOrder.LayoutOrder,
    Padding = UDim.new(0, 6)
}, controls)

ui.stats = text(controls, 'FPS -- / --ms', 10, palette.muted, {
    Size = UDim2.fromOffset(92, 24), TextXAlignment = Enum.TextXAlignment.Right,
    Font = Enum.Font.GothamMedium, LayoutOrder = 1
})
ui.afk = new('TextButton', {
    Size = UDim2.fromOffset(92, 22), LayoutOrder = 2, BorderSizePixel = 0,
    Font = Enum.Font.GothamBold, TextSize = 10, Text = '', AutoButtonColor = true,
    BackgroundColor3 = palette.raised
}, controls)
round(ui.afk, 5)
local afk_stroke = stroke(ui.afk, palette.success, 0.35)
local function render_afk_badge()
    local on = settings.anti_afk
    ui.afk.Text = on and 'ANTI-AFK ON' or 'ANTI-AFK OFF'
    ui.afk.TextColor3 = on and palette.success or palette.muted
    ui.afk.BackgroundColor3 = on and Color3.fromRGB(22, 44, 38) or palette.raised
    afk_stroke.Color = on and palette.success or palette.border
end
register_render('anti_afk', render_afk_badge)
render_afk_badge()
bind(ui.afk.MouseButton1Click, function()
    settings.anti_afk = not settings.anti_afk
    update_toggle('anti_afk')
    storage.schedule()
    notify('Anti-AFK: ' .. (settings.anti_afk and 'enabled' or 'disabled'))
end)

local log_button = button(controls, 'LOG', UDim2.new(), UDim2.fromOffset(38, 22), function()
    ui.log_panel.Visible = not ui.log_panel.Visible
    if ui.log_panel.Visible then ui.results.Visible = false; ui.refresh_log() end
end)
log_button.LayoutOrder = 3
local min_button = button(controls, '-', UDim2.new(), UDim2.fromOffset(24, 22), function() end)
min_button.LayoutOrder = 4
min_button.TextSize = 14
local close_button = button(controls, 'x', UDim2.new(), UDim2.fromOffset(24, 22), function() end)
close_button.LayoutOrder = 5
close_button.TextColor3 = palette.danger


local sidebar = new('Frame', {
    Name = 'Sidebar', Position = UDim2.fromOffset(0, HEADER),
    Size = UDim2.new(0, SIDEBAR, 1, -HEADER),
    BackgroundColor3 = palette.side, BorderSizePixel = 0
}, window)
new('Frame', {
    Position = UDim2.new(1, -1, 0, 0), Size = UDim2.new(0, 1, 1, 0),
    BackgroundColor3 = palette.border, BorderSizePixel = 0
}, sidebar)

ui.search = new('TextBox', {
    Position = UDim2.fromOffset(8, 8), Size = UDim2.new(1, -46, 0, 26),
    BackgroundColor3 = palette.background, BorderSizePixel = 0,
    PlaceholderText = 'Search...', PlaceholderColor3 = palette.dim,
    ClearTextOnFocus = false, Text = '', Font = Enum.Font.Gotham, TextSize = 11,
    TextColor3 = palette.text, TextXAlignment = Enum.TextXAlignment.Left
}, sidebar)
round(ui.search, 5)
stroke(ui.search)
new('UIPadding', { PaddingLeft = UDim.new(0, 8), PaddingRight = UDim.new(0, 4) }, ui.search)
local search_icon = new('TextButton', {
    Position = UDim2.new(1, -34, 0, 8), Size = UDim2.fromOffset(26, 26),
    BackgroundColor3 = palette.accentdark, BorderSizePixel = 0, Text = '', AutoButtonColor = true
}, sidebar)
round(search_icon, 5)
local lens = new('Frame', {
    Position = UDim2.fromOffset(7, 6), Size = UDim2.fromOffset(10, 10), BackgroundTransparency = 1
}, search_icon)
round(lens, 5)
new('UIStroke', {
    Color = palette.lilac, Thickness = 1.5, ApplyStrokeMode = Enum.ApplyStrokeMode.Border
}, lens)
new('Frame', {
    Position = UDim2.fromOffset(15, 17), Size = UDim2.fromOffset(6, 2), Rotation = 45,
    BackgroundColor3 = palette.lilac, BorderSizePixel = 0
}, search_icon)
bind(search_icon.MouseButton1Click, function() ui.search:CaptureFocus() end)

local nav = new('ScrollingFrame', {
    Name = 'Navigation', Position = UDim2.fromOffset(6, 44), Size = UDim2.new(1, -12, 1, -68),
    CanvasSize = UDim2.new(), AutomaticCanvasSize = Enum.AutomaticSize.Y,
    BackgroundTransparency = 1, BorderSizePixel = 0,
    ScrollBarThickness = 2, ScrollBarImageColor3 = palette.accent
}, sidebar)
new('UIListLayout', { Padding = UDim.new(0, 2), SortOrder = Enum.SortOrder.LayoutOrder }, nav)
text(sidebar, 'V14  /  VIOLET', 9, palette.dim, {
    Position = UDim2.new(0, 14, 1, -22), Size = UDim2.new(1, -28, 0, 16), Font = Enum.Font.GothamMedium
})


local content = new('Frame', {
    Name = 'Content', Position = UDim2.fromOffset(SIDEBAR, HEADER),
    Size = UDim2.new(1, -SIDEBAR, 1, -HEADER),
    BackgroundColor3 = palette.surface, BorderSizePixel = 0
}, window)
ui.title = text(content, 'Home', 15, palette.text, {
    Position = UDim2.fromOffset(14, 7), Size = UDim2.new(0.55, -14, 0, 20), Font = Enum.Font.GothamBold
})
ui.subtitle = text(content, '', 10, palette.muted, {
    Position = UDim2.fromOffset(14, 28), Size = UDim2.new(0.55, -14, 0, 16), TextTruncate = Enum.TextTruncate.AtEnd
})
ui.tag = text(content, 'MAIN', 10, palette.accent, {
    AnchorPoint = Vector2.new(1, 0), Position = UDim2.new(1, -14, 0, 8), Size = UDim2.new(0.45, -14, 0, 16),
    TextXAlignment = Enum.TextXAlignment.Right, Font = Enum.Font.GothamBold
})
ui.status = text(content, 'Reading round status...', 10, palette.muted, {
    AnchorPoint = Vector2.new(1, 0), Position = UDim2.new(1, -14, 0, 26), Size = UDim2.new(0.45, -14, 0, 16),
    TextXAlignment = Enum.TextXAlignment.Right, TextTruncate = Enum.TextTruncate.AtEnd
})
new('Frame', {
    Position = UDim2.fromOffset(10, 50), Size = UDim2.new(1, -20, 0, 1),
    BackgroundColor3 = palette.border, BorderSizePixel = 0
}, content)
new('Frame', {
    Position = UDim2.new(0, 10, 1, -26), Size = UDim2.new(1, -20, 0, 1),
    BackgroundColor3 = palette.border, BorderSizePixel = 0
}, content)
ui.message = text(content, 'Ready.', 10, palette.muted, {
    Position = UDim2.new(0, 10, 1, -24), Size = UDim2.new(1, -150, 0, 22), TextTruncate = Enum.TextTruncate.AtEnd
})
text(content, 'LUNA.DEV / V14 VIOLET', 9, palette.dim, {
    AnchorPoint = Vector2.new(1, 0), Position = UDim2.new(1, -10, 1, -24), Size = UDim2.fromOffset(130, 22),
    TextXAlignment = Enum.TextXAlignment.Right
})


local tab_defs = {
    { 'MAIN', { { 'home', 'Home' }, { 'players', 'Players' }, { 'autofarm', 'Auto Farm' } } },
    { 'FEATURES', { { 'visuals', 'Visuals' }, { 'movement', 'Movement' }, { 'farming', 'Farming' }, { 'abilities', 'Abilities' }, { 'shop', 'Shop' } } },
    { 'SYSTEM', { { 'tests', 'Tests' }, { 'config', 'Config' } } }
}
local page_titles = {
    home = { 'Home', 'Overview, shortcuts and quick toggles.' },
    players = { 'Players', 'Seeker first, then hiders and lobby.' },
    autofarm = { 'Auto Farm', 'Automatic phase selection and farming.' },
    visuals = { 'Visuals', 'ESP, highlights and awareness tools.' },
    movement = { 'Movement', 'Speed, jump, noclip and teleports.' },
    farming = { 'Farming', 'Manual and repeating farming controls.' },
    abilities = { 'Abilities', 'Seeker abilities and practice effects.' },
    shop = { 'Shop', 'Credit purchase tools.' },
    tests = { 'Tests', 'Experiments and gamepass diagnostics.' },
    config = { 'Config', 'Save, reset and system options.' }
}

local pages, tabs = {}, {}
local current_page = 'home'

local function show_page(name)
    current_page = name
    local info_ = page_titles[name]
    ui.title.Text = info_[1]
    ui.subtitle.Text = info_[2]
    ui.tag.Text = string.upper(info_[1])
    ui.results.Visible = false
    ui.log_panel.Visible = false
    for page_name, frame in pairs(pages) do
        frame.Visible = not S.minimized and page_name == name
    end
    for tab_name, control in pairs(tabs) do
        local selected_tab = tab_name == name
        control.BackgroundTransparency = selected_tab and 0 or 1
        control.TextColor3 = selected_tab and palette.lilac or palette.muted
        control.Diamond.BackgroundColor3 = selected_tab and palette.accent or palette.dim
    end
end

local page_order = {}
for _, group in ipairs(tab_defs) do
    local label = text(nav, group[1], 9, palette.dim, {
        Size = UDim2.new(1, 0, 0, 20), Font = Enum.Font.GothamBold, LayoutOrder = nextorder(nav)
    })
    new('UIPadding', { PaddingLeft = UDim.new(0, 6), PaddingTop = UDim.new(0, 4) }, label)
    for _, def in ipairs(group[2]) do
        table.insert(page_order, def[1])
        local tab = new('TextButton', {
            Size = UDim2.new(1, -4, 0, 25), BackgroundColor3 = palette.accentdark, BackgroundTransparency = 1,
            BorderSizePixel = 0, Text = def[2], TextSize = 11, Font = Enum.Font.GothamMedium,
            TextColor3 = palette.muted, TextXAlignment = Enum.TextXAlignment.Left,
            AutoButtonColor = false, LayoutOrder = nextorder(nav)
        }, nav)
        round(tab, 5)
        new('UIPadding', { PaddingLeft = UDim.new(0, 26) }, tab)
        new('Frame', {
            Name = 'Diamond', Position = UDim2.fromOffset(-14, 10), Size = UDim2.fromOffset(5, 5),
            Rotation = 45, BackgroundColor3 = palette.dim, BorderSizePixel = 0
        }, tab)
        tabs[def[1]] = tab
        bind(tab.MouseButton1Click, function() show_page(def[1]) end)
    end
end

for _, name in ipairs(page_order) do
    local page = new('ScrollingFrame', {
        Name = name, Position = UDim2.fromOffset(10, 56), Size = UDim2.new(1, -20, 1, -86),
        CanvasSize = UDim2.new(), AutomaticCanvasSize = Enum.AutomaticSize.Y,
        BackgroundTransparency = 1, BorderSizePixel = 0,
        ScrollBarThickness = 3, ScrollBarImageColor3 = palette.accent, Visible = false
    }, content)
    new('UIListLayout', { Padding = UDim.new(0, 5), SortOrder = Enum.SortOrder.LayoutOrder }, page)
    new('UIPadding', { PaddingRight = UDim.new(0, 7), PaddingBottom = UDim.new(0, 4) }, page)
    pages[name] = page
end


ui.results = new('ScrollingFrame', {
    Position = UDim2.fromOffset(10, 56), Size = UDim2.new(1, -20, 1, -86),
    CanvasSize = UDim2.new(), AutomaticCanvasSize = Enum.AutomaticSize.Y,
    BackgroundColor3 = palette.background, BorderSizePixel = 0,
    ScrollBarThickness = 3, ScrollBarImageColor3 = palette.accent, Visible = false, ZIndex = 40
}, content)
round(ui.results, 6)
stroke(ui.results)
new('UIListLayout', { Padding = UDim.new(0, 4), SortOrder = Enum.SortOrder.LayoutOrder }, ui.results)
new('UIPadding', {
    PaddingTop = UDim.new(0, 6), PaddingBottom = UDim.new(0, 6),
    PaddingLeft = UDim.new(0, 6), PaddingRight = UDim.new(0, 8)
}, ui.results)

ui.log_panel = new('ScrollingFrame', {
    Position = UDim2.fromOffset(10, 56), Size = UDim2.new(1, -20, 1, -86),
    CanvasSize = UDim2.new(), AutomaticCanvasSize = Enum.AutomaticSize.Y,
    BackgroundColor3 = palette.background, BorderSizePixel = 0,
    ScrollBarThickness = 3, ScrollBarImageColor3 = palette.accent, Visible = false, ZIndex = 45
}, content)
round(ui.log_panel, 6)
stroke(ui.log_panel)
new('UIPadding', {
    PaddingTop = UDim.new(0, 8), PaddingBottom = UDim.new(0, 8),
    PaddingLeft = UDim.new(0, 10), PaddingRight = UDim.new(0, 10)
}, ui.log_panel)
local log_label = text(ui.log_panel, '', 10, palette.muted, {
    Size = UDim2.new(1, 0, 0, 0), AutomaticSize = Enum.AutomaticSize.Y,
    TextWrapped = true, TextYAlignment = Enum.TextYAlignment.Top
})
function ui.refresh_log()
    if not ui.log_panel.Visible then return end
    local lines = {}
    for i = #logs, 1, -1 do table.insert(lines, logs[i]) end
    log_label.Text = #lines > 0 and table.concat(lines, '\n') or 'No activity yet.'
end


local search_index = {}

local function heading(pagename, content_text)
    local page = pages[pagename]
    return text(page, content_text, 10, palette.accent, {
        Size = UDim2.new(1, 0, 0, 18), Font = Enum.Font.GothamBold,
        TextYAlignment = Enum.TextYAlignment.Bottom, LayoutOrder = nextorder(page)
    })
end

local function info(pagename, content_text, color, size)
    local page = pages[pagename]
    return text(page, content_text, size or 10, color or palette.muted, {
        Size = UDim2.new(1, 0, 0, 0), AutomaticSize = Enum.AutomaticSize.Y,
        TextWrapped = true, TextYAlignment = Enum.TextYAlignment.Top, LayoutOrder = nextorder(page)
    })
end

local function make_card(pagename, border_color)
    local page = pages[pagename]
    local card = new('Frame', {
        Size = UDim2.new(1, 0, 0, 0), AutomaticSize = Enum.AutomaticSize.Y,
        BackgroundColor3 = palette.card, BorderSizePixel = 0, LayoutOrder = nextorder(page)
    }, page)
    round(card, 6)
    stroke(card, border_color)
    new('UIPadding', {
        PaddingTop = UDim.new(0, 8), PaddingBottom = UDim.new(0, 8),
        PaddingLeft = UDim.new(0, 10), PaddingRight = UDim.new(0, 10)
    }, card)
    new('UIListLayout', { Padding = UDim.new(0, 3), SortOrder = Enum.SortOrder.LayoutOrder }, card)
    return card
end

local function status_card(pagename, color)
    local card = make_card(pagename)
    return text(card, '', 11, color or palette.text, {
        Size = UDim2.new(1, 0, 0, 0), AutomaticSize = Enum.AutomaticSize.Y,
        TextWrapped = true, TextYAlignment = Enum.TextYAlignment.Top
    })
end

local function warn_card(pagename, title, body)
    local card = make_card(pagename, palette.danger)
    text(card, title, 10, palette.danger, { Size = UDim2.new(1, 0, 0, 14), Font = Enum.Font.GothamBold, LayoutOrder = 1 })
    return text(card, body, 10, palette.muted, {
        Size = UDim2.new(1, 0, 0, 0), AutomaticSize = Enum.AutomaticSize.Y,
        TextWrapped = true, TextYAlignment = Enum.TextYAlignment.Top, LayoutOrder = 2
    })
end

local function make_row(pagename, title, desc, reserve)
    local page = pages[pagename]
    local row = new('Frame', {
        Size = UDim2.new(1, 0, 0, 38), BackgroundColor3 = palette.card,
        BorderSizePixel = 0, LayoutOrder = nextorder(page)
    }, page)
    round(row, 6)
    stroke(row)
    text(row, title, 12, palette.text, {
        Position = UDim2.fromOffset(10, 3), Size = UDim2.new(1, -(reserve + 14), 0, 17),
        Font = Enum.Font.GothamMedium, TextTruncate = Enum.TextTruncate.AtEnd
    })
    text(row, desc, 10, palette.muted, {
        Position = UDim2.fromOffset(10, 20), Size = UDim2.new(1, -(reserve + 14), 0, 14),
        TextTruncate = Enum.TextTruncate.AtEnd
    })
    table.insert(search_index, { page = pagename, title = title, desc = desc, row = row })
    return row
end

local function add_switch(row, key, callback, title)
    local track = new('TextButton', {
        AnchorPoint = Vector2.new(1, 0.5), Position = UDim2.new(1, -10, 0.5, 0),
        Size = UDim2.fromOffset(34, 18), Text = '', AutoButtonColor = false,
        BorderSizePixel = 0, BackgroundColor3 = palette.raised
    }, row)
    round(track, 9)
    local knob = new('Frame', {
        Size = UDim2.fromOffset(12, 12), Position = UDim2.fromOffset(3, 3),
        BorderSizePixel = 0, BackgroundColor3 = palette.muted
    }, track)
    round(knob, 6)
    local function render()
        local on = settings[key] == true
        track.BackgroundColor3 = on and palette.accentmid or palette.raised
        tweenservice:Create(knob, TweenInfo.new(0.12), {
            Position = on and UDim2.fromOffset(19, 3) or UDim2.fromOffset(3, 3),
            BackgroundColor3 = on and palette.text or palette.muted
        }):Play()
    end
    register_render(key, render)
    render()
    bind(track.MouseButton1Click, function()
        settings[key] = not settings[key]
        if callback then callback(settings[key]) end
        update_toggle(key)
        storage.schedule()
        notify(title .. ': ' .. (settings[key] and 'enabled' or 'disabled'))
    end)
end

local function add_input(row, right, value, min_value, max_value, apply)
    local box = new('TextBox', {
        AnchorPoint = Vector2.new(1, 0.5), Position = UDim2.new(1, -right, 0.5, 0),
        Size = UDim2.fromOffset(52, 22), BackgroundColor3 = palette.background, BorderSizePixel = 0,
        Font = Enum.Font.Gotham, TextSize = 11, TextColor3 = palette.text,
        Text = tostring(value), ClearTextOnFocus = false
    }, row)
    round(box, 5)
    stroke(box)
    box:SetAttribute('last', value)
    bind(box.FocusLost, function()
        local number = tonumber(box.Text)
        if not number then
            box.Text = tostring(box:GetAttribute('last'))
            return
        end
        number = math.floor(math.clamp(number, min_value, max_value) * 10 + 0.5) / 10
        box.Text = tostring(number)
        box:SetAttribute('last', number)
        apply(number)
    end)
    return box
end

local function toggle(pagename, title, desc, key, callback, input)
    local row = make_row(pagename, title, desc, input and 120 or 56)
    if input then add_input(row, 54, input.value, input.min, input.max, input.apply) end
    add_switch(row, key, callback, title)
    return row
end

local function input_row(pagename, title, desc, value, min_value, max_value, apply)
    local row = make_row(pagename, title, desc, 70)
    return add_input(row, 10, value, min_value, max_value, apply)
end

local function buttons(pagename, defs)
    local page = pages[pagename]
    local holder = new('Frame', {
        Size = UDim2.new(1, 0, 0, 28), BackgroundTransparency = 1, LayoutOrder = nextorder(page)
    }, page)
    new('UIListLayout', {
        FillDirection = Enum.FillDirection.Horizontal, Padding = UDim.new(0, 6), SortOrder = Enum.SortOrder.LayoutOrder
    }, holder)
    local n = #defs
    local made = {}
    for i, def in ipairs(defs) do
        local control = button(holder, def[1], UDim2.new(), UDim2.new(1 / n, -6 * (n - 1) / n, 1, 0), def[2], def[3])
        control.LayoutOrder = i
        table.insert(search_index, { page = pagename, title = def[1], desc = 'Action', row = holder })
        made[i] = control
    end
    return made
end

local on = {}
function on.esp(enabled) if not enabled then V.clear_players() end end
function on.credit_esp(enabled) if not enabled then clear_store(V.credits) end end
function on.teleporter_esp(enabled) if not enabled then clear_store(V.tele) end end
function on.jump_pad_esp(enabled) if not enabled then clear_store(V.hz.jump) end end
function on.glue_esp(enabled) if not enabled then clear_store(V.hz.glue) end end
function on.camera_esp(enabled) if not enabled then clear_store(V.hz.camera) end end
function on.seeker_radius_esp(enabled) if not enabled then V.clear_seeker_radius() end end
function on.closest_credit_esp(enabled) if not enabled then V.clear_closest_line() end end
function on.fullbright(enabled) if not enabled then V.restore_lighting() end end
function on.speed(enabled) if not enabled then M.restore('speed') end end
function on.jump(enabled) if not enabled then M.restore('jump') end end
function on.noclip(enabled) if not enabled then M.reset_collisions() end end
function on.auto_credits(enabled)
    F.loop_origin = nil
    if enabled then
        settings.pull_credits = false
        update_toggle('pull_credits')
        F.restore_pulled()
        F.credit_attempts = 0
    end
end
function on.pull_credits(enabled)
    if enabled then
        settings.auto_credits = false
        update_toggle('auto_credits')
    else
        F.restore_pulled()
    end
end
function on.pull_lobby(enabled)
    F.lobby_target = nil
    F.lobby_started = 0
    if enabled then
        settings.auto_seek = false
        update_toggle('auto_seek')
        notify('Pulling lobby players. Client-only: tags and rewards are not confirmed.')
    end
end
function on.auto_seek(enabled)
    if enabled then
        settings.pull_lobby = false
        update_toggle('pull_lobby')
    end
end
function on.it_no_cooldown(enabled)
    if enabled then
        A.bridge()
        A.clear_cooldown()
    elseif not settings.it_keep_active then
        A.clear_hooks()
    end
end
function on.it_keep_active(enabled)
    if enabled then
        A.bridge()
        A.sprint_first = nil
        A.last_attempt = 0
    elseif not settings.it_no_cooldown then
        A.clear_hooks()
    end
end

do
    heading('home', 'QUICK ACCESS')
    local grid = new('Frame', {
        Size = UDim2.new(1, 0, 0, 0), AutomaticSize = Enum.AutomaticSize.Y,
        BackgroundTransparency = 1, LayoutOrder = nextorder(pages.home)
    }, pages.home)
    new('UIGridLayout', {
        CellSize = UDim2.new(1 / 3, -4, 0, 28), CellPadding = UDim2.fromOffset(6, 6),
        SortOrder = Enum.SortOrder.LayoutOrder
    }, grid)
    local cards = {
        { 'ESP', 'visuals' }, { 'Players', 'players' }, { 'Movement', 'movement' },
        { 'Auto Farm', 'autofarm' }, { 'Abilities', 'abilities' }, { 'Shop', 'shop' },
        { 'Farming', 'farming' }, { 'Config', 'config' }, { 'Search Everything', 'search' }
    }
    for i, def in ipairs(cards) do
        local card = new('TextButton', {
            BackgroundColor3 = palette.card, BorderSizePixel = 0, Text = '', AutoButtonColor = true, LayoutOrder = i
        }, grid)
        round(card, 5)
        stroke(card)
        new('Frame', {
            Position = UDim2.fromOffset(10, 12), Size = UDim2.fromOffset(5, 5), Rotation = 45,
            BackgroundColor3 = palette.accent, BorderSizePixel = 0
        }, card)
        text(card, def[1], 11, palette.text, {
            Position = UDim2.fromOffset(24, 0), Size = UDim2.new(1, -24, 1, 0), Font = Enum.Font.GothamMedium
        })
        if def[2] ~= 'search' then
            text(card, 'OPEN >', 9, palette.lilac, {
                AnchorPoint = Vector2.new(1, 0), Position = UDim2.new(1, -8, 0, 0), Size = UDim2.fromOffset(40, 28),
                TextXAlignment = Enum.TextXAlignment.Right
            })
        end
        bind(card.MouseButton1Click, function()
            if def[2] == 'search' then ui.search:CaptureFocus() else show_page(def[2]) end
        end)
    end

    heading('home', 'STATUS')
    ui.home_status = status_card('home')
    heading('home', 'SEEKER CHANCE')
    ui.home_chance = status_card('home')

    heading('home', 'QUICK TOGGLES')
    toggle('home', 'Player ESP', 'Highlight players through walls', 'esp', on.esp)
    toggle('home', 'Credit ESP', 'Highlight uncollected credits', 'credit_esp', on.credit_esp)
    toggle('home', 'Anti-AFK', 'Keeps you from being idle-kicked', 'anti_afk')
end

do
    local page = pages.players
    ui.roster = text(page, 'PLAYERS', 10, palette.accent, {
        Size = UDim2.new(1, 0, 0, 18), Font = Enum.Font.GothamBold,
        TextYAlignment = Enum.TextYAlignment.Bottom, LayoutOrder = nextorder(page)
    })
    local filter = new('TextBox', {
        Size = UDim2.new(1, 0, 0, 26), BackgroundColor3 = palette.background, BorderSizePixel = 0,
        PlaceholderText = 'Filter player or display name...', PlaceholderColor3 = palette.dim,
        ClearTextOnFocus = false, Text = '', Font = Enum.Font.Gotham, TextSize = 11,
        TextColor3 = palette.text, TextXAlignment = Enum.TextXAlignment.Left, LayoutOrder = nextorder(page)
    }, page)
    round(filter, 5)
    stroke(filter)
    new('UIPadding', { PaddingLeft = UDim.new(0, 9), PaddingRight = UDim.new(0, 6) }, filter)
    local list = new('ScrollingFrame', {
        Size = UDim2.new(1, 0, 0, 124), BackgroundColor3 = palette.background, BorderSizePixel = 0,
        ScrollBarThickness = 3, ScrollBarImageColor3 = palette.accent,
        CanvasSize = UDim2.new(), AutomaticCanvasSize = Enum.AutomaticSize.Y, LayoutOrder = nextorder(page)
    }, page)
    round(list, 6)
    stroke(list)
    new('UIListLayout', { Padding = UDim.new(0, 3), SortOrder = Enum.SortOrder.LayoutOrder }, list)
    new('UIPadding', {
        PaddingTop = UDim.new(0, 4), PaddingBottom = UDim.new(0, 4),
        PaddingLeft = UDim.new(0, 4), PaddingRight = UDim.new(0, 6)
    }, list)
    ui.selected_label = text(page, 'Selected: nobody', 11, palette.text, {
        Size = UDim2.new(1, 0, 0, 18), TextTruncate = Enum.TextTruncate.AtEnd, LayoutOrder = nextorder(page)
    })

    local rows = {}
    local function priority(player)
        if isit(player) then return 0 end
        if ingame(player) then return 1 end
        return 2
    end
    local function sorted_roster()
        local roster = players:GetPlayers()
        table.sort(roster, function(a, b)
            local ap, bp = priority(a), priority(b)
            if ap ~= bp then return ap < bp end
            return string.lower(a.Name) < string.lower(b.Name)
        end)
        return roster
    end

    function ui.refresh_players()
        for _, child in ipairs(list:GetChildren()) do
            if child:IsA('TextButton') then child:Destroy() end
        end
        table.clear(rows)
        local query = string.lower(filter.Text)
        for index, player in ipairs(sorted_roster()) do
            if query == '' or string.find(string.lower(player.Name), query, 1, true) or string.find(string.lower(player.DisplayName), query, 1, true) then
                local control = new('TextButton', {
                    Size = UDim2.new(1, 0, 0, 24), BackgroundColor3 = palette.raised, BorderSizePixel = 0,
                    Text = player.DisplayName, TextSize = 10, TextColor3 = palette.text,
                    TextTruncate = Enum.TextTruncate.AtEnd, TextXAlignment = Enum.TextXAlignment.Left,
                    Font = Enum.Font.GothamMedium, LayoutOrder = index
                }, list)
                round(control, 5)
                new('UIPadding', { PaddingLeft = UDim.new(0, 8), PaddingRight = UDim.new(0, 6) }, control)
                rows[player] = control
                bind(control.MouseButton1Click, function()
                    S.selected = player
                    notify('Target set to ' .. player.Name .. '.')
                end)
            end
        end
        if S.selected and S.selected.Parent ~= players then S.selected = nil end
    end

    function ui.update_rows()
        local myroot = getroot(localplayer)
        local ordered = sorted_roster()
        local counts = { 0, 0, 0 }
        for index, player in ipairs(ordered) do
            local p = priority(player)
            counts[p + 1] += 1
            local row = rows[player]
            if row and row.LayoutOrder ~= index then row.LayoutOrder = index end
        end
        ui.roster.Text = string.format('PLAYERS  /  IT %d  -  HIDERS %d  -  LOBBY %d', counts[1], counts[2], counts[3])
        for player, control in pairs(rows) do
            if control.Parent and player.Parent == players then
                local root = getroot(player)
                local distance = myroot and root and math.floor((myroot.Position - root.Position).Magnitude + 0.5)
                control.Text = player.DisplayName .. (player == localplayer and ' (YOU)' or '') .. '  |  ' .. role_of(player) .. (distance and ('  |  ' .. distance .. ' studs') or '')
                control.TextColor3 = isit(player) and palette.danger or (ingame(player) and palette.text or palette.dim)
                control.BackgroundColor3 = player == S.selected and palette.accentdark or palette.raised
            end
        end
        if S.selected and S.selected.Parent == players then
            ui.selected_label.Text = 'Selected: ' .. S.selected.DisplayName .. ' (@' .. S.selected.Name .. ')'
        else
            ui.selected_label.Text = 'Selected: nobody'
        end
    end

    bind(filter:GetPropertyChangedSignal('Text'), ui.refresh_players)
    bind(players.PlayerAdded, ui.refresh_players)
    bind(players.PlayerRemoving, function(player)
        if S.selected == player then S.selected = nil end
        task.defer(function() if alive then ui.refresh_players() end end)
    end)

    buttons('players', {
        { 'TELEPORT TO PLAYER', function() teleport(S.selected) end, true },
        { 'NEAREST HIDER', function()
            if not isit(localplayer) then notify('Nearest hider teleport requires being It.'); return end
            teleport(nearest_hider())
        end }
    })
    buttons('players', {
        { 'SPECTATE / STOP', function()
            if S.spectating then restore_camera(); notify('Spectating stopped.'); return end
            local _, humanoid = getroot(S.selected)
            if not humanoid then notify('Choose a living player to spectate.'); return end
            S.spectating = S.selected
            if workspace.CurrentCamera then workspace.CurrentCamera.CameraSubject = humanoid end
            notify('Spectating ' .. S.selected.Name .. '.')
        end }
    })
    toggle('players', 'Auto evade It', 'Escape to a known safe spot before It reaches you', 'auto_evade', function(enabled)
        if not enabled then table.clear(F.safe_positions) end
    end)

    heading('players', 'PLAYER FLING')
    info('players', 'Spin into one player or all except It. Auto Farm movement pauses during fling and resumes afterward; server-side results are not guaranteed.', palette.muted, 10)
    local fling = {
        active = false, token = 0, power = 1300, attempts = 0, skipped = 0,
        last = 'none', mode = 'idle', original = nil, spinner = nil,
        attachment = nil, last_root = nil, last_humanoid = nil
    }
    input_row('players', 'Spin strength', 'Classic fling rotation (300-3000)', fling.power, 300, 3000, function(value)
        fling.power = math.floor(value)
    end)
    local fling_status = status_card('players')
    local function restore_fling()
        if fling.spinner then pcall(function() fling.spinner:Destroy() end) end
        if fling.attachment then pcall(function() fling.attachment:Destroy() end) end
        fling.spinner, fling.attachment = nil, nil
        local original, root, humanoid = fling.original, fling.last_root, fling.last_humanoid
        fling.original, fling.last_root, fling.last_humanoid = nil, nil, nil
        if root and root.Parent then
            pcall(function()
                root.AssemblyLinearVelocity = Vector3.zero
                root.AssemblyAngularVelocity = Vector3.zero
                if original and root == getroot(localplayer) then root.CFrame = original.cf end
            end)
        end
        if humanoid and humanoid.Parent and original then
            pcall(function()
                humanoid.PlatformStand = original.stand
                humanoid.AutoRotate = original.rotate
                if not original.stand then humanoid:ChangeState(Enum.HumanoidStateType.GettingUp) end
            end)
        end
    end
    local function stop_fling()
        fling.token += 1
        fling.active = false
        fling.mode = 'stopped'
        restore_fling()
        S.fling_active = false
    end
    local function eligible_fling_target(player)
        return player and player ~= localplayer and player.Parent == players
            and not isit(player) and getroot(player) ~= nil
    end
    local function spin_at(player, token)
        local myroot, humanoid = getroot(localplayer)
        if not myroot or not humanoid or not eligible_fling_target(player) then return false end
        fling.original = { cf = myroot.CFrame, stand = humanoid.PlatformStand, rotate = humanoid.AutoRotate }
        fling.last_root, fling.last_humanoid = myroot, humanoid
        local installed = pcall(function()
            humanoid.PlatformStand = true
            humanoid.AutoRotate = false
            local attachment = Instance.new('Attachment')
            attachment.Name = 'LunaFlingSpin'
            attachment.Parent = myroot
            fling.attachment = attachment
            local motor = Instance.new('AngularVelocity')
            motor.Name = 'LunaFlingMotor'
            motor.Attachment0 = attachment
            motor.RelativeTo = Enum.ActuatorRelativeTo.World
            motor.MaxTorque = 1000000000
            motor.AngularVelocity = Vector3.new(fling.power, fling.power, fling.power)
            motor.Parent = myroot
            fling.spinner = motor
        end)
        if not installed then restore_fling(); return false end
        local began = os.clock()
        local direction = 1
        while alive and fling.active and token == fling.token and eligible_fling_target(player)
            and os.clock() - began < 1.15 do
            local targetroot = getroot(player)
            if not targetroot or not myroot.Parent or getroot(localplayer) ~= myroot then break end
            direction = -direction
            local angle = math.random() * math.pi * 2
            local radius = 0.45 + math.random() * 1.45
            local wobble = (math.random() - 0.5) * 3.2
            local offset = Vector3.new(math.cos(angle) * radius, wobble, math.sin(angle) * radius)
            pcall(function()
                myroot.CFrame = CFrame.new(targetroot.Position + offset)
                myroot.AssemblyAngularVelocity = Vector3.new(
                    fling.power * direction, fling.power * (math.random(0, 1) == 1 and 1 or -1), fling.power * -direction)
                myroot.AssemblyLinearVelocity = Vector3.new(
                    math.random(-90, 90), math.random(-145, 145), math.random(-90, 90))
                if fling.spinner then
                    fling.spinner.AngularVelocity = myroot.AssemblyAngularVelocity
                end
            end)
            task.wait(0.035)
        end
        if token == fling.token then restore_fling() end
        return true
    end
    local function start_fling(mode)
        if fling.active then notify('A fling is already running. Use STOP FLING first.'); return end
        local targets = {}
        if mode == 'selected' then
            if not eligible_fling_target(S.selected) then
                notify('Select a living player other than It first.')
                return
            end
            table.insert(targets, S.selected)
        else
            for _, player in ipairs(players:GetPlayers()) do
                if eligible_fling_target(player) then table.insert(targets, player) end
            end
        end
        if #targets == 0 then notify('No eligible players: It and you are excluded.'); return end
        table.sort(targets, function(a, b) return a.UserId < b.UserId end)
        fling.token += 1
        local token = fling.token
        fling.active, fling.mode = true, mode
        fling.attempts, fling.skipped = 0, 0
        S.fling_active = true
        task.spawn(function()
            for _, player in ipairs(targets) do
                if not alive or not fling.active or token ~= fling.token then break end
                if eligible_fling_target(player) then
                    fling.last = player.DisplayName
                    local ok, result = pcall(spin_at, player, token)
                    if token ~= fling.token then break end
                    if not ok then restore_fling() end
                    if ok and result then fling.attempts += 1 else fling.skipped += 1 end
                    task.wait(0.16)
                else
                    fling.skipped += 1
                end
            end
            if token == fling.token then
                fling.active, fling.mode = false, 'finished'
                restore_fling()
                S.fling_active = false
                notify('Spin fling finished: ' .. fling.attempts .. ' attempts. Auto Farm resumed if enabled.')
            end
        end)
    end
    buttons('players', {
        { 'FLING SELECTED', function() start_fling('selected') end, true },
        { 'FLING ALL EXCEPT IT', function() start_fling('all') end }
    })
    buttons('players', {
        { 'STOP FLING', function() stop_fling(); notify('Fling stopped and character restored.') end }
    })
    every(0.3, function()
        fling_status.Text = 'Status: ' .. fling.mode .. ' | Running: ' .. (fling.active and 'YES' or 'NO')
            .. '\nAttempts: ' .. fling.attempts .. ' | Skipped: ' .. fling.skipped
            .. '\nLast target: ' .. fling.last .. ' | Spin: ' .. fling.power
            .. '\nAuto Farm: ' .. (S.fling_active and 'movement paused for fling' or (settings.auto_farm and 'running' or 'off'))
    end)
    table.insert(cleanups, stop_fling)
end

do
    toggle('visuals', 'Player ESP', 'Highlight players through walls; It is red', 'esp', on.esp)
    toggle('visuals', 'Name labels', 'Show name and role above characters', 'labels')
    toggle('visuals', 'Distance on labels', 'Distance from your character in studs', 'distance')
    toggle('visuals', 'Credit ESP', 'Highlight uncollected credit pickups', 'credit_esp', on.credit_esp)
    toggle('visuals', 'Closest credit line', 'On-screen line to the nearest coin', 'closest_credit_esp', on.closest_credit_esp)
    toggle('visuals', 'Fullbright', 'Bright environment, no fog (local)', 'fullbright', on.fullbright)
    toggle('visuals', 'Early It indicator', 'Show the next It above the selection UI', 'early_it')
    toggle('visuals', 'Teleporter ESP', 'Map teleporter pads and destinations', 'teleporter_esp', on.teleporter_esp)
    toggle('visuals', 'Jump pad ESP', 'Mark jump pads with green boxes', 'jump_pad_esp', on.jump_pad_esp)
    toggle('visuals', 'Seeker radius', 'Range ring and live seeker distance', 'seeker_radius_esp', on.seeker_radius_esp)
    toggle('visuals', 'Glue ESP', 'Highlight placed glue hazards', 'glue_esp', on.glue_esp)
    toggle('visuals', 'Camera ESP (experimental)', 'Mark likely seeker cameras', 'camera_esp', on.camera_esp)
    input_row('visuals', 'Seeker warning distance', 'Studs before the warning turns red (10-150)', settings.seeker_warning_distance, 10, 150, function(value)
        settings.seeker_warning_distance = math.floor(value + 0.5)
        storage.schedule()
        notify('Seeker warning distance: ' .. settings.seeker_warning_distance .. ' studs.')
    end)
    info('visuals', 'RED = IT   PURPLE = HIDER   GREY = LOBBY   CYAN = TELEPORTER', palette.dim, 9)
end

do
    toggle('movement', 'Walk speed', 'Subtle +6% by default; type a value to override', 'speed', on.speed, {
        value = move.speed, min = 0, max = 150,
        apply = function(value)
            move.subtle_speed = false
            move.speed = value
            if not settings.speed then settings.speed = true; update_toggle('speed') end
            storage.schedule()
            notify('Walk speed set to ' .. value .. '.')
        end
    })
    toggle('movement', 'Jump power', 'Subtle +6% by default; type a value to override', 'jump', on.jump, {
        value = move.jump, min = 0, max = 200,
        apply = function(value)
            move.subtle_jump = false
            move.jump = value
            if not settings.jump then settings.jump = true; update_toggle('jump') end
            storage.schedule()
            notify('Jump power set to ' .. value .. '.')
        end
    })
    toggle('movement', 'Noclip', 'Pass through collidable map parts locally', 'noclip', on.noclip)
    toggle('movement', 'Infinite jump', 'Jump again while airborne', 'infinite_jump')
    toggle('movement', 'Auto seek teleport', 'As It, teleport to the nearest hider every 2s', 'auto_seek', on.auto_seek)
    buttons('movement', {
        { 'TP TO NEAREST CREDIT', function()
            local part = find_nearest_credit(false)
            if part and F.teleport_credit(part, false) then notify('Teleported to nearby credit.')
            else notify('No available credits found.') end
        end, true },
        { 'RETURN TO OLD SPOT', function()
            local root = getroot(localplayer)
            if root and S.return_cframe then
                settings.auto_credits = false
                F.loop_origin = nil
                update_toggle('auto_credits')
                local destination = S.return_cframe
                S.return_cframe = root.CFrame
                root.AssemblyLinearVelocity = Vector3.zero
                root.CFrame = destination
                notify('Returned to previous position.')
            else
                notify('No previous position saved.')
            end
        end }
    })
end

do
    heading('farming', 'AUTOMATED COLLECTION')
    toggle('farming', 'Loop credit teleport', 'Visits available credits every 0.7 seconds', 'auto_credits', on.auto_credits)
    toggle('farming', 'Loop pull lobby players', 'Overlaps lobby players with your character', 'pull_lobby', on.pull_lobby)
    warn_card('farming', 'WARNING: VERY OBVIOUS CHEATING',
        'Loop pull moves other players over your character locally and can look very obvious. Server-side tags and credit rewards are not guaranteed.')
    ui.farm_status = info('farming', 'Credit teleports: 0  |  Lobby targets: 0  |  Pull ticks: 0', palette.muted, 10)
    ui.lobby_debug = info('farming', 'Lobby diagnostic: pending', palette.dim, 10)
    buttons('farming', { { 'DESTRUCT LUNA.DEV', function() ui.shutdown() end } })
end

do
    local auto = {
        phase = 'idle', empty = 0, passes = 0, pulls = 0, scans = 0, last_target = 'none',
        shelter = nil, shelter_origin = nil, map = nil, target = nil, target_since = 0,
        time_left = nil, time_valid = false, round_started = false, release_all = false,
        hop_attempt = false, hop_last = 0, hop_status = 'Monitoring population',
        seed = nil, seed_userid = nil, seed_captured = false, sweep_index = 0,
        lobby_index = 0,
        round_resets = 0, last_round_reset = 'Startup',
        seen_running = nil, seen_selecting = nil, seen_it = nil, seen_role = nil,
        shelter_character = nil, shelter_recovery_at = 0,
        seed_replacements = 0, seed_last_left = 'none', seed_replacement_after = 0
    }

    toggle('autofarm', 'Hop from dead servers', 'Only you in server: seek a busier server and reinject (executor queue required)', 'auto_server_hop')
    toggle('autofarm', 'Smart AFK farming', 'One initial hider, then LOBBY ONLY; hiders in final 30s', 'auto_farm', function(enabled)
        if enabled then
            for _, key in ipairs({ 'auto_credits', 'pull_lobby', 'auto_seek', 'pull_credits' }) do
                if settings[key] then
                    settings[key] = false
                    if on[key] then on[key](false) end
                end
                update_toggle(key)
            end
            auto.empty = 0
            auto.phase = 'checking new round'
            auto.map = nil
            auto.shelter_character = nil
            auto.seed = nil
            auto.seed_userid = nil
            auto.seed_captured = false
            auto.seed_replacements, auto.seed_last_left, auto.seed_replacement_after = 0, 'none', 0
            auto.time_valid = false
            auto.release_all = false
        else
            auto.phase, auto.empty, auto.target = 'idle', 0, nil
            auto.seed = nil
            auto.seed_userid = nil
            auto.release_all = false
            ui.clear_shelter(true)
        end
    end)
    info('autofarm', 'Credits > eight checks > ONE hider if lobby empty > LOBBY ONLY until final 30s', palette.muted, 10)
    info('autofarm', 'Server hops need a supported teleport-queue API. Restarting and round capture still depend on the executor and server.', palette.dim, 10)
    ui.auto_state = status_card('autofarm')
    warn_card('autofarm', 'NOTICE: HIGHLY VISIBLE / EXPERIMENTAL',
        'Client-side player pulling does not guarantee tags or credit rewards. The out-of-map resting platform is local and the server may move you back. Auto farming can be reported by other players.')

    function ui.clear_shelter(restore)
        if auto.shelter then auto.shelter:Destroy(); auto.shelter = nil end
        if restore and auto.shelter_origin then
            local root = getroot(localplayer)
            if root then root.CFrame = auto.shelter_origin end
        end
        auto.shelter_origin = nil
        auto.shelter_character = nil
        auto.shelter_recovery_at = 0
    end

    local function reset_round(reason)
        if not settings.auto_farm then return end
        auto.round_resets += 1
        auto.last_round_reset = reason
        auto.empty = 0
        auto.map = nil
        auto.target, auto.target_since = nil, 0
        auto.seed, auto.seed_userid, auto.seed_captured = nil, nil, false
        auto.seed_replacements, auto.seed_last_left, auto.seed_replacement_after = 0, 'none', 0
        auto.sweep_index, auto.lobby_index = 0, 0
        auto.time_left, auto.time_valid, auto.release_all = nil, false, false
        auto.phase = 'Round reset (' .. reason .. '): waiting for next role'
        ui.clear_shelter(false)
    end

    local function on_seed_departure(player)
        if not settings.auto_farm or not player or auto.seed_userid ~= player.UserId then return end
        auto.seed_replacements += 1
        auto.seed_last_left = player.DisplayName
        auto.seed, auto.seed_userid, auto.seed_captured = nil, nil, false
        auto.seed_replacement_after = os.clock() + 0.75
        auto.target, auto.target_since = nil, 0
        auto.phase = 'Captured target left: checking lobby or one new hider'
        notify('Auto Farm: ' .. player.DisplayName .. ' left; replacing only if the lobby is empty.')
    end
    bind(players.PlayerRemoving, on_seed_departure)

    local function is_round_ready()
        local running = gamedata:FindFirstChild('GameRunning')
        local selecting = gamedata:FindFirstChild('SelectingIt')
        if not running or not running.Value then return false, 'waiting for next round' end
        if selecting and selecting.Value then return false, 'waiting for It selection' end
        if not isit(localplayer) and not ingame(localplayer) then
            return false, 'waiting for role assignment'
        end
        return true
    end

    local function monitor_round()
        local running = gamedata:FindFirstChild('GameRunning')
        local selecting = gamedata:FindFirstChild('SelectingIt')
        local now_running = running and running.Value == true or false
        local now_selecting = selecting and selecting.Value == true or false
        local it = getit()
        local now_it = it and it.UserId or 0
        local now_role = isit(localplayer) and 'IT' or (ingame(localplayer) and 'HIDER' or 'LOBBY')
        if auto.seen_running ~= nil then
            if auto.seen_running ~= now_running then
                reset_round(now_running and 'round started' or 'round ended')
            elseif auto.seen_selecting ~= now_selecting then
                reset_round(now_selecting and 'It selection started' or 'It selection finished')
            elseif auto.seen_it ~= now_it and now_it ~= 0 then
                reset_round('new It chosen')
            elseif auto.seen_role ~= now_role and now_running then
                reset_round('your role changed to ' .. now_role)
            end
        end
        auto.seen_running, auto.seen_selecting = now_running, now_selecting
        auto.seen_it, auto.seen_role = now_it, now_role
    end

    for _, name in ipairs({ 'GameRunning', 'SelectingIt' }) do
        local signal = gamedata:FindFirstChild(name)
        if signal and signal:IsA('ValueBase') then
            bind(signal:GetPropertyChangedSignal('Value'), monitor_round)
        end
    end
    local it_data = gamedata:FindFirstChild('It')
    local it_object = it_data and it_data:FindFirstChild('PlayerObject')
    if it_object and it_object:IsA('ObjectValue') then
        bind(it_object:GetPropertyChangedSignal('Value'), monitor_round)
    end
    bind(localplayer.CharacterAdded, function()
        reset_round('character respawned')
    end)
    monitor_round()
    every(0.5, monitor_round)

    local function outside_map()
        local root = getroot(localplayer)
        local map = workspace:FindFirstChild('Map')
        if not root or not map then return false end
        local center, extents
        if map:IsA('Model') then
            center, extents = map:GetBoundingBox()
        elseif map:IsA('BasePart') then
            center, extents = map.CFrame, map.Size
        else
            return false
        end
        if extents.X > 20000 or extents.Z > 20000 then return false end
        if not auto.shelter_origin then auto.shelter_origin = root.CFrame end
        local position = Vector3.new(center.Position.X + extents.X * 0.5 + 115, center.Position.Y + math.max(20, extents.Y * 0.35), center.Position.Z)
        if not auto.shelter or not auto.shelter.Parent then
            auto.shelter = new('Part', {
                Name = 'luna_shelter',
                Anchored = true, CastShadow = false, CanCollide = true, CanTouch = false,
                Size = Vector3.new(18, 1, 18), CFrame = CFrame.new(position),
                Material = Enum.Material.SmoothPlastic, Color = palette.accentdark, Transparency = 0.5
            }, workspace)
        end
        root.AssemblyLinearVelocity = Vector3.zero
        root.CFrame = auto.shelter.CFrame + Vector3.new(0, 4, 0)
        auto.shelter_character = localplayer.Character
        auto.shelter_recovery_at = os.clock() + 3
        return true
    end

    local function credit_count()
        local objects = workspace:FindFirstChild('GameObjects')
        if not objects then return nil end
        local count = 0
        for _, part in ipairs(objects:GetChildren()) do
            if part:IsA('BasePart') and part.Name == 'Credit' then
                local collected = part:FindFirstChild('Collected')
                if not collected or not collected.Value then count += 1 end
            end
        end
        return count
    end

    local function round_seconds()
        local timer = replicatedstorage:FindFirstChild('Timer')
        local remaining = timer and timer:FindFirstChild('Time')
        local visible = timer and timer:FindFirstChild('TimeVisible')
        if not remaining or not remaining:IsA('ValueBase') then return nil end
        if visible and visible:IsA('BoolValue') and not visible.Value then return nil end
        local value = tonumber(remaining.Value)
        if not value or value < 0 or value > 86400 then return nil end
        return value
    end

    local function active_hiders()
        local result = {}
        for _, player in ipairs(players:GetPlayers()) do
            if player ~= localplayer and player.Parent == players and not isit(player) and ingame(player) and getroot(player) then
                table.insert(result, player)
            end
        end
        table.sort(result, function(a, b) return a.UserId < b.UserId end)
        return result
    end

    local function sweep_target()
        local candidates = active_hiders()
        if #candidates == 0 then return nil end
        auto.sweep_index = auto.sweep_index % #candidates + 1
        return candidates[auto.sweep_index]
    end

    local function lobby_target()
        local candidates = {}
        for _, player in ipairs(players:GetPlayers()) do
            if player ~= localplayer and player.Parent == players and not isit(player)
                and not ingame(player) and getroot(player) then
                table.insert(candidates, player)
            end
        end
        if #candidates == 0 then return nil end
        table.sort(candidates, function(a, b) return a.UserId < b.UserId end)
        auto.lobby_index = auto.lobby_index % #candidates + 1
        return candidates[auto.lobby_index]
    end

    local function queue_loader()
        local q = (type(queue_on_teleport) == 'function' and queue_on_teleport)
            or (type(queueonteleport) == 'function' and queueonteleport)
            or (type(syn) == 'table' and type(syn.queue_on_teleport) == 'function' and syn.queue_on_teleport)
            or (type(fluxus) == 'table' and type(fluxus.queue_on_teleport) == 'function' and fluxus.queue_on_teleport)
        if type(q) ~= 'function' then
            auto.hop_status = 'Executor has no teleport-queue API'
            return false
        end
        local local_path = 'luna.dev/scripts/hide_and_seek_extreme_v22.lua'
        local hosted_url = 'https://lunadev.fixseroalt.workers.dev/release/game-scripts/hide_and_seek_extreme.lua'
        local loader
        if type(readfile) == 'function' then
            local ok, content = pcall(readfile, local_path)
            if ok and type(content) == 'string' and content:find("luna.dev v22 loaded", 1, true) then
                loader = 'local s = readfile("luna.dev/scripts/hide_and_seek_extreme_v22.lua"); local f = loadstring(s); if f then f() end'
            end
        end
        if not loader then
            local ok, content = pcall(function() return game:HttpGet(hosted_url) end)
            if ok and type(content) == 'string' and content:find("luna.dev v22 loaded", 1, true) then
                loader = 'local f = loadstring(game:HttpGet("https://lunadev.fixseroalt.workers.dev/release/game-scripts/hide_and_seek_extreme.lua")); if f then f() end'
            end
        end
        if not loader then
            auto.hop_status = 'Save v22 as ' .. local_path .. ' to enable reinjection'
            return false
        end
        local ok = pcall(q, loader)
        if not ok then auto.hop_status = 'Teleport reinjection could not be queued' end
        return ok
    end

    local function fetch_public_servers(cursor)
        local url = 'https://games.roblox.com/v1/games/' .. tostring(game.PlaceId) .. '/servers/Public?sortOrder=Desc&limit=100'
        if cursor and cursor ~= '' then url = url .. '&cursor=' .. httpservice:UrlEncode(cursor) end
        local provider = (type(request) == 'function' and request)
            or (type(http_request) == 'function' and http_request)
            or (type(syn) == 'table' and type(syn.request) == 'function' and syn.request)
        local ok, body
        if provider then
            ok, body = pcall(function()
                local result = provider({ Url = url, Method = 'GET' })
                if not result or result.StatusCode and result.StatusCode ~= 200 then error('server list request failed') end
                return result.Body
            end)
        else
            ok, body = pcall(function() return game:HttpGet(url) end)
        end
        if not ok or type(body) ~= 'string' then return nil end
        local decoded, result = pcall(httpservice.JSONDecode, httpservice, body)
        return decoded and type(result) == 'table' and result or nil
    end

    local function hop_to_busier_server()
        if auto.hop_attempt or not alive or S.fling_active or not settings.auto_server_hop or not settings.auto_farm then return end
        if #players:GetPlayers() >= 2 or os.clock() - started_at < 5 then return end
        if auto.hop_last > 0 and os.clock() - auto.hop_last < 35 then return end
        auto.hop_attempt = true
        auto.hop_last = os.clock()
        auto.hop_status = 'Looking for a populated server...'
        task.spawn(function()
            local best, largest = nil, 1
            local cursor = nil
            for _ = 1, 2 do
                if not alive or not settings.auto_farm or #players:GetPlayers() >= 2 then break end
                local response = fetch_public_servers(cursor)
                if not response or type(response.data) ~= 'table' then
                    auto.hop_status = 'Server list unavailable; will retry'
                    break
                end
                for _, server in ipairs(response.data) do
                    if type(server) == 'table' and type(server.id) == 'string'
                        and server.id ~= game.JobId and type(server.playing) == 'number'
                        and type(server.maxPlayers) == 'number'
                        and server.playing >= 2 and server.playing < server.maxPlayers
                        and server.playing > largest then
                        best, largest = server.id, server.playing
                    end
                end
                if best then break end
                cursor = response.nextPageCursor
                if not cursor then break end
            end
            if best and alive and settings.auto_farm and #players:GetPlayers() < 2 then
                if not queue_loader() then
                    if auto.hop_status == 'Looking for a populated server...' then
                        auto.hop_status = 'Autoexec not ready; staying in current server'
                    end
                else
                    storage.save()
                    auto.hop_status = 'Joining server with ' .. largest .. ' players'
                    local ok, err = pcall(function()
                        teleportservice:TeleportToPlaceInstance(game.PlaceId, best, localplayer)
                    end)
                    if not ok then
                        auto.hop_status = 'Teleport failed: ' .. tostring(err)
                    end
                end
            elseif auto.hop_status == 'Looking for a populated server...' then
                auto.hop_status = 'No suitable public server found; retrying'
            end
            auto.hop_attempt = false
        end)
    end

    every(2, hop_to_busier_server)

    every(0.8, function()
        if not settings.auto_farm then return end
        if S.fling_active then auto.phase = 'Paused for player fling'; return end
        local ready, reason = is_round_ready()
        if not ready then
            auto.empty = 0
            auto.phase = reason
            if auto.shelter then ui.clear_shelter(false) end
            return
        end
        local map = workspace:FindFirstChild('Map')
        if map ~= auto.map then
            auto.map, auto.empty, auto.target, auto.phase = map, 0, nil, 'checking'
            auto.seed, auto.seed_userid, auto.seed_captured = nil, nil, false
            auto.seed_replacements, auto.seed_last_left, auto.seed_replacement_after = 0, 'none', 0
            auto.time_valid, auto.release_all = false, false
            ui.clear_shelter(true)
        end
        local count = credit_count()
        auto.scans += 1
        if count == nil then
            auto.empty = 0
            auto.phase = 'waiting for GameObjects'
        elseif count > 0 then
            auto.empty = 0
            auto.phase = 'credits (' .. count .. ' available)'
            ui.clear_shelter(true)
            local part = find_nearest_credit(true)
            if part and F.teleport_credit(part, true) then auto.passes += 1 end
        else
            auto.empty = math.min(8, auto.empty + 1)
            auto.phase = auto.empty < 8 and ('confirming no credits (' .. auto.empty .. '/8)') or 'no credits confirmed'
        end
    end)

    every(0.06, function()
        if not settings.auto_farm or auto.empty < 8 or S.fling_active then return end
        local ready, reason = is_round_ready()
        if not ready then
            auto.phase = reason
            if auto.shelter then ui.clear_shelter(false) end
            return
        end
        local running = gamedata:FindFirstChild('GameRunning')
        local seeking = gamedata:FindFirstChild('ItSeeking')
        if not running or not running.Value then
            auto.phase = 'waiting for a round'
            auto.seed, auto.seed_userid, auto.target = nil, nil, nil
            auto.time_valid, auto.release_all = false, false
        elseif isit(localplayer) and seeking and seeking.Value then
            ui.clear_shelter(false)
            local now = os.clock()
            local remaining = round_seconds()
            auto.time_left = remaining
            if remaining and remaining > 0 then auto.time_valid = true end
            auto.release_all = auto.time_valid and remaining ~= nil and remaining <= 30
            if auto.release_all then
                auto.phase = 'FINAL 30 SECONDS: remaining hiders eligible'
                if not auto.target or auto.target.Parent ~= players or not ingame(auto.target)
                    or now - auto.target_since >= 0.75 then
                    auto.target = sweep_target()
                    auto.target_since = now
                end
            else
                if auto.seed_userid and auto.seed and auto.seed.Parent ~= players then
                    on_seed_departure(auto.seed)
                end
                if auto.seed_userid == nil and os.clock() >= auto.seed_replacement_after then
                    local lobby_available = false
                    for _, player in ipairs(players:GetPlayers()) do
                        if player ~= localplayer and player.Parent == players
                            and not isit(player) and not ingame(player) and getroot(player) then
                            lobby_available = true
                            break
                        end
                    end
                    if not lobby_available then
                        local hiders = active_hiders()
                        if #hiders > 0 then
                            auto.seed = hiders[1]
                            auto.seed_userid = auto.seed.UserId
                            auto.seed_captured = false
                        end
                    end
                end
                local seed = auto.seed
                if seed and seed.Parent == players and seed.UserId == auto.seed_userid
                    and not ingame(seed) then
                    auto.seed_captured = true
                end
                if not auto.seed_captured and seed and ingame(seed) and getroot(seed) then
                    auto.target = seed
                    auto.phase = 'FIRST hider only: ' .. seed.DisplayName
                else
                    auto.target = lobby_target()
                    auto.phase = auto.target and 'LOBBY ONLY: remaining hiders protected'
                        or 'Waiting for a lobby target; other hiders protected'
                end
                if not remaining then
                    auto.phase = auto.phase .. ' (round timer unavailable)'
                end
            end
            if auto.target then
                local eligible = auto.release_all and ingame(auto.target)
                    or (not auto.release_all and
                        ((auto.seed_userid ~= nil and not auto.seed_captured
                            and auto.target.UserId == auto.seed_userid and ingame(auto.target))
                        or (not ingame(auto.target))))
                if eligible and F.pull_player(auto.target) then
                    auto.pulls += 1
                    auto.last_target = auto.target.DisplayName
                end
            end
        elseif ingame(localplayer) and not isit(localplayer) then
            local character = localplayer.Character
            if auto.shelter_character ~= character then
                ui.clear_shelter(false)
            end
            if not auto.shelter or not auto.shelter.Parent then
                auto.phase = outside_map() and 'hider: local out-of-map shelter' or 'hider: shelter unavailable'
            else
                auto.phase = 'hider: local out-of-map shelter'
                if os.clock() >= auto.shelter_recovery_at then
                    auto.shelter_recovery_at = os.clock() + 3
                    local root = getroot(localplayer)
                    if root and (root.Position - auto.shelter.Position).Magnitude > 14 then
                        root.AssemblyLinearVelocity = Vector3.zero
                        root.CFrame = auto.shelter.CFrame + Vector3.new(0, 4, 0)
                        auto.phase = 'hider: shelter position recovered'
                    end
                end
            end
        else
            auto.phase = 'waiting for It to start seeking'
            auto.seed = nil
            auto.seed_userid = nil
            auto.time_valid, auto.release_all = false, false
        end
    end)

    every(0.3, function()
        ui.auto_state.Text = string.format('Status: %s\nRound: %s  |  Players: %d  |  Sweep: %s\nScans: %d  |  Empty: %d/8  |  Credit TPs: %d\nPull updates: %d  |  Last: %s\nTarget departures: %d  |  Last left: %s\nRound resets: %d (%s)\nServer: %s',
            (S.fling_active and settings.auto_farm) and 'Paused for player fling' or auto.phase, auto.time_left and (math.floor(auto.time_left) .. 's') or 'unavailable',
            #players:GetPlayers(), auto.release_all and 'FINAL 30s' or 'LOBBY ONLY',
            auto.scans, auto.empty, auto.passes, auto.pulls, auto.last_target,
            auto.seed_replacements, auto.seed_last_left,
            auto.round_resets, auto.last_round_reset, auto.hop_status)
    end)

    table.insert(cleanups, function() ui.clear_shelter(false) end)
end

do
    heading('abilities', 'IT ABILITIES')
    toggle('abilities', 'No local cooldown', 'Clears the client timer; server cooldown may remain', 'it_no_cooldown', on.it_no_cooldown)
    toggle('abilities', 'Keep ability active', 'Re-uses your It ability while seeking', 'it_keep_active', on.it_keep_active)
    ui.ability_status = status_card('abilities')
    buttons('abilities', {
        { 'ACTIVATE ABILITY', function()
            if settings.it_no_cooldown then A.clear_cooldown() end
            A.press()
            notify('Ability input: ' .. A.last_result .. '. Server activation is not confirmed.')
        end, true },
        { 'RESET CLIENT TIMER', function()
            if not A.can_use() then notify('Reset needs your seeker character during the seeking phase.')
            elseif A.clear_cooldown() then notify('Client timer cleared. Server cooldowns may still apply.')
            else notify('Client timer clear failed; check game state or the ability UI.') end
        end }
    })
    warn_card('abilities', 'EXPERIMENTAL / CLIENT SIDE',
        'Clears the AbilityHandler timer locally. Server-side cooldowns and durations remain enforced, and Sprint is never spam-toggled.')
    heading('abilities', 'PRACTICE AS A HIDER')
    info('abilities', 'Local previews. They run without being It and never activate server abilities.', palette.muted, 10)
    buttons('abilities', {
        { 'PRACTICE SPRINT', A.practice_sprint, true },
        { 'PRACTICE GLUE', A.practice_glue }
    })
    buttons('abilities', {
        { 'PRACTICE CAMERA / EXIT', A.practice_camera_toggle },
        { 'PRACTICE SMASH', A.practice_smash }
    })

    every(0.2, function()
        if settings.it_no_cooldown then
            A.bridge()
            A.clear_cooldown()
        end
    end)

    every(1, function()
        local character = localplayer.Character
        if character ~= A.last_character then
            A.last_character = character
            A.sprint_first = nil
            A.active, A.active_known = false, false
        end
        if settings.it_no_cooldown or settings.it_keep_active then A.bridge() end
        if settings.it_no_cooldown then A.clear_cooldown() end
        if settings.it_keep_active and A.can_use() then
            A.name = A.equipped_name()
            local is_sprint = string.lower(A.name) == 'sprint'
            local should_press = not A.active
            if is_sprint and not A.active_known and A.sprint_first == character then should_press = false end
            if should_press and os.clock() - A.last_attempt > (is_sprint and 4 or 2.3) then
                A.last_attempt = os.clock()
                if is_sprint then A.sprint_first = character end
                A.press()
            end
        elseif not A.can_use() then
            A.name = 'Unknown'
        end
    end)
end

do
    local purchase = {
        auto = false, running = false, queued = false, cancelled = false,
        bought = 0, spent = 0, failed = 0, scanned = 0, unconfirmed = 0,
        status = 'Ready. Purchases can deduct credits without awarding an item.', last_balance = nil
    }
    heading('shop', 'CREDIT AUTO BUYER')
    info('shop', 'Characters > Taunts > Pets, cheapest first. In-game credits only.', palette.accent, 10)
    local balance_label = info('shop', 'Balance: checking...', palette.text, 11)
    local scan_label = info('shop', 'Shop: not scanned', palette.muted, 10)
    local status_label = info('shop', purchase.status, palette.text, 10)
    local stats_label = info('shop', '', palette.muted, 10)
    local warning_label = warn_card('shop', 'CREDIT PURCHASE WARNING',
        'Purchases may deduct credits without granting items. Auto Buy starts OFF. Both purchase buttons need a second click to confirm.')

    local pending_action, pending_expires = nil, 0
    local function acknowledge(action)
        if pending_action == action and os.clock() < pending_expires then
            pending_action = nil
            warning_label.Text = 'Spending acknowledged. Inventory is checked after each purchase.'
            return true
        end
        pending_action = action
        pending_expires = os.clock() + 10
        warning_label.Text = 'CAUTION: credits may be deducted without the item appearing. Click the same option again within 10 seconds to proceed.'
        notify('Purchase warning: click again within 10 seconds to confirm.')
        return false
    end

    local auto_button
    local function update_display()
        if not alive then return end
        local data = localplayer:FindFirstChild('PlayerData')
        local credit = data and data:FindFirstChild('Credits')
        balance_label.Text = 'Balance: ' .. (credit and tostring(credit.Value) or 'unavailable') .. ' credits'
        scan_label.Text = 'Found ' .. purchase.scanned .. ' credit-priced shop items'
        status_label.Text = purchase.status
        stats_label.Text = 'Confirmed: ' .. purchase.bought .. '  |  Spent: ' .. purchase.spent .. '  |  Unconfirmed: ' .. purchase.unconfirmed .. '  |  Failed: ' .. purchase.failed
        if auto_button then
            auto_button.Text = 'AUTO BUY: ' .. (purchase.auto and 'ON' or 'OFF')
            auto_button.BackgroundColor3 = purchase.auto and palette.accentdark or palette.raised
        end
    end

    local function scan_shop()
        local maingui = playergui:FindFirstChild('MainGui')
        local shopframe = maingui and maingui:FindFirstChild('ShopFrame')
        local results, seen = {}, {}
        if not shopframe then return results, false end
        local kinds = {
            CharacterCredits = { 'Character', 'Characters', 'CharacterName', 1 },
            TauntCredits = { 'Taunt', 'Taunts', 'TauntName', 2 },
            PetCredits = { 'Pet', 'Pets', 'PetName', 3 }
        }
        local has_scroller = false
        for _, main_name in ipairs({ 'MainFrame', 'XBoxMainFrame' }) do
            local main = shopframe:FindFirstChild(main_name)
            local scrolling = main and main:FindFirstChild('ScrollingFrame')
            if scrolling then
                has_scroller = true
                for _, element in ipairs(scrolling:GetDescendants()) do
                    local data = element:FindFirstChild('Data')
                    if data then
                        local entry_type = data:FindFirstChild('Type')
                        local kind = entry_type and kinds[entry_type.Value]
                        if kind then
                            local name_obj = data:FindFirstChild(kind[3])
                            local price_obj = data:FindFirstChild('Prize')
                            local name = name_obj and tostring(name_obj.Value) or ''
                            local price = price_obj and tonumber(price_obj.Value)
                            local key = kind[1] .. ':' .. name
                            if name ~= '' and price and price >= 0 and price < 1000000000 and not seen[key] then
                                seen[key] = true
                                table.insert(results, { category = kind[1], bag = kind[2], name = name, price = price, priority = kind[4] })
                            end
                        end
                    end
                end
            end
        end
        table.sort(results, function(a, b)
            if a.priority ~= b.priority then return a.priority < b.priority end
            if a.price ~= b.price then return a.price < b.price end
            return a.name < b.name
        end)
        return results, has_scroller
    end

    local function start_purchase()
        if not alive or purchase.running then return end
        purchase.running = true
        purchase.cancelled = false
        task.spawn(function()
            local items, has_shop = scan_shop()
            purchase.scanned = #items
            local data = localplayer:FindFirstChild('PlayerData')
            local credit = data and data:FindFirstChild('Credits')
            local inventory = localplayer:FindFirstChild('Inventory')
            local remotes = replicatedstorage:FindFirstChild('Remotes')
            local remote = remotes and remotes:FindFirstChild('PurchaseItem')
            if not has_shop or not credit or not inventory or not remote or not remote:IsA('RemoteFunction') then
                purchase.status = 'Shop, inventory, credits or purchase remote not ready.'
                purchase.running = false
                update_display()
                return
            end
            local successful, pending = 0, 0
            for _, item in ipairs(items) do
                if not alive or purchase.cancelled then break end
                local bag = inventory:FindFirstChild(item.bag)
                if bag and not bag:FindFirstChild(item.name) then
                    local balance = tonumber(credit.Value)
                    if balance and balance >= item.price then
                        purchase.status = 'Buying ' .. item.category .. ': ' .. item.name .. ' (' .. item.price .. ' Cr)'
                        update_display()
                        local before = balance
                        local ok, result = pcall(function() return remote:InvokeServer(item.name, item.category) end)
                        if not alive then break end
                        if ok and result then
                            local expiration = os.clock() + 3
                            while alive and not purchase.cancelled and not bag:FindFirstChild(item.name) and os.clock() < expiration do
                                task.wait(0.1)
                            end
                            if bag:FindFirstChild(item.name) then
                                purchase.bought += 1
                                successful += 1
                                purchase.spent += math.max(0, before - (tonumber(credit.Value) or before))
                            else
                                pending += 1
                                purchase.status = 'Server accepted ' .. item.name .. '; inventory not yet confirmed'
                            end
                        else
                            purchase.failed += 1
                            purchase.status = 'Purchase declined: ' .. item.name
                        end
                        local charged = math.max(0, before - (tonumber(credit.Value) or before))
                        if charged > 0 and not bag:FindFirstChild(item.name) then
                            purchase.unconfirmed += 1
                            purchase.spent += charged
                            purchase.auto = false
                            settings.auto_buy = false
                            purchase.cancelled = true
                            purchase.status = 'WARNING: spent ' .. charged .. ' credits on ' .. item.name .. ' but ownership was not confirmed. Purchases stopped.'
                            warning_label.Text = 'WARNING: credits were deducted but ownership is not confirmed. Buying stopped to prevent further losses.'
                            storage.schedule()
                            update_display()
                            break
                        end
                        update_display()
                        task.wait(0.25)
                    end
                end
            end
            purchase.running = false
            if alive then
                if purchase.cancelled then
                    purchase.status = purchase.unconfirmed > 0 and purchase.status or 'Purchase run stopped.'
                else
                    purchase.status = 'Finished: ' .. successful .. ' confirmed, ' .. pending .. ' pending. Other items may be unaffordable or rejected.'
                end
                update_display()
                if purchase.auto and purchase.queued and not purchase.cancelled then
                    purchase.queued = false
                    task.delay(0.5, function()
                        if alive and purchase.auto and not purchase.running then start_purchase() end
                    end)
                else
                    purchase.queued = false
                end
            end
        end)
    end

    local made = buttons('shop', {
        { 'BUY AFFORDABLE NOW', function()
            if not acknowledge('buy') then return end
            if purchase.running then notify('A purchase run is already active.')
            else purchase.queued = false; start_purchase() end
        end, true },
        { 'AUTO BUY: OFF', function()
            if not purchase.auto and not acknowledge('auto') then return end
            purchase.auto = not purchase.auto
            settings.auto_buy = purchase.auto
            storage.schedule()
            if purchase.auto then
                purchase.cancelled = false
                if purchase.running then purchase.queued = true else start_purchase() end
            else
                purchase.queued = false
                if purchase.running then
                    purchase.cancelled = true
                    purchase.status = 'Auto buy disabled; finishing current request.'
                end
            end
            update_display()
        end }
    })
    auto_button = made[2]
    buttons('shop', {
        { 'STOP PURCHASES', function()
            purchase.auto = false
            settings.auto_buy = false
            storage.schedule()
            purchase.queued = false
            purchase.cancelled = true
            purchase.status = 'Stopping after the current server request...'
            update_display()
        end }
    })
    info('shop', 'Skips gamepasses, developer products and owned items. Auto buy re-checks whenever you earn credits.', palette.dim, 9)
    update_display()

    task.spawn(function()
        local data = localplayer:WaitForChild('PlayerData', 12)
        local credit = data and data:WaitForChild('Credits', 12)
        if not alive or not credit then return end
        purchase.last_balance = tonumber(credit.Value)
        bind(credit:GetPropertyChangedSignal('Value'), function()
            if not alive then return end
            local current = tonumber(credit.Value)
            local increased = current and purchase.last_balance and current > purchase.last_balance
            purchase.last_balance = current
            update_display()
            if purchase.auto and increased then
                if purchase.running then
                    purchase.queued = true
                else
                    task.delay(0.35, function()
                        if alive and purchase.auto and not purchase.running then start_purchase() end
                    end)
                end
            end
        end)
        update_display()
    end)
end

do
    heading('tests', 'SEEKER CHANCE MONITOR')
    local chance_status = status_card('tests')
    info('tests', 'This is the client-calculated display chance, not verified server odds. A local multiplier can make the UI show 83% without changing who the server chooses.', palette.muted, 10)
    local preview = { value = 2, active = false, original = nil, instance = nil }
    local function multiplier_object()
        local data = localplayer:FindFirstChild('PlayerData')
        local obj = data and data:FindFirstChild('ItWeightMultiplier')
        if obj and obj:IsA('ValueBase') and type(obj.Value) == 'number' then return obj end
        return nil
    end
    local function reset_preview()
        if preview.active and preview.instance and preview.instance.Parent and preview.original ~= nil then
            pcall(function() preview.instance.Value = preview.original end)
        end
        preview.active, preview.original, preview.instance = false, nil, nil
    end
    info('tests', 'EXPERIMENTAL: The value below only changes the local client copy. It can change the game UI percentage without changing your server-controlled chance.', palette.danger, 10)
    input_row('tests', 'Client multiplier preview', '0.1–20x; not a server-side boost', 2, 0.1, 20, function(value)
        preview.value = value
    end)
    buttons('tests', {
        { 'APPLY LOCAL TEST', function()
            local obj = multiplier_object()
            if not obj then notify('ItWeightMultiplier is not available on your client.'); return end
            if preview.active and preview.instance ~= obj then reset_preview() end
            if not preview.active then
                preview.original = obj.Value
                preview.instance = obj
            end
            local ok = pcall(function() obj.Value = preview.value end)
            if ok then
                preview.active = true
                notify('Client UI preview: x' .. tostring(preview.value) .. '. Server chance is unchanged unless the server explicitly accepts it.')
            else
                notify('Experimental local multiplier could not be changed.')
            end
        end },
        { 'RESET LOCAL TEST', function()
            reset_preview()
            notify('Restored the pre-test client multiplier.')
        end }
    })
    every(0.6, function()
        if preview.active and (not preview.instance or not preview.instance.Parent) then
            preview.active, preview.original, preview.instance = false, nil, nil
        end
        local data = localplayer:FindFirstChild('PlayerData')
        local weight_obj = data and data:FindFirstChild('ItWeight')
        local multi_obj = multiplier_object()
        local afk_obj = data and data:FindFirstChild('AFK')
        local weight = weight_obj and tonumber(weight_obj.Value)
        local multiplier = multi_obj and tonumber(multi_obj.Value)
        local afk_known = afk_obj and afk_obj:IsA('BoolValue')
        local is_afk = afk_known and afk_obj.Value or false
        local eligible, combined, missing = 0, 0, 0
        for _, player in ipairs(players:GetPlayers()) do
            local pdata = player:FindFirstChild('PlayerData')
            local pw = pdata and pdata:FindFirstChild('ItWeight')
            local pm = pdata and pdata:FindFirstChild('ItWeightMultiplier')
            local pa = pdata and pdata:FindFirstChild('AFK')
            local wn, mn = pw and tonumber(pw.Value), pm and tonumber(pm.Value)
            if not wn or not mn or not pa or not pa:IsA('BoolValue') then
                missing += 1
            elseif not pa.Value then
                eligible += 1
                combined += math.max(0, wn * mn)
            end
        end
        local estimate
        if missing > 0 or weight == nil or multiplier == nil or not afk_known then
            estimate = 'Loading selection data'
        elseif is_afk then
            estimate = '0.00% (game AFK flag is ON)'
        elseif combined <= 0 then
            estimate = 'Unavailable (zero total weight)'
        else
            estimate = string.format('%.2f%%', math.clamp(weight * multiplier / combined * 100, 0, 100))
        end
        local state = not afk_known and 'UNKNOWN' or (is_afk and 'AFK: INELIGIBLE' or 'ELIGIBLE')
        local weight_text = weight and string.format('%.2f', weight) or '?'
        local multiplier_text = multiplier and string.format('%.2fx', multiplier) or '?'
        local preview_text = preview.active and ('ON (local x' .. tostring(preview.value) .. ')') or 'OFF'
        local msg = 'CLIENT DISPLAY estimate: ' .. estimate .. ' | SERVER chance: UNKNOWN\n'
            .. 'Game AFK flag: ' .. state .. ' | ItWeight: ' .. weight_text .. ' | Multiplier: ' .. multiplier_text .. '\n'
            .. 'Candidates: ' .. tostring(eligible) .. ' | Missing player data: ' .. tostring(missing) .. '\n'
            .. 'Experimental preview: ' .. preview_text .. ' | A missed selection does not prove the odds'
        chance_status.Text = msg
        if ui.home_chance then ui.home_chance.Text = msg end
    end)
    table.insert(cleanups, reset_preview)
end

do
    heading('tests', 'LOCAL EXPERIMENTS')
    toggle('tests', 'Pull credits to me', 'Holds one coin at you for 1.15s (local test)', 'pull_credits', on.pull_credits)
    warn_card('tests', 'CLIENT-ONLY TEST: NOT CONFIRMED REWARDS',
        'Credit pickups may disappear locally without awarding coins. Server-authoritative rewards and gamepasses cannot be granted by moving client-side copies.')
    local owned_label = info('tests', 'Triple Coin Value (pass 592144): not checked', palette.muted, 10)
    ui.test_stats = info('tests', 'Coin pull attempts: 0 | Credits: +0', palette.muted, 10)

    local running_equip = false
    local function discover_gamepass_items()
        local main = playergui:FindFirstChild('MainGui')
        local shop = main and main:FindFirstChild('ShopFrame')
        if not shop then return {} end
        local items, recorded = {}, {}
        local remote_for = { CharacterGamepass = 'EquipCharacter', TauntGamepass = 'EquipTaunt', PetGamepass = 'EquipPet' }
        local name_for = { CharacterGamepass = 'CharacterName', TauntGamepass = 'TauntName', PetGamepass = 'PetName' }
        for _, section in ipairs({ 'MainFrame', 'XBoxMainFrame' }) do
            local frame = shop:FindFirstChild(section)
            local scroller = frame and frame:FindFirstChild('ScrollingFrame')
            if scroller then
                for _, item in ipairs(scroller:GetDescendants()) do
                    local data = item:FindFirstChild('Data')
                    local type_value = data and data:FindFirstChild('Type')
                    local kind = type_value and type(type_value.Value) == 'string' and type_value.Value
                    if kind and remote_for[kind] then
                        local name_value = data:FindFirstChild(name_for[kind])
                        if name_value and name_value.Value ~= '' then
                            local id = remote_for[kind] .. ':' .. tostring(name_value.Value)
                            if not recorded[id] then
                                recorded[id] = true
                                table.insert(items, { name = name_value.Value, remote = remote_for[kind] })
                            end
                        end
                    end
                end
            end
        end
        return items
    end

    buttons('tests', {
        { 'CHECK 3X OWNERSHIP', function()
            notify('Checking gamepass ownership...')
            task.spawn(function()
                local ok, result = pcall(function() return marketplace:UserOwnsGamePassAsync(localplayer.UserId, 592144) end)
                if not alive then return end
                owned_label.Text = 'Triple Coin Value: ' .. (ok and (result and 'OWNED' or 'NOT OWNED') or 'check unavailable')
                notify(ok and 'Gamepass ownership check complete.' or 'Could not check gamepass ownership.')
            end)
        end },
        { 'TRY GAMEPASS EQUIPS', function()
            if running_equip then notify('An equip test is already running.'); return end
            local items = discover_gamepass_items()
            if #items == 0 then notify('No gamepass entries found in the shop UI.'); return end
            local remotes = replicatedstorage:FindFirstChild('Remotes')
            if not remotes then notify('Game equip remotes are unavailable.'); return end
            running_equip = true
            notify('Sending normal equip requests. Non-owned gamepasses are not granted.')
            task.spawn(function()
                local attempted = 0
                for i, entry in ipairs(items) do
                    if not alive or i > 16 then break end
                    local remote = remotes:FindFirstChild(entry.remote)
                    if remote and remote:IsA('RemoteEvent') then
                        if pcall(function() remote:FireServer(entry.name) end) then attempted += 1 end
                    end
                    task.wait(0.4)
                end
                running_equip = false
                if alive then notify('Sent ' .. attempted .. ' equip requests. Ownership is server-controlled.') end
            end)
        end }
    })
end

do
    heading('config', 'SAVED PREFERENCES')
    storage.label = info('config', storage.state, palette.muted, 10)
    buttons('config', {
        { 'SAVE SETTINGS NOW', function()
            if storage.save() then notify('Saved all luna.dev settings.')
            else notify('Could not save: executor file access may be unavailable.') end
        end, true },
        { 'RESET TO DEFAULTS', function()
            for key, value in pairs(storage.defaults) do
                if settings[key] ~= value then
                    settings[key] = value
                    if on[key] then on[key](value) end
                end
                update_toggle(key)
            end
            move.subtle_speed, move.subtle_jump = true, true
            storage.schedule()
            notify('Settings reset to defaults.')
        end }
    })
    heading('config', 'SYSTEM')
    toggle('config', 'Anti-AFK', 'Sends a harmless input when you go idle', 'anti_afk')
    ui.config_afk = info('config', 'Anti-AFK pulses sent: 0', palette.muted, 10)
    info('config', 'Auto-save covers every switch, the warning distance and the movement values. Right Shift hides or shows the menu.', palette.dim, 10)
    info('config', 'Storage: ' .. storage.path, palette.dim, 9)
end

local selecting_it = gamedata:FindFirstChild('SelectingIt')
local it_record = gamedata:FindFirstChild('It')
local it_object = it_record and it_record:FindFirstChild('PlayerObject')
local last_seen_it = it_object and it_object.Value
local early_candidate

local function refresh_early_it()
    if not settings.early_it or not selecting_it or not selecting_it.Value then
        early_banner.Visible = false
        return
    end
    early_banner.Visible = true
    if early_candidate and early_candidate.Parent == players then
        early_text.Text = 'NEXT IT: ' .. early_candidate.DisplayName .. ' (@' .. early_candidate.Name .. ')'
        early_text.TextColor3 = palette.gold
    else
        early_text.Text = 'SELECTING IT  |  Awaiting server choice'
        early_text.TextColor3 = palette.muted
    end
end

if selecting_it then
    bind(selecting_it:GetPropertyChangedSignal('Value'), function()
        early_candidate = nil
        if selecting_it.Value and it_object and it_object.Value and it_object.Value ~= last_seen_it then
            early_candidate = it_object.Value
        end
        refresh_early_it()
    end)
end
if it_object then
    bind(it_object:GetPropertyChangedSignal('Value'), function()
        if selecting_it and selecting_it.Value and it_object.Value and it_object.Value:IsA('Player') then
            early_candidate = it_object.Value
        end
        last_seen_it = it_object.Value
        refresh_early_it()
    end)
end

local function watch_it_flag(player)
    task.spawn(function()
        local data = player:FindFirstChild('PlayerData') or player:WaitForChild('PlayerData', 10)
        local it_flag = data and (data:FindFirstChild('It') or data:WaitForChild('It', 10))
        if not alive or player.Parent ~= players or not it_flag then return end
        bind(it_flag:GetPropertyChangedSignal('Value'), function()
            if selecting_it and selecting_it.Value and it_flag.Value == true then
                early_candidate = player
                refresh_early_it()
            end
        end)
    end)
end
for _, player in ipairs(players:GetPlayers()) do watch_it_flag(player) end
bind(players.PlayerAdded, watch_it_flag)

do
    local function reveal(entry)
        show_page(entry.page)
        task.delay(0.05, function()
            local page = pages[entry.page]
            if not alive or not page or not entry.row.Parent then return end
            page.CanvasPosition = Vector2.new(0, math.max(0, entry.row.AbsolutePosition.Y - page.AbsolutePosition.Y + page.CanvasPosition.Y - 4))
        end)
    end

    local function update_results()
        local query = string.lower(ui.search.Text):match('^%s*(.-)%s*$')
        for _, child in ipairs(ui.results:GetChildren()) do
            if child:IsA('GuiObject') then child:Destroy() end
        end
        if query == '' or S.minimized then
            ui.results.Visible = false
            return
        end
        ui.log_panel.Visible = false
        ui.results.Visible = true
        local hits = 0
        for _, entry in ipairs(search_index) do
            if hits >= 25 then break end
            if string.find(string.lower(entry.title), query, 1, true) or string.find(string.lower(entry.desc), query, 1, true) then
                hits += 1
                local item = new('TextButton', {
                    Size = UDim2.new(1, 0, 0, 26), BackgroundColor3 = palette.card, BorderSizePixel = 0,
                    Text = string.upper(entry.page) .. '  /  ' .. entry.title, TextColor3 = palette.text,
                    TextSize = 10, TextTruncate = Enum.TextTruncate.AtEnd, TextXAlignment = Enum.TextXAlignment.Left,
                    Font = Enum.Font.GothamMedium, ZIndex = 41, LayoutOrder = hits
                }, ui.results)
                round(item, 5)
                new('UIPadding', { PaddingLeft = UDim.new(0, 8) }, item)
                bind(item.MouseButton1Click, function()
                    ui.search.Text = ''
                    reveal(entry)
                end)
            end
        end
        if hits == 0 then
            text(ui.results, 'No feature matches "' .. ui.search.Text .. '".', 11, palette.muted, {
                Size = UDim2.new(1, 0, 0, 26), ZIndex = 41
            })
        end
    end
    bind(ui.search:GetPropertyChangedSignal('Text'), update_results)
end

function ui.shutdown()
    if not alive then return end
    storage.revision += 1
    storage.save()
    alive = false
    for _, connection in ipairs(connections) do connection:Disconnect() end
    for _, fn in ipairs(cleanups) do pcall(fn) end
    early_gui:Destroy()
    gui:Destroy()
end

bind(close_button.MouseButton1Click, ui.shutdown)
bind(min_button.MouseButton1Click, function()
    S.minimized = not S.minimized
    window.Size = S.minimized and UDim2.fromOffset(win_w, HEADER) or UDim2.fromOffset(win_w, win_h)
    sidebar.Visible = not S.minimized
    content.Visible = not S.minimized
    min_button.Text = S.minimized and '+' or '-'
    ui.results.Visible = false
    ui.log_panel.Visible = false
    show_page(current_page)
end)

local dragging, drag_origin, window_origin = false, nil, nil
bind(header.InputBegan, function(input)
    if input.UserInputType == Enum.UserInputType.MouseButton1 or input.UserInputType == Enum.UserInputType.Touch then
        dragging = true
        drag_origin = input.Position
        window_origin = window.Position
    end
end)
bind(userinputservice.InputChanged, function(input)
    if dragging and (input.UserInputType == Enum.UserInputType.MouseMovement or input.UserInputType == Enum.UserInputType.Touch) then
        local delta = input.Position - drag_origin
        window.Position = UDim2.new(window_origin.X.Scale, window_origin.X.Offset + delta.X, window_origin.Y.Scale, window_origin.Y.Offset + delta.Y)
    end
end)

local resizing, resize_start, resize_w, resize_h = false, nil, 0, 0
local resize_handle = new('TextButton', {
    Name = 'ResizeHandle', Position = UDim2.new(1, -18, 1, -18), Size = UDim2.fromOffset(16, 16),
    Text = '', BackgroundTransparency = 1, BorderSizePixel = 0, ZIndex = 180
}, window)
for _, offset in ipairs({ 5, 9, 13 }) do
    new('Frame', {
        Size = UDim2.fromOffset(offset, 1), Position = UDim2.fromOffset(15 - offset, offset + 1),
        Rotation = -45, BackgroundColor3 = palette.accent, BorderSizePixel = 0, ZIndex = 180
    }, resize_handle)
end
bind(resize_handle.InputBegan, function(input)
    if S.minimized then return end
    if input.UserInputType == Enum.UserInputType.MouseButton1 or input.UserInputType == Enum.UserInputType.Touch then
        resizing = true
        resize_start = input.Position
        resize_w, resize_h = win_w, win_h
    end
end)
bind(userinputservice.InputChanged, function(input)
    if resizing and (input.UserInputType == Enum.UserInputType.MouseMovement or input.UserInputType == Enum.UserInputType.Touch) then
        local delta = input.Position - resize_start
        win_w = math.clamp(resize_w + delta.X * 2, 500, 980)
        win_h = math.clamp(resize_h + delta.Y * 2, 300, 780)
        window.Size = UDim2.fromOffset(win_w, win_h)
    end
end)
bind(userinputservice.InputEnded, function(input)
    if input.UserInputType == Enum.UserInputType.MouseButton1 or input.UserInputType == Enum.UserInputType.Touch then
        dragging = false
        resizing = false
    end
end)

bind(userinputservice.InputBegan, function(input, processed)
    if not processed and input.KeyCode == Enum.KeyCode.RightShift then
        window.Visible = not window.Visible
    end
end)

bind(gui.AncestryChanged, function(_, parent)
    if not parent then ui.shutdown() end
end)

do
    local frames, frame_time = 0, 0
    local function ping()
        local ok, value = pcall(function() return statsservice.Network.ServerStatsItem['Data Ping']:GetValue() end)
        return ok and math.floor(value + 0.5) or nil
    end
    bind(runservice.Heartbeat, function(dt)
        frames += 1
        frame_time += dt
        if frame_time >= 0.5 then
            local fps = math.floor(frames / frame_time + 0.5)
            local p = ping()
            ui.stats.Text = 'FPS ' .. fps .. ' / ' .. (p and (p .. 'ms') or '--')
            frames, frame_time = 0, 0
        end
    end)
end

local function format_time(seconds)
    seconds = math.floor(seconds)
    return string.format('%dh %02dm', seconds // 3600, (seconds % 3600) // 60)
end

every(0.15, function()
    if settings.fullbright then
        lighting.Brightness = 3
        lighting.ClockTime = 14
        lighting.FogEnd = 100000
        lighting.GlobalShadows = false
        lighting.Ambient = Color3.fromRGB(210, 210, 220)
        lighting.OutdoorAmbient = Color3.fromRGB(210, 210, 220)
    end
    V.sync_players()
    ui.update_rows()
    refresh_early_it()
    V.update_seeker_radius()
    V.update_credit_direction()

    
    local running = gamedata:FindFirstChild('GameRunning')
    local seeking = gamedata:FindFirstChild('ItSeeking')
    local it = getit()
    local phase = (not running or not running.Value) and 'INTERMISSION' or (seeking and seeking.Value and 'SEEKING' or 'HIDING')
    local role = isit(localplayer) and 'IT' or (ingame(localplayer) and 'HIDER' or 'SPECTATOR')
    ui.status.Text = phase .. '  /  ' .. role .. '  /  IT: ' .. (it and it.Name or '?')

    
    local data = localplayer:FindFirstChild('PlayerData')
    local credit = data and data:FindFirstChild('Credits')
    local balance = credit and typeof(credit.Value) == 'number' and credit.Value
    if balance then
        if F.credit_last ~= nil and balance > F.credit_last then F.verified_gains += balance - F.credit_last end
        F.credit_last = balance
    end

    
    local lobby_count, hider_count = 0, 0
    for _, player in ipairs(players:GetPlayers()) do
        if player ~= localplayer and getroot(player) then
            if not ingame(player) and not isit(player) then lobby_count += 1
            elseif ingame(player) and not isit(player) then hider_count += 1 end
        end
    end
    local reason = not isit(localplayer) and 'not It'
        or (not running or not running.Value) and 'no round'
        or (not seeking or not seeking.Value) and 'waiting for seek'
        or lobby_count == 0 and 'no eligible lobby targets'
        or 'server acceptance unknown'
    ui.lobby_debug.Text = string.format('%d lobby targets / %d hiders | %s', lobby_count, hider_count, reason)
    ui.farm_status.Text = 'Credit TPs: ' .. F.credit_attempts .. '  |  Lobby targets: ' .. F.lobby_targets .. '  |  Pull ticks: ' .. F.pull_ticks
    ui.test_stats.Text = 'Coin pull attempts: ' .. F.pull_credit_attempts .. ' | Credits: +' .. F.verified_gains
    ui.ability_status.Text = 'Ability: ' .. A.name .. '  |  Active: ' .. (A.active_known and (A.active and 'YES' or 'NO') or 'UNKNOWN') .. '  |  Attempts: ' .. A.attempts
        .. '\nClient cooldown: ' .. (settings.it_no_cooldown and (A.err and 'clear failed' or ('cleared ' .. A.clears .. ' / intercepted ' .. A.intercepts)) or 'off')
        .. '\n' .. A.last_result
    ui.config_afk.Text = 'Anti-AFK pulses sent: ' .. S.afk_pulses
    ui.home_status.Text = string.format('Round: %s  |  Role: %s  |  It: %s\nCredits: %s  |  Session: %s  |  AFK pulses: %d',
        phase, role, it and it.DisplayName or '?', balance and tostring(balance) or '--', format_time(os.clock() - started_at), S.afk_pulses)

    if S.spectating then
        local _, humanoid = getroot(S.spectating)
        if not humanoid then restore_camera() end
    end
end)

every(0.2, function()
    V.sync_credits()
    V.sync_teleporters()
    V.sync_hazards()
end)
every(0.12, function() if not S.fling_active then F.try_evade() end end)
every(1.6, function()
    if not S.fling_active and settings.pull_credits and F.try_pull_credit() then F.pull_credit_attempts += 1 end
end)
every(0.06, function()
    local running = gamedata:FindFirstChild('GameRunning')
    local seeking = gamedata:FindFirstChild('ItSeeking')
    if not S.fling_active and settings.pull_lobby and isit(localplayer) and running and running.Value and seeking and seeking.Value then
        F.try_pull_lobby()
    else
        F.lobby_target = nil
        F.lobby_started = 0
    end
end)
every(0.7, function()
    if not S.fling_active and settings.auto_credits then
        local part = find_nearest_credit(true)
        if part and F.teleport_credit(part, true) then F.credit_attempts += 1 end
    end
end)
every(2, function()
    local running = gamedata:FindFirstChild('GameRunning')
    local seeking = gamedata:FindFirstChild('ItSeeking')
    if not S.fling_active and settings.auto_seek and isit(localplayer) and running and running.Value and seeking and seeking.Value then
        local target = nearest_hider()
        if target then teleport(target) end
    end
end)

ui.refresh_players()
V.sync_players()
show_page('home')
if not storage.loaded then storage.save() end
notify('luna.dev v22 loaded' .. (storage.loaded and ' (settings restored)' or '') .. '. Right Shift hides the menu.')
Executor compatibility changes over time. Check current reports before testing.

Copy the code above into a compatible Roblox script executor if you choose to test it.

Hide and Seek Extreme Script Status

✓AvailabilityFree
$Price$0
◫CompatibilityDesktop & Mobile
★SafetyVerified

Roblox and game updates can affect compatibility, so the script’s status should be treated as temporary rather than guaranteed.

Frequently Asked Questions

What is a Hide and Seek Extreme script?

A Hide and Seek Extreme script is third-party Lua code intended to run alongside the Roblox game through a compatible script executor.

What features does this Hide and Seek Extreme script include?

Different scripts contain different code. This version includes: Luna.dev — Hide and Seek Extreme, SMART AUTO FARM, Lobby player farming, Captures remaining hiders during the final 30 seconds, Automatic round and role detection, Replaces farming targets when players leave, AFK shelter and automatic recovery, PLAYER TOOLS, Teleport and spectate players, Fling selected player, Fling everyone except It.

How do you use the Hide and Seek Extreme script?

Pick a compatible executor such as Hydrogen, MacSploit, AWP.GG, Delta, Fluxus, Codex, launch Roblox and join the game, copy the complete Lua code from the script card above, paste it into the executor’s editor, and click Run or Execute. Compatibility changes over time, so a name here doesn’t guarantee current support.

Can a Hide and Seek Extreme script get your Roblox account banned?

Yes. Third-party scripts can violate Roblox rules and may put an account at risk. Only use code from a source you trust, never enter your Roblox password into a script or executor, and test on an alternate account first.

Does the Hide and Seek Extreme script work on mobile?

Check whether both the executor and the script support your device and the current Roblox version. Mobile executor support does not guarantee that every script will work.

Check out our Roblox Scripts hub and Roblox Games Directory for more.

Leave a Comment