At a glance

Test
crates/engine-shell/tests/soak_harness.rs - disc-gated, skips and passes without LEGAIA_DISC_BIN
One run
(scene, seed, frames) - nothing else; the input is a pure function of the seed and the observed session
Actuator
World::set_pad, once per frame
Scene set
every playable CDNAME scene, the 0x3F destinations of those scenes, the five minigames, plus +rt, +talk and +shop twins - nothing hand-listed
Output
target/soak/<tag>/: report.md, findings.json, one reduced replay per signature
Regressions
scripts/replays/soak/fixed/ - closed findings that must never fire again
Seeded policyreads what holds the frame Pad maskWorld::set_pad Engine tick+ the hosts' duties Detectors panic / hang softlock script_stall battle_loop value, ... next frame: the policy sees the new state Finding signaturedetector | scene | location Confirm, then reducej-replay-v1, minimal pads fixed/ regressionfails if the signature fires again after the fix
The policy and the detectors both read the session each frame, so (scene, seed, frames) regenerates a run exactly. A finding leaves the loop as a reduced replay; once a fix stops it reproducing, it becomes a regression.

The driver

Each run opens a fresh BootSession, seeds the retail New Game party, mounts an empty two-port card rack, seeds the world RNG from the run seed and enters the scene live with the random encounter loop and player-driven battles armed. Then it ticks.

BootSession::tick is not a whole host. Both play hosts do several things around it every frame, and a driver that skipped them would measure its own omissions. The harness does what the hosts do:

Host dutyWhat the harness does
faithful-play armingwhat both hosts set on a bare session: inline field-VM talk, the leading-edge wall footprint, solid NPCs, terrain follow, the NPC ambient walk
presentation queuesdrains the field-event, battle-event, hit, SFX, shout, XA, CLUT-stage and minigame-cue queues after every tick
FMV playbackskips the movie and runs the shared post-play hand-off
name entryroutes the pad edge into name entry and skips the field tick while the overlay is up
shop / prize counteropens a staged shop in a MenuRuntime, steps it on the frame's edges, and unparks the field script when it closes
save screena Save writes into an in-memory card; a Load resumes that block through resume_save

Pseudo-scenes

<venue>+mg<id>
A minigame: the venue is entered, then the same mode-24 door warp the cabinet's op 0x3E makes is armed. The party is given casino coins first - the one non-pad precondition.
<scene>+rt
A save / load round trip on every free-roam frame a player could save on, at a fixed interval: save, resume that file as a Load does, save again - the two must agree field for field. The run starts with one save on the card, so the pause menu's Load row has a file to resume.
<scene>+shop
Every scene with a priced gold shop: the scene's shops are handed to the menu runtime as the merchant's op 0x49 would, since random walking almost never reaches a merchant and picks Buy.
<scene>+talk
Every field scene: the player is stood in front of the next action-button target - NPCs first, then interact-gated props that carry a record - facing it, and the button is pressed. Encounters are disarmed; the run is there for the records.
<label>+rage
Every roster member carries the Evil Medallion's Rage passive for the whole run, so the anim tick delegates the whole party in every battle: the command ring refuses every member, and each round must still open on Begin | Run, reach the commit confirm and play out on the delegated picks.
...@<save>
Any label can lift a library-card save (town01+rt@PRO-14) and play the scene with that save's party, bag and story flags, so late-game scenes are soaked with the party they are reached with.

A talk seat is taken only where play could put the player: on authored walk-visible floor in an open component, somewhere the walk controller's own probes let the player walk a tile away and come back. An actor is faced from 112 units where it can be (a face-on walk stops 104 short of a standing NPC), a prop from 128. The seat is a function of the frame and the world, so a replay takes the same seats.

Round trips caught a real defect this way: a field save folded actor slots 1 and 2 back into the party records, but in a field those slots belong to the scene, so opurud+rt lost two party records on resume. World::save_party now folds actor mirrors back only in a battle. The library-wide sibling, engine-session/tests/save_roundtrip_library.rs, round-trips every save on the library cards through the card path both hosts take.

