review-ladder r2: a oneshot probe mid-run is 'activating', which
is-active does not count, so the wait never waited. The root copy must
also be byte-identical to /opt/fly/current/flysim, so a manual rollback
cannot approve an archive the running build refuses. Docs: the hold
after an unrestorable rung, the post-deploy --list check, and never
stopping the unit mid-step.
The loop-recovery helper's notice (/run/fly/wd/recovery-notice.json,
FLY_RECOVERY_NOTICE) is served by serve.mjs at /recovery-notice.json and
polled once a second by the page, independent of flysim. Countdown shows a
Game Boy text box over the bottom of the game; acting covers the game while
flysim is down; done/failed show briefly. Stale, malformed or oversized
notices are ignored and helper strings are clipped to a closed charset.
Adds ?recovery=, __stage.recovery(), the recovery-notice dev tool, unit
tests (model, poller, serve.mjs route) and an e2e spec.
B1: fly-loop-reset runs only /opt/fly/sbin/flysim, a root-owned copy
05-deploy installs from the release tarball after checking it against
the tarball's MANIFEST; fixed paths, fly.env parsed as data, env -i.
B2: the wrapper pauses fly-watchdog.timer (and waits out a running
probe) for the reset, and starts flysim and the timer on every exit.
H1: a step that raises or times out is a failed step; the ladder state
is saved before the step runs. H2: one --list call computes the build's
compatibility once; TimeoutStartSec 25 min. H3: level 2+ resets to the
rung below the best or restarts, never lower.
Tests run the wrapper against a fake flysim, systemctl and archives.
fly-reset-to-milestone does not check compatibility and a flysim that
refuses every checkpoint does not start. fly-loop-reset --check applies
05-deploy's rule (identical, or an adapter-only difference named in
FLY_ACCEPT_ADAPTERS); the ladder picks the highest restorable rung and
falls back to a restart when there is none.
Restart flysim, then reset to the current rung's milestone, then to the
archive below the best rung (never lower), one step per confirmed trap
that outlives the previous one. Two resets a day, three-hour restarts
once they are spent; the ladder starts over at a new best rung or after
six quiet hours. State and history live in the unit's StateDirectory so
a reboot does not forget where the ladder stood.
A router model list confirms each step; a 'not stuck' answer delays it
at most three probes and no answer leaves the watchdog to decide alone.
Each step is announced 60 s ahead in /run/fly/wd/recovery-notice.json
for the stage's recovery splash.
fly-loop-reset is the one new root surface (a sudoers line); 05-deploy
now converges config/fly-sudoers so a release can add it.
trap_hunt, palette_bench and room_escape ran the frame through NeuralAgent::tick, which installs
a frame and its rewards after the next ticks: one frame behind the stream, with the ratchet
observed without the objective signal and a rollback that never re-observed the scene. They now
restore the way the stream restores and run LegacyFrame::transition and ::boundary, looking in
through FrameObserver; FLY_TRACE works in each of them. The stub-readout drivers of
rom_macros_mode, rom_catch and scene_probe run LegacyFrame::execute and ::stub_advance, which are
the same calls they made, in the same order.
Every admitted sugar and operator reward pulse is appended to sugar-journal.jsonl with the frame
counter it was applied at, which is the step of the next transition in FLY_TRACE, so a shadow run
can replay the audience's input. Append-only, not checkpointed, never read back into the fly.
prepare, execute, advance, evaluate, commit, then the boundary's ratchet capture and rollback,
in the order the stream runs them. The frame owns the remainder, the frame counter, the frame on
screen, the mask and the blocked-direction window; the sim loop keeps the feed, the event log,
the milestone archive between commit and boundary, and the checkpoints. Two moves between
disjoint state change no byte: the visual frame is installed at commit, and the stimulations
follow the scene's observation. The FLY_TRACE of a run from the rung-10 checkpoint is
byte-identical to the one the inline hooks wrote.
One JSON line per transition: ticks and the exact remainder, digests of the rates, of the
transition's spikes, of the frame and of work RAM, the decision and the mask, macro and reward
events in order, the rank, and the boundary's slot saves, rollback and captures in the shape of
TraceBehaviour.boundaryActions and TraceOperational.captures. Off unless FLY_TRACE names a file.
Recorded inline in step_frame first, so the frame's extraction can be held to it.
Row 60: TAIL WHIP against a Pidgey at DEFENSE -6 was MOVE 2 183 times. The
rule is PP's: a refused move leaves the pad while another move is usable, and
with none usable the moves stay as PP deals them, so an open list never
comes down to BACK alone.
move_data reads a row of Moves from the cartridge image ($0E:$4000, each row
checked against its own id); move_without_effect answers the refusals the
effect routines make on bytes already in WRAM: a stat stage at its limit or a
stat at 1/999, Mist or a substitute against a stat-lowering move, a status
move against a statused, Poison-type or (Electric) Ground-type target.
MacroState::move_without_effect defaults to false.
resolve_wram.py now follows the decomp's const and _RS counters and a struct
macro's field labels, so battle_struct fields resolve and 77 of 81 pinned
addresses are re-derived with no disagreement.