Autoplay reduces grind: it picks a live creature on the current floor and engages it, over and over, and advances the floor when the portal is open. It is a convenience over the ordinary floor loop — it takes no action a player could not take by hand, and it never enters a boss den.
Autoplay is unlocked on floor 6: floors 1–5 are the scripted opening and are played by hand, and the AUTOPLAY button appears on the floor the moment the unlock lands (Tower rulings form card T6 part 4, D 2026-10-18; LIVE since 0.89). Its settings live under Settings > Battle & Autoplay: On Death (Stay / Go Down, default Go Down), Autochallenge Boss — Out of Keys (Stay / Go Up / Go Down, default Go Down), Targeting (Lowest Health / Highest Health / Random, default Lowest Health), Ultimate (Turn 1 / 2 / 3 / Never Use, default Turn 1), Prioritize Buffs and Prioritize Debuffs (both off by default). They are part of your save and follow you between devices.
tickFloor(), which only executes while phase === 'floor'. Every other phase (battle, boss den, victory, defeat) stops it dead.floor % 10 === 9) it climbs only if that zone's boss is already down — otherwise it keeps farming. Autoplay never takes a boss; the player does.Status: LIVE on release since 2026-08-05 (0.33). The On Death battle option (Stay / Go Down, default Go Down) is read in exactly one place — the autoplay defeat handler. A defeat dismissed by hand never descends, whatever the setting says.
With autoplay on, 2200ms after the defeat scene appears:
floorNum already reads the pre-boss floor for the whole boss fight (travelToBossRoom() parks it at bf-1), so the floor below the boss is the one already loaded — the run returns to it. Descending here would have skipped a floor, which it did until 2026-07-31.Go Down, every autoplay death on floor 2 or above — a descent, and a boss loss too (no descent, the run returns to the floor below the boss) — switches Auto-Advance Floors off, persisted, so the climb is re-armed deliberately rather than immediately walking back into the loss.The den is skipped, autoplay only. A manual descent onto a boss floor always routes into the den (travel() hands off to travelToBossRoom()), mirroring the climb — a downed boss does not skip it. An autoplay descent passes opts.skipBoss, which bypasses that branch; the same clamp that keeps an upward target off a boss floor then carries the target one further down. So 11 → 9, 21 → 19, and an ordinary descent (8 → 7) is untouched.
Why it must be skipped: autoplay never fights bosses, and the loop only runs while phase === 'floor'. A death descent that landed in the den therefore parked the run in a room the loop cannot act in, with no timer and no path back — it stood still until the player noticed and walked out by hand. Reported live 2026-08-05 (feedback #28) while descending repeatedly after losses. Verified by tools/probe_r28.py (8/8; the unpatched build fails three of the eight, including “autoplay resumes on the landed floor”).
Changing floor or entering a boss den is a transit: floorTransit is set, an input-blocking veil covers the stage, and every image for the destination is decoded before the new phase is applied. The veil holds for a minimum of 1400ms, with a 30s anti-softlock failsafe.
The phase does not change until the transit lands. Through the whole veil the game still reads phase === 'floor'. Three rules follow, and all three are load-bearing:
floorTransit is set.!floorTransit when it fires, not only when it is scheduled. A transit that begins inside that 650ms window is the whole bug class.enterBattle() refuses outright while floorTransit is set. This is the central guard: it covers autoplay, a manual click on a creature under the veil, and any future caller.The floor loop never auto-enters a boss den; that is a fixed rule and not a setting, and it is unchanged. Autochallenge Boss is a separate switch that lives inside the den, under the challenge button. Armed, it opens the boss fight as soon as the gravestone clears, fights it, and does so again and again until the boss keys run out. D, 2026-08-07: “It should keep fighting only the boss until you are out of keys.”
tickFloor() still reads s.autoplay alone, so walking out of the den with the switch on engages nothing.autoFighting() is s.autoplay or s.bossAuto while inBossFight (the fight) or vicWasBoss (the victory, power-up and advancement scenes that follow it, set by the boss-win commit and cleared by toFloor()). Every manual-play affordance — move menu, target picker, confirm, ultimate, actor ring — hides on the same test, or the fight would resolve itself underneath a live menu.bossExit(), travel() up, travel() down, atlas travelTo() and the key's own boss button — and a list of five is wrong the first time a sixth is added. bossAutoLeaveGuard() asks the only question that matters instead: are we still somewhere the switch means anything? The den, the boss fight (inBossFight, covering bintro through resolving) and the scenes after a boss win (vicWasBoss) are the whole of that; anything else and it turns itself off. A transit is not a departure — travel() holds the phase at 'bossroom' while the next floor's art decodes, so the guard fires on arrival, and the loop already refuses while floorTransit is set.BOSS_RESPAWN (10s) exactly as a player would; it does not shorten, reset or bypass it. Keys remain the real rate limit.isPanelOpen() written for the incense pill, so a panel added later is covered without touching this code. A panel is what is on screen (2026-08-26): a sub-window of the bag that only renders while the bag is open — the soul bag's FILTERS window — does not count on its own; the bag's own flag already does. Its flag outlives the bag (nothing clears it on the bag's close), and before this it silently held the switch off for the rest of the session (#580).The default is Go Down, one default for everyone. There is one realm since the hardcore mode left the game (2026-08-28), so optDef() simply returns the option’s def; there is no per-realm fallback and no defHc. restoreSection() deletes keys rather than writing defaults, so a restored section reads Go Down again.
Why the loop rides the den's repaint instead of its own timer. challengeBoss() calls clearTimers(). A loop built on setTimeout would therefore destroy itself on its first challenge and never fire again — the same shape as the gravestone freeze in report #18, where the room sat frozen because the fight had cleared the timer that repainted it. Autochallenge instead hangs off the 400 ms tick the den already runs, which nothing in the battle path can cancel.
Client: bossAutoTick(), bossAutoOut(), toggleBossAuto(), optDef() in play/app.js. Probe: tools/probe_bossauto.py (18/18).
Reported live 2026-08-01 (feedback #12) and reproduced against the reporter's own save. With autoplay on, an engage armed 650ms before the player entered the boss den. The den transit began; the phase was still 'floor'; the engage fired and started an ordinary floor battle. enterBattle() calls clearTimers(), which destroyed the den transit's own pending apply — including its 30s failsafe — so floorTransit stayed set permanently. The veil never lifted, the den was never reached, and the boss challenge button underneath stayed live: the player spent a Key from that wedged state and got the standard fight. Keys are the rarest lane in a zone, so the loss stranded him on the floor.
Client: tickFloor() autoplay block, enterBattle(), enterBossRoom(), travel() in play/app.js
One segmented setting decides when autoplay fires the class ultimate. Turn-N semantics: the ult fires on the first legal round at or after N — if it is denied there (echo with nothing slotted, unit dead), autoplay takes a normal move and retries every round after. Never Use never queues it. Default is Turn 1, the old behavior.
First Turn read back — and behave as — Turn 1; the engine treats any unknown value as 1 for the same reason: a stored settings snapshot must never change behavior on a string.autoQueue) pilots the account's defense with the owner's settings, so the timing chosen here is the timing attackers meet.The same picker (module autoQueue) drives PvE autoplay and every account’s Auto PVP defense. Per unit, per round:
Why brain 2 exists: a no-cooldown move that out-hit the slot beside it won the base pick every round, so the cooldown move never fired
(“my 4th slot does nothing on auto”). D’s ruling: cooldown beats higher damage. The brain is a field on the pinned
defense settings, brain; see Auto PVP for why it rides the snapshot.
2026-11-03 — codex review (dfmc-client lane codexreview-a; D 2026-11-03 “review the codex, clean it up, update whatever needs updating”). WIKI ONLY. A player-facing opening: the floor-6 unlock (bound to unlocks.csv) and the full list of autoplay settings with their defaults, which the page had never stated in one place. Every mechanic below re-checked against dev 0.93: the constants (2600 ms step, 650 ms engage, 1400 ms veil, 10 s gravestone, 2200 ms defeat wait), the defaults and the picker are as written.
2026-10-22 — the Auto PVP attacker re-aims by his own Targeting too (T13 remainder, D 2026-10-18). LIVE on release since 2026-10-23 (0.89). Needs a worker deploy. The T13 entry below handed D the one seat the rule could not reach: the Auto PVP attacker, whose Targeting was not on the match row. D ruled to pin it at the mint: the row now carries the attacker’s Targeting from his stored save (the three settings-page modes, default Lowest Health), the client pins its side from the mint’s answer and the worker’s replay pins the same, so a dead target re-aims identically on both ends. Matches minted before this keep the first enemy in line on the attacker’s side. Transport and the match row on system-pvp; gate tools/probe_t13b.py 10/20 before, 20/20 after.
2026-10-18 — a target that dies mid-round is re-aimed by Targeting in autoplay (T13, D). LIVE on release since 2026-10-23 (0.89). Needs a worker deploy: the same resolver replays Auto PVP. Tower rulings form card T13 (“If a target dies mid-round, who gets hit?” → “Follow Targeting in autoplay”). Until now a queued single-target attack whose target died earlier in the round landed on the first living enemy in slot order, whatever Targeting said — the case the 2026-10-01 entry below reviewed and handed to D. The resolver (src/battle_round.js, shipped as play/battle_module.js) now re-aims it the way the picker makes its first pick: Lowest / Highest Health by current HP (ties keep slot order, no roll spent), Random one roll off the round’s seeded stream, so the fight the player watches and the server’s re-simulation draw the same numbers. The rule is a per-side pin the caller sets (state.tg): the player’s own Targeting in PvE autoplay, and the Auto PVP defense by the Targeting pinned in its snapshot (src/apvp_sim.js pins the same for the worker’s replay). Not changed: manual play, Competitive and wild enemies still hit the first enemy in line. Not changed and handed to D: the Auto PVP attacker keeps the first enemy in line on both the client and the server, because the match row carries the defense’s settings and not the attacker’s Targeting — pinning it at mint is a transport change. Replays of past Auto PVP matches in which the defense re-aimed a dead target change. Gate tools/probe_t13.py 6/15 before, 15/15 after; the three battle fixtures are unmoved (manual queues, no pin).
2026-10-01 — Targeting = Lowest / Highest Health is honoured by the Prioritize Debuffs pass (TGT, D). ON DEV (0.89). Needs a worker deploy: the same picker pilots the Auto PVP defense. Found while fixing #1032, no player report. The pass aimed every single-target debuff at the first enemy in slot order still missing the status, whatever Targeting said, so a Lowest Health player with the pass on debuffed a full-HP first slot while a near-dead enemy waited. D ruled it against what the option says (Bug Run 0.89 board, Q12). Lowest / Highest now aim by current HP among the enemies still missing the status, as the base pick always has; ties keep slot order and no roll is spent, so seeded fights draw the same numbers. Random is unchanged. Reviewed and NOT changed: when a queued single-target attack’s target dies earlier in the round, the act still lands on the first living enemy in slot order — that rule belongs to the resolver, covers manual play and enemy actions alike, and is handed to D. Client gate tools/probe_tgt.py 5/9 before, 9/9 after; the three battle fixtures run the pass off and are unmoved.
2026-10-01 — Targeting = Random rolls a fresh enemy per action in the Prioritize Debuffs pass too (#1032, D). ON DEV (0.89). A player with Random selected saw autoplay focus one enemy every round and asked whether Random meant one roll at the start of the fight. It never did: the base pick rolls per unit per round from the queue’s rng. The Prioritize Debuffs pass did not roll at all — it aimed every debuff at the first living enemy in slot order still missing the status, so a single-target debuff kit with the pass on hit the first slot round after round whatever Targeting said. The pass now honours Random with a fresh roll among the enemies still missing the status, from the same seeded rng (Auto PVP re-sims and replays agree); Lowest / Highest Health in that pass are unchanged (first such enemy), and the base pick is untouched. Client gate tools/probe_01032.py 6/8 before, 8/8 after; the three battle fixtures pin Lowest Health and are unmoved.
2026-09-30 — review drift fixes: F302, F303. WIKI ONLY. Descending on death: a boss loss with On Death = Go Down does not descend but does switch Auto-Advance Floors off (the code comment says this is intended); the bullet said only a descent did. Out of Keys: the body still described the per-realm default and defHc, removed 2026-08-28; the default is Go Down and optDef() returns def. DESCRIPTIVE ONLY.
2026-09-24 — the AUTOPLAY OPTIONS popup is retired; the cogwheel opens Settings on Battle & Autoplay (#981, D). ON DEV (0.87). The cogwheel beside the AUTOPLAY pill (and the one in the Auto PVP hub) opened a popup of its own that listed six of the Battle & Autoplay rows — the ones it was built with — and never gained the later ones: Stop Advancing After a Loss, Autochallenge Boss — Out of Keys and the Ultimate timing above were all absent from it. D ruled it out of date: “it should take the player to the regular settings section for autoplay … remove that one.” The cogwheel now opens GAME SETTINGS landed on that section, the popup is gone from markup, state and the menu, and every autoplay setting has exactly one home; see Settings for the section’s rows. Gate tools/probe_autoset.py 7/7, red 1/7.
2026-09-09 — Bait stops reporting itself as not applied while it is running (#806, D). LIVE on release since 2026-09-09 (0.73). The tile on the floor reads its countdown from the expiry the server sends with the floor. When a battle came back, the client re-applied the floor from a shorter answer that carries the spawn budget and no boon expiries at all — and Bait’s expiry was the one written without a guard, so every fight cleared it while Incense, which was guarded, survived. That is why it only ever happened to Bait, and only after a fight. An answer that does not mention a boon now leaves it alone; one that says the boon has ended still clears it, and the server says so explicitly rather than by leaving the field out. Nothing was ever lost — the boon kept running server-side the whole time and the next floor refresh corrected the tile. Gate tools/probe_baitpill.py 5/5, red 4/5.
tools/probe_autobtn.py 2/5 before, 5/5 after, boxes read off the rendered screen.settings.brain, which PvE autoplay passes as 2 and the daily fold stamps into every new defense snapshot. Oracle tools/probe_brain2.mjs 6/10 before, 10/10 after; client gate tools/probe_brain2_client.py 5/5 on D’s save; module parity 306/306 unchanged.tools/probe_bossauto_panel.py 11/11, red-first (7/11 on the unfixed tree).travel() gained opts.skipBoss and the On Death Go Down handler passes it, so a death descent lands on the next non-boss floor instead of the lair. Manual descent is unchanged. The whole “Descending on death” section above is new — the behaviour existed and had never been written down. Report #28.floorTransit when it fires, and enterBattle() refuses during a transit.