Policies

What the policy presses depends on what holds the frame. Every UI surface reads just_pressed, so a press is a tap: the wanted mask on one frame, neutral on the next.

StatePolicy
field / overworldsegments of held walks (straight or diagonal, sometimes with Cross taps), Cross / Circle mashing, a Start tap, idle, and chaos (random masks of everything but Start)
dialogue, or a script holding the playermostly Cross, some directions, some Circle
a script polling the held padthe face button it polls, held - op 0x42 mode 1 reads the held word on one frame of its loop, which a tap hits only by luck
a pickera random number of Down taps, then Cross, once the box's slide has come to rest; the aimed row steps on each time the same picker returns, so a quiz that asks again gets every answer
pause menurandom taps for a random budget, then Circle until it closes
battlemostly the route ladder's fighter (Begin, Attack, Auto, confirm); in the sparring tutorial it follows the lesson, since any other pick rewinds it
minigame / otherrandom face buttons and held directions, rarely Start - the minigames' escape

Detectors

DetectorFires when
panic / tick_errora tick, scene entry or FMV hand-off panics or returns an error
hangone tick runs past the wall-time watchdog; it writes HANG.txt and exits
softlockthe progress digest is unchanged for a whole window while the pad took at least three distinct masks
script_stallthe modal timeline or first helper sits on one PC for a window of field frames - unless its scripted walkers are still stepping, or it is a walk-on record re-polled every tick
battle_loopthree battles open from one parked timeline PC
menu_stuck / battle_endlessthe pause menu survives the Circle back-out; one battle outlasts its threshold
control_starvedthe field refuses the pad with no free-roam frame, the script reaches no new PC, and confirm presses are still arriving
confined900 direction-held frames, all four directions among them, never carry the player 48 units
door_loopsix scene changes inside 600 frames with no direction pressed between them
dropped_to_title / unknown_scenethe mode becomes Title without a game over; a transition names no scene in the set
non_finitea camera float or the caption alpha is NaN or infinite
valueHP / MP over max, live maxima that are not the stat aggregator's rebuild, a level outside 1..=99, a bag stack over 99, a count on a free slot, a duplicated stack
unbounded_growthan engine queue grows past a cap
effect_residuea battle effect is still live on the first field frame after a battle exit or scene load - retail resets the whole pool there (effect VM)
timeline_capthe engine's anti-hang frame cap ended a script context; retail has no such cap, so the record was cut short
save_roundtripin a +rt run, the post-resume save disagrees; the location names the first differing field

The progress digest hashes everything a player could see move: mode, scene, position and heading, every script PC, dialogue, menu, shop and name-entry state, scripted NPC glides, the battle session and every actor's HP, the minigame session, money and bag size. It is deliberately broad: a false "frozen" costs a triage, a false "moving" only a missed finding.

A softlock is located by what holds the frame - pause-menu:<row>:<phase>, battle:state:<action state>, timeline@pc:op, mode:<minigame>, free-roam:immobile@tile(x,z) and so on. A battle park on an absorbing HP-bar pair (the shape that holds the bar-drain gate for good) carries a :hp-bar-absorbing suffix.

  • control_starved catches a script going round. The softlock and stall clocks restart whenever anything moves, so a conversation that reopens its own question passes both forever. Its clock restarts only on free roam, a scene change, or a PC this stretch has not seen.
  • confined is measured, not predicted. It uses the walk controller: the leading-edge probes let a player rest where a flood of the grid says walled, and an actor can shut a seat the grid calls open.
  • door_loop ignores a held direction. A two-way stair sets the player one tile from the door back, so a held direction walks straight back in.

Findings, signatures and reports

