local _setIdent do local names = { "setthreadidentity", "set_thread_identity", "setidentity", "setcontext", "setthreadcontext" } for _, n in ipairs(names) do local f = rawget(getfenv(), n) if typeof(f) == "function" then _setIdent = f; break end end end local function elevate() if _setIdent then if pcall(_setIdent, 8) then return end if pcall(_setIdent, 7) then return end pcall(_setIdent, 2) end end elevate() local _firesignal = rawget(getfenv(), "firesignal") or firesignal local _getconnections = rawget(getfenv(), "getconnections") or getconnections local function fireSignalSafely(sig, ...) if typeof(_firesignal) == "function" then pcall(_firesignal, sig, ...) end if typeof(_getconnections) == "function" then local ok, conns = pcall(_getconnections, sig) if ok and type(conns) == "table" then for _, conn in ipairs(conns) do if type(conn.Fire) == "function" then pcall(conn.Fire, conn, ...) end if type(conn.Function) == "function" then pcall(conn.Function, ...) end end end end end local function fireClick(btn) fireSignalSafely(btn.MouseButton1Down, 0, 0) fireSignalSafely(btn.MouseButton1Up, 0, 0) fireSignalSafely(btn.MouseButton1Click) fireSignalSafely(btn.Activated, nil, 1) end local function findScreenFrame() local objects = workspace:FindFirstChild("objects") if not objects then return nil, "workspace.objects not found" end local puzzle = objects:FindFirstChild("puzzle") if not puzzle then return nil, "puzzle not found" end local screen = puzzle:FindFirstChild("Screen", true) if not screen then return nil, "Screen part not found" end local sg = screen:FindFirstChildOfClass("SurfaceGui") if not sg then return nil, "SurfaceGui not found" end local sf = sg:FindFirstChild("ScreenFrame") if not sf then return nil, "ScreenFrame not found" end return sf end local function findButtonsContainer() local sf, err = findScreenFrame() if not sf then return nil, err end local b = sf:FindFirstChild("Buttons") if not b then return nil, "Buttons not found" end return b end local function findFormulaFrame(sf) for _, d in ipairs(sf:GetDescendants()) do if d.Name == "FormulaFrame" then return d end end end local function getFormulaText(ff) if not ff then return "" end for _, d in ipairs(ff:GetDescendants()) do if d:IsA("TextLabel") then local t = d.Text if t and t ~= "" and t ~= "Label" then return t end end end return "" end local function findPlayButton() local sf = findScreenFrame() if not sf then return nil end for _, d in ipairs(sf:GetDescendants()) do if d:IsA("TextButton") and (d.Name == "PlayButton" or d.Text == "START") then return d end end end local function findRegenerateBtn() local objects = workspace:FindFirstChild("objects") if not objects then return nil end local puzzle = objects:FindFirstChild("puzzle") if not puzzle then return nil end for _, d in ipairs(puzzle:GetDescendants()) do if (d:IsA("ImageButton") or d:IsA("TextButton")) and d.Name == "RegenerateStage" then return d end end end local function isVisibleChain(g) local cur = g while cur and cur:IsA("GuiObject") do if not cur.Visible then return false end cur = cur.Parent end return true end local function splitTopLevelCommas(s) local parts, depth, cur = {}, 0, "" for i = 1, #s do local ch = s:sub(i, i) if ch == "(" or ch == "[" or ch == "{" then depth = depth + 1; cur = cur .. ch elseif ch == ")" or ch == "]" or ch == "}" then depth = depth - 1; cur = cur .. ch elseif ch == "," and depth == 0 then parts[#parts + 1] = cur; cur = "" else cur = cur .. ch end end if cur:match("%S") then parts[#parts + 1] = cur end return parts end local function normalizeExpr(t) t = t:gsub("π", "math.pi") t = t:gsub("\226\136\146", "-") t = t:gsub("\226\128\147", "-") t = t:gsub("\226\128\148", "-") t = t:gsub("%)%s*%(", ")*(") t = t:gsub("(%d)%s*%(", "%1*(") t = t:gsub("%)%s*(%d)", ")*%1") return t end local function evaluateMath(text) if type(text) ~= "string" or text:match("^%s*$") then return nil end local expr = normalizeExpr(text) local hasAssign = expr:find("=") ~= nil local hasEqEq = expr:find("==") ~= nil local hasNeq = expr:find("~=") ~= nil local code if hasAssign and not hasEqEq and not hasNeq then local pieces, lastVar = {}, nil for _, p in ipairs(splitTopLevelCommas(expr)) do p = p:match("^%s*(.-)%s*$") local var = p:match("^([%a_][%w_]*)%s*=[^=]") or p:match("^([%a_][%w_]*)%s*=$") if not var then return nil end pieces[#pieces + 1] = "local " .. p lastVar = var end if not lastVar then return nil end code = table.concat(pieces, "; ") .. "; return " .. lastVar else code = "return " .. expr end local loader = loadstring or load if type(loader) ~= "function" then return nil end local fn = loader(code) if not fn then return nil end local ok, res = pcall(fn) if not ok or type(res) ~= "number" or res ~= res then return nil end return res end local SHIFT_MAP = { ["!"]="1",["@"]="2",["#"]="3",["$"]="4",["%"]="5", ["^"]="6",["&"]="7",["*"]="8",["("]="9",[")"]="0", } local function decodeShift(text) if type(text) ~= "string" then return nil end local digs = {} for i = 1, #text do local ch = text:sub(i, i) if ch ~= " " and ch ~= "\t" and ch ~= "\n" and ch ~= "\r" then local d = SHIFT_MAP[ch] if not d then return nil end digs[#digs + 1] = d end end if #digs == 0 then return nil end return tonumber(table.concat(digs)) end local function parseBased(text) if type(text) ~= "string" then return nil end local sign, digits, base = text:match("^%s*(%-?)%s*([%w]+)%s*%(%s*(%d+)%s*%)%s*$") if not digits then return nil end base = tonumber(base) if not base or base < 2 or base > 36 then return nil end local n = tonumber(digits, base) if not n then return nil end if sign == "-" then n = -n end return n end local CLICK_DELAY = 0.15 local function sortedClick(entries, status) table.sort(entries, function(a, b) return a.value < b.value end) status(("clicking %d cells ascending"):format(#entries)) for i, e in ipairs(entries) do fireClick(e.btn) status(("%d/%d -> %s"):format(i, #entries, tostring(e.value))) task.wait(CLICK_DELAY) end end local function hoverObject(obj) if not obj then return end local x, y = 0, 0 pcall(function() local p, s = obj.AbsolutePosition, obj.AbsoluteSize x = math.floor(p.X + s.X / 2) y = math.floor(p.Y + s.Y / 2) end) fireSignalSafely(obj.MouseEnter, x, y) fireSignalSafely(obj.MouseMoved, x, y) fireSignalSafely(obj.InputBegan) fireSignalSafely(obj.InputChanged) end local function unhoverObject(obj) if not obj then return end fireSignalSafely(obj.MouseLeave, 0, 0) end local function hoverCell(c) if c.slot then hoverObject(c.slot) for _, ch in ipairs(c.slot:GetChildren()) do if ch:IsA("GuiObject") then hoverObject(ch) end end else hoverObject(c.mtb); hoverObject(c.btn) end end local function unhoverCell(c) if c.slot then for _, ch in ipairs(c.slot:GetChildren()) do if ch:IsA("GuiObject") then unhoverObject(ch) end end unhoverObject(c.slot) else unhoverObject(c.mtb); unhoverObject(c.btn) end end local function focusBox(tb) pcall(function() tb:CaptureFocus() end) fireSignalSafely(tb.Focused) end local function submitBox(tb, value) tb.Text = tostring(value) fireSignalSafely(tb.FocusLost, true) pcall(function() tb:ReleaseFocus(true) end) end local function cancelBox(tb) fireSignalSafely(tb.FocusLost, false) pcall(function() tb:ReleaseFocus(false) end) end local function waitForFormula(ff, prev, timeout) local deadline = os.clock() + (timeout or 0.4) while os.clock() < deadline do local cur = getFormulaText(ff) if cur ~= "" and cur ~= prev then return cur end task.wait(0.03) end return getFormulaText(ff) end local function waitCellSolved(c, timeout) local deadline = os.clock() + (timeout or 1.5) while os.clock() < deadline do if c.btn.Visible and not c.mtb.Visible then return true end task.wait(0.05) end return false end local function solveL1(status) local container, err = findButtonsContainer() if not container then return false, err end local list = {} for _, slot in ipairs(container:GetChildren()) do if slot:IsA("Frame") then local btn = slot:FindFirstChildOfClass("TextButton") local lbl = slot:FindFirstChildOfClass("TextLabel") if btn and btn.Visible and (not lbl or not lbl.Visible) then local n = tonumber(btn.Text) if n then list[#list + 1] = { btn = btn, value = n } end end end end if #list == 0 then return false, "no L1 buttons" end sortedClick(list, status) return true end local function solveL2(status) local container, err = findButtonsContainer() if not container then return false, err end local list = {} for _, slot in ipairs(container:GetChildren()) do if slot:IsA("Frame") then local btn = slot:FindFirstChildOfClass("TextButton") if btn and btn.Visible then local v = decodeShift(btn.Text) if v then list[#list + 1] = { btn = btn, value = v } end end end end if #list == 0 then return false, "no L2 shift symbols" end sortedClick(list, status) return true end local function solveL3(status) local container, err = findButtonsContainer() if not container then return false, err end local list = {} for _, slot in ipairs(container:GetChildren()) do if slot:IsA("Frame") then local btn = slot:FindFirstChildOfClass("TextButton") if btn and btn.Visible then local v = parseBased(btn.Text) if v then list[#list + 1] = { btn = btn, value = v } end end end end if #list == 0 then return false, "no L3 base format" end sortedClick(list, status) return true end local function solveL4(status) if type(loadstring) ~= "function" and type(load) ~= "function" then return false, "no loadstring" end local sf, err = findScreenFrame() if not sf then return false, err end local container = sf:FindFirstChild("Buttons") if not container then return false, "Buttons not found" end local ff = findFormulaFrame(sf) if not ff then return false, "FormulaFrame not found" end local cells = {} for _, slot in ipairs(container:GetChildren()) do if slot:IsA("Frame") then local mtb = slot:FindFirstChild("MathTextBox") local btn = slot:FindFirstChildOfClass("TextButton") if mtb and btn then cells[#cells + 1] = { slot = slot, mtb = mtb, btn = btn } end end end if #cells == 0 then return false, "no MathTextBox cells" end local lastHovered local function isPending(c) return c.mtb.Visible and not c.btn.Visible end local function countPending() local n = 0 for _, c in ipairs(cells) do if isPending(c) then n = n + 1 end end return n end local function resetHover() if lastHovered then unhoverCell(lastHovered); lastHovered = nil end hoverObject(sf); unhoverObject(sf) task.wait(0.05) end local function readFormulaForCell(c) if lastHovered and lastHovered ~= c then unhoverCell(lastHovered) task.wait(0.06) end for attempt = 1, 4 do if attempt == 3 then resetHover() end hoverCell(c) lastHovered = c local deadline = os.clock() + 0.5 local f = "" while os.clock() < deadline do f = getFormulaText(ff) if f ~= "" then break end task.wait(0.04) end if f ~= "" then return f end unhoverCell(c) task.wait(0.06) end return getFormulaText(ff) end local function processCell(i, c) if not isPending(c) then return false, "already solved" end local formula = readFormulaForCell(c) if formula == "" then return false, "no formula" end local ans = evaluateMath(formula) if not ans then return false, "compute failed: " .. formula end local txt = (math.floor(ans) == ans) and string.format("%d", ans) or tostring(ans) status(("[L4] %d/%d %s = %s"):format(i, #cells, formula, txt)) focusBox(c.mtb) task.wait(0.10) submitBox(c.mtb, txt) local ok = waitCellSolved(c, 1.8) task.wait(0.25) if ok then return true end return false, ("rejected: %s = %s"):format(formula, txt) end local MAX_PASSES = 8 local solved = 0 local lastFails = {} for pass = 1, MAX_PASSES do if countPending() == 0 then break end local progressed = false for i, c in ipairs(cells) do if isPending(c) then local ok, reason = processCell(i, c) if ok then solved = solved + 1 progressed = true lastFails[i] = nil else lastFails[i] = reason cancelBox(c.mtb) if lastHovered then unhoverCell(lastHovered); lastHovered = nil end resetHover() end end end local left = countPending() status(("[L4] pass %d/%d solved %d/%d left %d") :format(pass, MAX_PASSES, solved, #cells, left)) if left == 0 then break end if not progressed then resetHover(); task.wait(0.25) else task.wait(0.10) end end if lastHovered then unhoverCell(lastHovered) end local left = countPending() if left > 0 then local msgs = {} for i, c in ipairs(cells) do if isPending(c) then msgs[#msgs + 1] = ("#%d:%s"):format(i, lastFails[i] or "?") end end return false, ("phase 1 incomplete %d/%d unsolved (%s)") :format(left, #cells, table.concat(msgs, ";")) end status(("[L4] phase 1: %d/%d, settling"):format(solved, #cells)) task.wait(0.6) local readyDeadline = os.clock() + 3 while os.clock() < readyDeadline do local ready = true for _, c in ipairs(cells) do if not (c.btn.Visible and tonumber(c.btn.Text)) then ready = false; break end end if ready then break end task.wait(0.1) end local list = {} for _, c in ipairs(cells) do if c.btn.Visible then local n = tonumber(c.btn.Text) if n then list[#list + 1] = { btn = c.btn, value = n } end end end if #list ~= #cells then return false, ("phase 2: only %d/%d buttons ready"):format(#list, #cells) end status("[L4] phase 2: clicking") sortedClick(list, status) return true end local function detectLevel() local container = findButtonsContainer() if not container then return nil end local hasMtb, hasShift, hasBased, hasNum = false, false, false, false for _, slot in ipairs(container:GetChildren()) do if slot:IsA("Frame") then local mtb = slot:FindFirstChild("MathTextBox") local btn = slot:FindFirstChildOfClass("TextButton") if mtb and mtb.Visible then hasMtb = true end if btn and btn.Visible then local t = btn.Text if decodeShift(t) then hasShift = true elseif parseBased(t) then hasBased = true elseif tonumber(t) then hasNum = true end end end end if hasMtb then return "L4" end if hasBased then return "L3" end if hasShift then return "L2" end if hasNum then return "L1" end return nil end local SOLVERS = { L1 = solveL1, L2 = solveL2, L3 = solveL3, L4 = solveL4 } local function getGuiParent() local lp = game:GetService("Players").LocalPlayer if lp then local pg = lp:FindFirstChildOfClass("PlayerGui") if pg then return pg end end local h = rawget(getfenv(), "gethui") if typeof(h) == "function" then local ok, r = pcall(h) if ok and r then return r end end return game:GetService("CoreGui") end local guiParent = getGuiParent() local existing = guiParent:FindFirstChild("HarryPuzzleSolverGui") if existing then existing:Destroy() end local screenGui = Instance.new("ScreenGui") screenGui.Name = "HarryPuzzleSolverGui" screenGui.ResetOnSpawn = false screenGui.ZIndexBehavior = Enum.ZIndexBehavior.Sibling screenGui.IgnoreGuiInset = true screenGui.Parent = guiParent local C = { headerBg = Color3.fromRGB(142, 201, 252), border1 = Color3.fromRGB(142, 201, 252), border2 = Color3.fromRGB(121, 171, 214), panelBg = Color3.fromRGB(255, 255, 255), titleTx = Color3.fromRGB(12, 50, 85), btnBg = Color3.fromRGB(244, 244, 244), btnBgHover = Color3.fromRGB(232, 240, 252), btnBorder = Color3.fromRGB(228, 228, 228), btnTx = Color3.fromRGB(0, 0, 0), statusBg = Color3.fromRGB(252, 252, 252), statusTx = Color3.fromRGB(40, 50, 60), hideBg = Color3.fromRGB(252, 234, 160), closeBg = Color3.fromRGB(252, 118, 118), autoOnBg = Color3.fromRGB(177, 232, 184), autoOffBg = Color3.fromRGB(244, 244, 244), stopBg = Color3.fromRGB(252, 200, 200), } local FONT = Enum.Font.SourceSansSemibold local FONT_REG = Enum.Font.SourceSans local W, BAR, BODYH = 350, 25, 230 local drag = Instance.new("Frame") drag.Name = "drag" drag.BackgroundColor3 = C.headerBg drag.BorderSizePixel = 0 drag.Size = UDim2.fromOffset(W, BAR) drag.Position = UDim2.new(0.5, -W/2, 0.4, -(BAR + BODYH)/2) drag.Active = true drag.ZIndex = 4 drag.Parent = screenGui local bg1 = Instance.new("Frame") bg1.Parent = drag bg1.BorderSizePixel = 4 bg1.BorderColor3 = C.border1 bg1.BackgroundColor3 = C.panelBg bg1.Size = UDim2.fromOffset(W, BODYH + 21) bg1.Position = UDim2.fromOffset(0, 4) bg1.ZIndex = 0 local bg2 = Instance.new("Frame") bg2.Parent = bg1 bg2.BorderSizePixel = 5 bg2.BorderColor3 = C.border2 bg2.BackgroundColor3 = C.panelBg bg2.Size = UDim2.new(1, 0, 1, 0) bg2.ZIndex = 0 local main = Instance.new("Frame") main.Parent = drag main.BackgroundColor3 = C.panelBg main.BorderSizePixel = 1 main.BorderColor3 = C.border2 main.Size = UDim2.fromOffset(W, BODYH) main.Position = UDim2.fromOffset(0, BAR) main.ZIndex = 2 local title = Instance.new("TextLabel") title.Parent = drag title.BackgroundTransparency = 1 title.Position = UDim2.fromOffset(8, 0) title.Size = UDim2.new(1, -90, 1, 0) title.Font = FONT_REG title.TextSize = 17 title.TextColor3 = C.titleTx title.TextXAlignment = Enum.TextXAlignment.Left title.Text = "HARRY AUTO-SOLVER" title.ZIndex = 5 local function makeCornerBtn(name, txt, color, x) local b = Instance.new("TextButton") b.Name = name b.Parent = drag b.AutoButtonColor = true b.BackgroundColor3 = color b.BorderSizePixel = 1 b.BorderColor3 = C.btnBorder b.Size = UDim2.fromOffset(35, 20) b.Position = UDim2.fromOffset(x, 2) b.Font = FONT_REG b.TextSize = 14 b.TextColor3 = C.btnTx b.Text = txt b.ZIndex = 5 return b end local hideBtn = makeCornerBtn("hib", "Hide", C.hideBg, W - 35 - 35 - 2) local closeBtn = makeCornerBtn("deb", "Del", C.closeBg, W - 35 - 2) local row1 = Instance.new("Frame") row1.Parent = main row1.BackgroundTransparency = 1 row1.Size = UDim2.new(1, 0, 0, 30) row1.Position = UDim2.fromOffset(0, 4) local row1L = Instance.new("UIListLayout", row1) row1L.FillDirection = Enum.FillDirection.Horizontal row1L.HorizontalAlignment = Enum.HorizontalAlignment.Center row1L.VerticalAlignment = Enum.VerticalAlignment.Center row1L.Padding = UDim.new(0, 4) row1L.SortOrder = Enum.SortOrder.LayoutOrder local function makeTabBtn(parent, label, w, order, bg) local b = Instance.new("TextButton") b.Parent = parent b.LayoutOrder = order b.AutoButtonColor = true b.BackgroundColor3 = bg or C.btnBg b.BorderSizePixel = 1 b.BorderColor3 = C.btnBorder b.Size = UDim2.fromOffset(w, 22) b.Font = FONT b.TextSize = 14 b.TextColor3 = C.btnTx b.Text = label return b end local btnL1 = makeTabBtn(row1, "L1", 50, 1) local btnL2 = makeTabBtn(row1, "L2", 50, 2) local btnL3 = makeTabBtn(row1, "L3", 50, 3) local btnL4 = makeTabBtn(row1, "L4", 50, 4) local btnDetect = makeTabBtn(row1, "DETECT", 66, 5) local btnRefresh = makeTabBtn(row1, "REFRESH", 66, 6) local row2 = Instance.new("Frame") row2.Parent = main row2.BackgroundTransparency = 1 row2.Size = UDim2.new(1, 0, 0, 30) row2.Position = UDim2.fromOffset(0, 38) local row2L = Instance.new("UIListLayout", row2) row2L.FillDirection = Enum.FillDirection.Horizontal row2L.HorizontalAlignment = Enum.HorizontalAlignment.Center row2L.VerticalAlignment = Enum.VerticalAlignment.Center row2L.Padding = UDim.new(0, 4) row2L.SortOrder = Enum.SortOrder.LayoutOrder local btnAuto = makeTabBtn(row2, "AUTO OFF", 160, 1, C.autoOffBg) local btnStop = makeTabBtn(row2, "STOP", 160, 2, C.stopBg) local statusBox = Instance.new("Frame") statusBox.Parent = main statusBox.BackgroundColor3 = C.statusBg statusBox.BorderSizePixel = 1 statusBox.BorderColor3 = C.btnBorder statusBox.Size = UDim2.new(1, -16, 0, 130) statusBox.Position = UDim2.fromOffset(8, 74) local statusHdr = Instance.new("TextLabel") statusHdr.Parent = statusBox statusHdr.BackgroundTransparency = 1 statusHdr.Position = UDim2.fromOffset(6, 2) statusHdr.Size = UDim2.new(1, -12, 0, 14) statusHdr.Font = FONT statusHdr.TextSize = 12 statusHdr.TextColor3 = C.titleTx statusHdr.TextXAlignment = Enum.TextXAlignment.Left statusHdr.Text = "STATUS" local statusLbl = Instance.new("TextLabel") statusLbl.Parent = statusBox statusLbl.BackgroundTransparency = 1 statusLbl.Position = UDim2.fromOffset(6, 18) statusLbl.Size = UDim2.new(1, -12, 1, -22) statusLbl.Font = FONT_REG statusLbl.TextSize = 13 statusLbl.TextColor3 = C.statusTx statusLbl.TextXAlignment = Enum.TextXAlignment.Left statusLbl.TextYAlignment = Enum.TextYAlignment.Top statusLbl.TextWrapped = true statusLbl.Text = "ready" local function setStatus(s) statusLbl.Text = tostring(s) end local UIS = game:GetService("UserInputService") do local dragging, dragStart, startPos drag.InputBegan:Connect(function(input) elevate() if input.UserInputType == Enum.UserInputType.MouseButton1 or input.UserInputType == Enum.UserInputType.Touch then dragging = true dragStart = input.Position startPos = drag.Position input.Changed:Connect(function() if input.UserInputState == Enum.UserInputState.End then dragging = false end end) end end) UIS.InputChanged:Connect(function(input) if dragging and (input.UserInputType == Enum.UserInputType.MouseMovement or input.UserInputType == Enum.UserInputType.Touch) then elevate() local delta = input.Position - dragStart drag.Position = UDim2.new( startPos.X.Scale, startPos.X.Offset + delta.X, startPos.Y.Scale, startPos.Y.Offset + delta.Y ) end end) end closeBtn.MouseButton1Click:Connect(function() elevate(); screenGui:Destroy() end) local minimized = false hideBtn.MouseButton1Click:Connect(function() elevate() minimized = not minimized if minimized then main.Visible = false; bg1.Visible = false; hideBtn.Text = "Show" else main.Visible = true; bg1.Visible = true; hideBtn.Text = "Hide" end end) local running = false local autoMode = false local autoThread local function setAutoBtn(state) if state then btnAuto.Text = "AUTO ON" btnAuto.BackgroundColor3 = C.autoOnBg else btnAuto.Text = "AUTO OFF" btnAuto.BackgroundColor3 = C.autoOffBg end end local function runOnce(name, fn) elevate() if running then setStatus("busy"); return false end running = true setStatus(name .. ": start") local ok, err, msg = pcall(fn, setStatus) running = false if not ok then setStatus(name .. " error: " .. tostring(err)) return false elseif err == false then setStatus(name .. " failed: " .. tostring(msg)) return false else setStatus(name .. " done") return true end end local function waitForPlayButton(timeout) local deadline = os.clock() + (timeout or 8) while os.clock() < deadline do if not autoMode then return nil end local pb = findPlayButton() if pb and isVisibleChain(pb) then return pb end task.wait(0.15) end return findPlayButton() end local function waitForLevelReady(timeout) local deadline = os.clock() + (timeout or 8) while os.clock() < deadline do if not autoMode then return nil end local lvl = detectLevel() if lvl then return lvl end task.wait(0.15) end return detectLevel() end local function waitForTransition(prevLvl, timeout) local deadline = os.clock() + (timeout or 6) while autoMode and os.clock() < deadline do local pb = findPlayButton() if pb and isVisibleChain(pb) then return "start" end local lvl = detectLevel() if lvl and lvl ~= prevLvl then return "level", lvl end task.wait(0.15) end return "timeout" end local function autoLoop() setStatus("auto: scanning") local lastLvl local blockedLvl, blockedUntil = nil, 0 while autoMode do local pb = findPlayButton() if pb and isVisibleChain(pb) then elevate() fireClick(pb) setStatus("auto: START clicked, waiting load") lastLvl = nil blockedLvl = nil local t0 = os.clock() while autoMode and os.clock() - t0 < 3.0 do local pb2 = findPlayButton() if not (pb2 and isVisibleChain(pb2)) then break end task.wait(0.1) end task.wait(1.5) local kind, newLvl = waitForTransition(nil, 8) if kind == "level" then setStatus("auto: " .. newLvl .. " loaded") task.wait(0.5) end else local lvl = detectLevel() local now = os.clock() local isBlocked = lvl and lvl == blockedLvl and now < blockedUntil if lvl and lvl ~= lastLvl and not isBlocked then elevate() running = true setStatus("auto: solving " .. lvl) local fn = SOLVERS[lvl] local ok, err, msg = pcall(fn, setStatus) running = false if not ok then setStatus("auto: " .. lvl .. " error: " .. tostring(err)) local rb = findRegenerateBtn() if rb then fireClick(rb) setStatus("auto: REFRESH after error") task.wait(1.5) else blockedLvl = lvl blockedUntil = os.clock() + 5 task.wait(2) end elseif err == false then setStatus("auto: " .. lvl .. " failed: " .. tostring(msg)) local rb = findRegenerateBtn() if rb then fireClick(rb) setStatus("auto: REFRESH after fail") task.wait(1.5) else blockedLvl = lvl blockedUntil = os.clock() + 5 task.wait(2) end else setStatus("auto: " .. lvl .. " done") lastLvl = lvl blockedLvl = nil if not autoMode then break end local kind, newLvl = waitForTransition(lvl, 8) if kind == "level" then setStatus("auto: " .. newLvl .. " loaded") lastLvl = nil elseif kind == "start" then setStatus("auto: round done, START visible") lastLvl = nil else setStatus("auto: transition timeout") lastLvl = nil end end else if isBlocked then setStatus(("auto: %s blocked, waiting"):format(lvl)) elseif lastLvl then setStatus("auto: waiting for next level") else setStatus("auto: idle") end task.wait(0.4) end end end setStatus("auto: stopped") end btnL1.MouseButton1Click:Connect(function() elevate(); runOnce("L1", solveL1) end) btnL2.MouseButton1Click:Connect(function() elevate(); runOnce("L2", solveL2) end) btnL3.MouseButton1Click:Connect(function() elevate(); runOnce("L3", solveL3) end) btnL4.MouseButton1Click:Connect(function() elevate(); runOnce("L4", solveL4) end) btnDetect.MouseButton1Click:Connect(function() elevate() local lvl = detectLevel() if lvl then runOnce(lvl, SOLVERS[lvl]) else setStatus("DETECT: no level") end end) btnRefresh.MouseButton1Click:Connect(function() elevate() local rb = findRegenerateBtn() if rb then fireClick(rb) setStatus("REFRESH: clicked") else setStatus("REFRESH: button not found") end end) btnAuto.MouseButton1Click:Connect(function() elevate() if autoMode then autoMode = false setAutoBtn(false) else autoMode = true setAutoBtn(true) autoThread = task.spawn(autoLoop) end end) btnStop.MouseButton1Click:Connect(function() elevate() autoMode = false setAutoBtn(false) setStatus("stopped") end) setStatus("ready - pick a level or AUTO")