A finding's signature is detector|scene|location. Findings are deduplicated by signature and ranked: panics, hangs and tick errors first; softlocks, loops, stuck menus, endless battles, title drops and unknown scenes next; immobility, stalls, starvation and confinement after; value, non-finite, growth and round-trip checks last.

  1. Confirmthe first occurrence is replayed from its pads, truncated to the finding frame, and must fire again
  2. Reducechunks of the pad stream are zeroed while the signature still fires - a bounded delta-debugging pass
  3. Writereplays/<slug>.replay.toml, with scene, seed and signature in header comments TOML ignores

A softlock is reduced under the neutral-pad control, so input that only fed the frozen window drops out; the replay records that in a # soak-min-distinct header. A control_starved finding is reduced only ahead of its window, because a stream cut down to one looping option would starve a healthy conversation too. Every replay names the session configuration it ran under (# soak-arming = "host" or "bare", with a -retail suffix under the hosts' retail mode).

Running

The fixed-budget gate runs a few scenes plus the five minigames and asserts no panic:

cargo test -p legaia-engine-shell --profile release-test --test integration soak_harness::soak_smoke

A long soak is report-only unless LEGAIA_SOAK_STRICT=1:

LEGAIA_SOAK_SEEDS=30 LEGAIA_SOAK_FRAMES=18000 LEGAIA_SOAK_JOBS=8 LEGAIA_SOAK_TAG=big cargo test -p legaia-engine-shell --profile release-test --test integration soak_harness::soak_long -- --nocapture
VariableMeaning
LEGAIA_DISC_BINrequired; without it every disc-gated test skips
LEGAIA_SOAK_SEEDS / _SEED_BASE / _FRAMESseed count, first seed, frames per run
LEGAIA_SOAK_SCENES / _SHARDan explicit scene list; i/n to chunk a long soak across invocations
LEGAIA_SOAK_JOBS / _TAG / _OUTworker threads; report directory name and base
LEGAIA_SOAK_SOFTLOCK_FRAMES / _STARVE_FRAMES / _BATTLE_FRAMES / _HANG_SECSdetector windows and the single-tick watchdog
LEGAIA_SOAK_MINIMIZE / _CONFIRM_MAX / _NO_CONFIRMreduction budget per signature; how many to confirm; skip confirmation
LEGAIA_SOAK_SAVE / _CARDplay every run from a library-card save
LEGAIA_SOAK_NO_ENCOUNTERSdisarm the encounter roll - a battle that still starts is scripted
LEGAIA_SOAK_RETAILsoak under the hosts' retail mode: the options' engine knobs and every fidelity toggle pinned to retail
LEGAIA_SOAK_BAREsoak a bare BootSession without the hosts' arming
LEGAIA_SOAK_REDUCE / _REDUCE_SIG / _REDUCE_OUTre-reduce a saved replay, optionally toward a different signature

The headless engine ticks far faster than real time, so a soak is bounded by the minimiser, not by the runs. Overflow and debug-assertion coverage comes from building the same test with those checks on, into its own target directory.

Reproducing and triaging a finding

LEGAIA_SOAK_REPLAY=target/soak/big/replays/<slug>.replay.toml LEGAIA_SOAK_TRACE=60 cargo test -p legaia-engine-shell --profile release-test --test integration soak_harness::soak_replay -- --nocapture

LEGAIA_SOAK_TRACE=N prints a state line every N frames - mode, scene, holder, position, the bytes at every live script PC, scripted glides, and in battle the action state and every actor's HP / display / accumulator - plus each distinct script body once, as hex. LEGAIA_SOAK_TRACE_NPC=<slot> follows one placement; LEGAIA_SOAK_TRACE_WALLS=1 draws the wall bits around the player. The trace goes to the terminal only.

A fix is checked both ways: the replay must stop firing on the fixed engine, and must still fire on the engine without the fix under the harness as it now stands - a detector that changed since the replay was written can leave it passing for the wrong reason.

Fixtures

scripts/replays/soak/
Reduced replays of open findings, pad input only. soak_fixtures parses each without a disc and, with one, reports whether it still reproduces. It never fails on one that stopped: that is the signal a fix landed, and the fixture moves to fixed/.
scripts/replays/soak/fixed/
Closed findings as regressions. soak_fixed_fixtures fails if a recorded signature fires again; a # soak-expect-max-battles header also bounds the battles a replay may open. Only the recorded signature is asserted.
What the fixed replays pinned
Fixed replayFindingWhat closed it
town0c_scripted_battle_loopthe Rim Elm bee fight re-fired on every returnthe touch-resumed beat ends at its 0x21; the system script sits out a held player and the battle intro
town0d_dual_player_walktwo first-talk records walked the player at oncea playing timeline steps only the placements it addresses
rugi_ / urudre3_npc_walk_from_hide_boxa placed NPC walked in from the off-map hide boxa seat poke that moves an actor is published at once
bylon_party_actor_from_hide_boxa party actor walked in from the hide box4C 37 copies the player onto the actor
urudre1_player_seat_droppeda scripted player walk ran 8000 unitsa record's player seat takes the player arm
vell_attack_short_step_parkan attack approach parked a short step from its target+0x3C is the per-frame body pair
taiku2_hp_bar_absorbing_parkthe 0x51 bar-drain gate parked on an absorbing HP-bar pairthe restaged action clip replays
station3_ / conc3_door_of_light_head_definea Door of Light with an all-zero return triple warped to init_dataa travel word inside the TOC header rows is a miss
ropeway_player_parked_on_gondolaOctam's arrival cutscene left the player seated on the gondolaonly the scene-init sweep's placements collide (FUN_801CF754 walks the +0x0C list)
edlast_ending_soft_resetafter the credits the party stood with no way out49 0C runs the return-to-title soft reset
koin2_fatal_decision_stone_partya fight sat in Fatal Decision's capture band for goodthe 0x5A wipe scan reads the packed status word, so a petrified party wipes
map01_overworld_beat_walk_never_stepsan overworld hand-off beat parked on its walk waitthe world-map arm steps cross-context walk legs
opurud_field_save_zeroes_party_recordsa round trip lost party records 1 and 2actor mirrors fold back only in a battle
balden_fishing_exchange_pad_deadthe pond's prize list answered no pad inputthe list steps off the pad edge
bubu2_bag_stack_past_99a battle grant raised a stack of 99 to 100every bag grant goes through the retail add, capped at 99
jouina_final_heal_readout_overshoota Final-Healed member parked the bar-drain gatethe Final Heal sweep re-syncs the readout first
retockin_monster_curse_folded_as_capturea monster's Curse took a party member off the fielda capture roll downs only a monster seat
town0d_tetsu_talk_never_ends / izumi_spring_question_never_endsa conversation or prop restarted on every way outa touch-resumed context ends at the parking byte after a dismissed box
map01_life_water_live_max_stale / jou_level_up_bakes_accessory_into_baselive maxima drifted from the aggregator's rebuildstat-ups and level-ups grow the base window; the live maxima are rebuilt from it
jouind_ledge_hop_walled_landinga diagonal step-up hop boxed the player in a raised pocketretail tests no landing; the port's landing guard (an enhancement, off under --retail) refuses it
edlast_credits_end_takes_a_held_confirmthe credits parked on their last loopnot an engine defect: the loop polls a held button; the policy now holds it
jouina_terra_summon_hits_for_oneNoa's Terra summon hit for 1every player summon body is its own summon.dat record

The full list, with every fixed replay, is in the source doc.

What it cannot detect

  • Visual or audio wrongness. No frame is rendered and no sample mixed; garbage on screen reads as healthy while the state advances.
  • Retail divergence. Every detector is an internal-consistency check. Wrong damage, a wrong landing tile, a script branch retail does not take - all look like progress. The parity oracles own that question.
  • Anything past the first fatal finding in a run, or past a game over.
  • Deep progression. A plain run starts from a New Game party, which loses most late-game fights; an @<save> run reaches only the states the library card holds.
  • Host drift. A duty a real host performs that the harness does not mirror is invisible here, and one the harness performs that a host has dropped is masked.

See also