Xenon 2

Autopilot · write-up

Native campaign from the game start (2026-10-01)

xenondoc/FULL_NATIVE_CAMPAIGN_20261001.MD · 43 KB · updated 2026-10-03

The full campaign could not be completed: both attempts reproduce a Level 2 entry-corridor failure near world (140, 4136), before either Level 2 boss. Level 1 completes without shield damage or life loss. Levels 3-5 were not reached, so this is a failed full-campaign validation, not a campaign pass.

Recordings and settings

Latest recording: E:/xenon_runs/full-campaign-native-release-20261001-r136.validation/full-campaign-native-release-20261001-r136.x2events.

Both attempts start at canonical frame 0 from assets/xenonplay.sav, with 39 shield and three lives. The official PowerShell build path rebuilt both Release Hatari and the native replay DLL before the first attempt. The same executable drives the second attempt: SHA-256 ca3bc754bad38f2316892caaa09a8cafedbb72419faa90c4c961a7e41b114758.

Hatari was visible, fast-forward was enabled, developer memory tracing used its automatic default, and both original and Sprite Stream AVIs were recorded. The checkpoint interval was 150 canonical frames. All 11855 captured frames of r136 identify the resident C autopilot as the controller; no Python pilot was used and no gameplay tactics were changed.

The initial attempt, r135, stopped at frame 9010 because native-run's default policy ends at the first detected wall stall. Its recording is E:/xenon_runs/full-campaign-native-release-20261001-r135.validation/full-campaign-native-release-20261001-r135.x2events.

The second attempt restarted from frame 0 with --continue-after-stall and --continue-after-life-loss. The new stall option only changes recording policy: incidents remain reported, and the monitor still stops for a persistent stall. It confirmed the same damage and position failure, allowing roughly 3000 stalled frames before stopping at 11854. Neither attempt lost a life.

Incidents in r136

Every row refers to the latest recording above. Source identities come from the emulator's collision-time capture, not recomputed replay prediction.

Frame Problem
8851 Directional projectile #16978 hits the ship; shield 39 -> 35.
8911 Directional projectile #17235 hits the stationary ship; shield 35 -> 31.
8915 Directional projectile #17256 hits the stationary ship; shield 31 -> 27.
8951 Directional projectile #17382 hits the stationary ship; shield 27 -> 23.
9005 Repeated wall collisions hold the ship near world (140, 4136); the episode began around 8856.
9751 The general stall detector confirms no meaningful position or score progress since 8852.
11854 Recording finalizes after the persistent-stall stop; Level 2 remains incomplete, shield 23, lives 3.

The C trace is E:/xenon_runs/full-campaign-native-release-20261001-r136-level2.trace. Its corridor guard records show a navigation goal and screen-band combination that fails to recover the ship from the wall:

  • At 8839, the ship is world (217, 4176), with route destination (216, 4120).
  • At 8851, it has moved to (154, 4154); route progress is 2/3.
  • At 8911, the ship is (140, 4135), route progress is now 1/4, and the immediate movement goal is X [260, 268], world Y [4147, 4149].
  • At 9005, 9751 and 11852, the same destination, route index and immediate goal remain. The proposed movement is Down, while the recorded consumed inputs repeatedly become zero and the ship's Y changes by only about one pixel. Screen Y is around 176; the corridor reaction band remains [144, 164].

Confirmed cause of the persistent stall

The route planner finds a retreat around the wall, but several Level 2 overrides prevent the ship from executing it. At frame 9005 the route's next point is (140, 4148): first move Down to clear the lip, then move right. The mission proposes Down, but the final resident input has no movement bits (0x80, Fire only). There are three cooperating defects:

  1. xap_level2_corridor_guard, in src/autopilot/xenon_autopilot_level2.c around lines 1863-1882, treats the desired screen band [144,164] as a mandatory steering rule. At screen Y=176 it replaces the route's Down with Up if map-clear, otherwise zero. Up is blocked by the wall, so the retreat is discarded.
  2. level2_clamp_escape, around lines 551-561, reverses Down into Up when the hull bottom reaches screen Y=184. It checks only screen coordinates, ignoring the game's ability to backscroll when Down is held at the lower edge. The live collision-choice trace consequently lists duplicated Up candidates and no Down candidates at the stalled position.
  3. level2_move_is_clear, around lines 376-400, requires an occupied start to reach a completely free four-pixel span cell in a single forecast step. Starting camera backscroll does not immediately move the anchor to another grid cell. This rejects the beginning of a valid multi-frame escape even though the main C wall evaluator supports movement out of occupied starts.

Wall-replay recovery in src/autopilot/xenon_autopilot_driver.c, around lines 877-922, is gated by state.level2.boss_active. This failure occurs before the boss, so that recovery cannot break the deadlock. The reaction band and bottom clamp predate the profiling changes: git blame attributes them to commit 7df0f5639 from September 19.

The lane rule also damages the route's geometry: level2_set_corridor_pacing around lines 1953-1968 overwrites the next point's X=140 with lane X=264. level2_right_lane_target_x can search across a blocked row to find that lane. The result is a diagonal/rightward goal before the required downward escape. It should remain a preference inside the reachable corridor rather than replace a connector waypoint.

Native geometry proof and its limits

work/probe_level2_corridor_stall.py feeds raw captures 8961-9005 to the latest C DLL, using checkpoints/f8961-periodic.sav.ram to restore the map. All decisions and geometry probes execute in C; no Python pilot is used. The output is work/full-campaign-r136-stall-probe.json.

At frame 9005 the probe reproduces the Down mission and Fire-only final control. The camera is at scroll Y=3960 with backward limit 3975, so 15 pixels of backscroll remain. Up, Left and Right fail the physical wall forecast. Down passes it; holding Down for 16 simulated steps reaches world (140,4151) at a free physical anchor. The initial and first-step Down anchors are still occupied in the four-pixel span graph, demonstrating the one-step rejection described above. The camera/ship simulator uses entry scroll for world Y, so a backscroll input begins moving the camera before world Y advances on the following update.

This is a fresh C diagnostic session with restored RAM, not a replay of the entire live controller's private history; its map revision is therefore different. The route, band and Down/hold conflict match the original live trace. The 16-step test checks terrain and camera movement only: it does not prove that every possible projectile encounter along a revised route is safe. No C gameplay changes have been made as part of this analysis.

Reproduce the probe from the repository root:

python work/probe_level2_corridor_stall.py E:/xenon_runs/full-campaign-native-release-20261001-r136.validation/full-campaign-native-release-20261001-r136.x2events --first 8961 --last 9005 --ram E:/xenon_runs/full-campaign-native-release-20261001-r136.validation/checkpoints/f8961-periodic.sav.ram --output work/full-campaign-r136-stall-probe.json

The next fix should give a terrain-validated connector escape precedence over corridor pacing, preserve its waypoint coordinates, and evaluate bottom-edge inputs using the actual camera forecast. Occupied-start escape must be judged over enough steps to observe backscroll progress, using the existing physical wall evaluator rather than demanding an immediately free span cell. This should be restricted to the affected Level 2 corridor/recovery code and then validated live from before the corridor, followed by a complete Level 2 run.

Checkpoint restart comparison (2026-10-01)

A Hatari snapshot is not a checkpoint of the resident C pilot. On restore, XenonControl_OnSnapshotRestored calls XenonAutopilot_Reset and clears pending controls. Reset creates a fresh C session; retained route/maneuver state and prediction history are rebuilt. The snapshot restores game RAM and object identities, but does not serialize the pilot's private history. Therefore a restarted live validation need not reproduce the original continuation even when its gameplay source is unchanged.

This was verified using the r136 checkpoint checkpoints/f8488-periodic.sav:

Recording Source Frame 8606
E:/xenon_runs/full-campaign-native-release-20261001-r136.validation/full-campaign-native-release-20261001-r136.x2events Continuous baseline Screen (255,83); no damage.
E:/xenon_runs/level2-stream-bend-local-r141.validation/level2-stream-bend-local-r141.x2events Experimental local changes Screen (241,136); 16 damage from swarm #16068.
E:/xenon_runs/level2-checkpoint-baseline-r142.validation/level2-checkpoint-baseline-r142.x2events Unchanged e01b55c7 gameplay source, both binaries rebuilt The same 16-damage hit at 8606.

This establishes a restart effect for the early hit, not an excuse for all subsequent failures. Two later tests of the narrower, uncommitted row-259 fix make that distinction:

  • E:/xenon_runs/level2-start-local-r143.validation/level2-start-local-r143.x2events starts at the early Level 2 checkpoint f7912-periodic.sav.
  • E:/xenon_runs/level2-continuous-local-r144.validation/level2-continuous-local-r144.x2events starts at assets/xenonplay.sav (frame 0), with no mid-level pilot restart.

Both pass the original lip without damage or the r136 stall. Both then take damage at 9097, 9217, 9310, 9324, 9449, 9519 and 9656, and lose a life at 9673. Player positions and scroll values match at those incidents, although object identity counters differ. The later failures are reproducible in continuous gameplay and cannot be blamed on restarting the checkpoint. The local change is not accepted as a successful Level 2 validation and has not been committed. All four probe recordings above have paired AVIs; r143/r144 also retain periodic checkpoints requested every 150 game frames.

Matched early-checkpoint comparison with committed gameplay

E:/xenon_runs/level2-start-committed-r145.validation/level2-start-committed-r145.x2events uses the unchanged e01b55c7 gameplay source, both native binaries rebuilt, and exactly the same f7912-periodic.sav start as modified r143. It reproduces the continuous baseline: four 4-point hits at 8851, 8911, 8915 and 8951, then the wall stall at 9005. Shield ends at 23 with three lives; recording stops at 9018 after detecting the stall. Both AVIs finalize normally.

Thus the committed pilot is not damage-free apart from the stall. The local change removes those early incidents but the modified run later loses a life. Since committed gameplay never reaches those later encounters in this test, the comparison does not establish whether their failures predate the change or result from its changed arrival route/timing. The modified pilot still fails validation; no further tactic changes were made for this comparison.

Historical Level 2 comparisons

The statement that committed gameplay does not reach later encounters applies to the current r136/r145 comparisons, not to every historical recording. Older resident-C recordings do pass the present stall area:

Recording Captured result Scope
E:/xenon_autopilot_runs/native-l2-first-contact-priority-20260918-r39.validation/native-l2-first-contact-priority-20260918-r39.x2events Frames 8146–9446: shield 39 throughout, three lives, no shield loss. Passes world Y=4136 at 9067 and Y=4024 at 9191; ends at (230,3812). Clean early-corridor comparison; stops before the bosses.
D:/src/hatari/xenon_tools/run_logs/l2-eye-static-full-20260922-r18.validation/l2-eye-static-full-20260922-r18.x2events Passes the same area with full shield. One 4-point shield loss at 9165, then reaches the three-eye encounter and stalls; no life lost. Newer comparison covering the corridor and first boss approach.

Both have the current entry loadout: Double Shot (0xC0), Cannon (0x34) and Rear Shot (0x44); neither relies on Side Shot. The September 18 r39 manifest identifies resident-c-autopilot, hatari-c, no per-frame Python controller calls and cheats disabled. Its starting snapshot is D:/src/hatari/l2-f8139-cleanstart.sav. Its recorded commit is 1717d0a3, but its archived source/native/ and working-tree.patch must be consulted because the commit alone does not describe the tested source. The September 22 r18 likewise contains archived native source and an uncommitted patch.

The additional E:/xenon_autopilot_runs directory contains 224 recordings, mostly September 17–19 experiments. Reports alone often contain only paths and frame bounds. Decoding the early-run captures identifies r39 as a clean comparison for the current corridor problem; it does not establish a clean full-level run. Audit data is saved in work/older-level2-extra-directory-audit.json. The r132 report names a recording that is absent, so it cannot serve as capture evidence.

Source comparison and controlled C evaluation

The archived r39 and r18 native sources were compared with committed e01b55c7, separately from the uncommitted row-259 experiment. The r136 archive matches HEAD's native files exactly. Level 2's embedded tile codes have not changed between r18 and HEAD; the later map-data edits concern Levels 4 and 5. Earlier r39-to-r18 corridor edits did change the reaction band from screen Y=104–128 to 144–164 and lane tolerance from 24 to 8, but those edits were already present in r18, which passed the current stall.

Several later changes have effects outside the bosses they were developed for:

  • e693fefa (September 29, "Fix native Level 4/5 tactics and tank cannon stall") added forecast Cannon rounds to the shared combat rollout. It also widened shared attack selection to installed Cannon lanes and nearby projectile sources. These additions are not restricted to Levels 4/5.
  • 5b5b5873 (September 23, "Improve Level 2 boss tactics and shop policy") changed retained_threat to require retained_commit. That commitment applies in the eye/final arenas, so the earlier corridor stopped retaining an escape against its still-live original threat.
  • 5c6944a1 (September 23, "Widen Level 2 spider firing lane") clears a neutral selected escape instead of preserving its state. This too affects the corridor, not just the spider.
  • 4a03e159 (October 1) extended the shared fire schedule from 32 frames to the configured horizon. This fixes truncated forecasting, but also changes decisions outside Level 5.

To distinguish these changes from different game timing, isolated DLLs were built with copies of the official hatari_dev.ps1 script under work/l2-source-comparison/. The real Hatari/replay DLL and working C source were not replaced. Each version consumed the same raw r18 observations and start.ram, through work/compare_native_history.py. Python only decodes recordings, serializes inputs and reads diagnostics; all decisions are C. The archived ABI-91 ctypes definitions were used with the archived DLL, and current definitions with the ABI-98 variants.

For l2-eye-static-full-20260922-r18.x2events, 1310 observations through frame 9446 give these control differences relative to archived r18:

C variant Different controls First difference
Committed HEAD 52 8753
HEAD with only the old attack-selection function 52 8753
HEAD with only the old 32-frame fire schedule 50 8753
HEAD omitting only newly forecast Cannon rounds 29 9104
Above plus old live-threat retention 7 9244
Above plus old neutral-escape retention 0 None

The archived DLL reproduces every recorded input from 8500 through 9000 when its output is compared with the next recorded frame's consumed input: 501/501 matches. HEAD similarly reproduces 501/501 in current committed level2-start-committed-r145.x2events, confirming the comparison's frame alignment and native serialization.

At r18 frame 8753, the mission goal is identical in old and current code, but archived C outputs DOWN+FIRE (0x82), while HEAD outputs UP+RIGHT+FIRE (0x89). Forecast damage changes from 2 to 4. Omitting only forecast Cannon rounds restores the archived control, and restores every control through 9103. At r145 frame 8465, archived C selects an up-right cash interception; HEAD selects a stationary plan. The same Cannon ablation restores the old interception on those observations. These differences precede the row-259 stall and occur outside the uncommitted experiment's world-Y scope.

The remaining difference at r18 frame 9104 occurs after the maneuver selection: its winning prefix is identical, but the old live-threat escape retention changes the final control from UP to RIGHT. Restoring that rule removes 22 of the remaining 29 differences. Restoring neutral retention removes the final seven. Together, the two retention changes and Cannon addition account for all tested control differences without reverting the later boss implementations or the fire-horizon fix.

This establishes the source of changed decisions, not a completed live regression fix. Captured observations retain the historical ship trajectory; different controls are not fed back into the emulator by this diagnostic. A matched live run is still required to prove that a scoped correction avoids the current damage/stall. The next correction should address corridor dodge retention and the new destruction forecast's effect on survival decisions, rather than layering another route threshold over the changed behavior. Cannon modeling should not be globally removed merely because its omission reproduces an older policy.

Follow-up investigation: Cannon allocation and encounter scope

The new Cannon forecast contains a concrete timing error, rather than merely a different tactical preference. xap_prepare_fire_schedule explicitly returns FIRE intent, not guaranteed projectile allocations. Its usual period is two frames. xap_prepare_candidate_fire forwards those times unchanged, and xap_step_combat creates a Cannon round at every ordinary forward intent time. No Cannon animation phase is consulted. The comment describing this as a conservative round per ordinary firing edge is therefore incorrect.

ReVa /mydumpat0 was checked against the captured Level 2 start.ram: the shared $4902 entry bytes match. The actual routine ticks animation $34C2, then allocates only when its current sprite is $29CBA ($4932..$493A). An idle sprite $29B5A can start the sequence at $2FE2 after a trigger at $A6A; it cannot allocate immediately. The sequence has four one-tick active frames followed by idle. $3E56 copies the input-edge latch $A01 to $A6A and clears $A01. A button pulse and a weapon allocation are different events.

work/audit_cannon_forecast.py audits recorded animation state and calls the isolated C fire-schedule API. In l2-eye-static-full-20260922-r18.x2events, frames 8500–9000 contain 110 Cannon births. Every next-tick allocation test matches the capture across 501 observations. Birth gaps are four frames (46 cases) or five frames (63 cases). At frame 8753, the following 56 frames contain 12 real Cannon births, while the current C intent-based forecast creates 28 rounds. The audit is a one-step allocation check from freshly captured phases; it does not validate a long rollout's future trigger-latch alignment. A fixed five-frame timer is not an exact replacement either.

All 110 first captured projectile anchors are (shipX-26, shipY-5) in this loadout. The signed attachment offset is read correctly by C. The projectile's rectangle and renderer centre differ from that anchor: for example at 8743, ship (77,136), anchor (51,131), collision rectangle (49,118)..(54,124). Do not label the rollout's extra upward offset a separate confirmed bug without accounting for this geometry and update order. Nor should ordinary/Cannon shots gain imaginary tile-map occlusion: the original object collision paths do not perform that general terrain check.

A second isolated build restores the pre-September-23 retention behavior only in the existing left-stream region, world Y=3360–4208. The two boss rules remain active in their respective encounters. On the r18 recording this removes all 29 early retention-related control differences. Continuing through frame 11683 changes 37 corridor controls but none of the 2691 observations outside that region. The extra eight changes occur later in the corridor than the initial comparison window.

A conservative combined ablation also withholds future Cannon allocations before the eye approach, world Y>=3360, while preserving observed Cannon bullets, installed-weapon aiming, and ordinary/side fire prediction. This experimental DLL is only for Level-2 recordings: its workspace Y gate lacks a level field and must not be copied into production as a global rule. A real implementation must select the policy in the Level 2 mission/navigation layer and pass that choice into scene preparation.

Comparison Observations Changed controls Changed diagnostic rows
Combined ablation versus archived r18, through 9446 1310 0 Not an equality requirement
Combined ablation versus HEAD, eye approach (Y<3360, Y>3100) 266 0 0
Combined ablation versus HEAD, three-eye arena (Y<=3100) 1810 0 0
Combined ablation versus HEAD, spider recording 814 0 0

The spider recording is D:/src/hatari/xenon_tools/run_logs/l2-spider-front-earlier-20260924-r1.validation/l2-spider-front-earlier-20260924-r1.x2events. These are captured-history comparisons, not new live runs or proof that a changed early trajectory will enter a boss with identical state/loadout.

The current r145 capture has another 19 earlier control differences at 8481–8502 after this narrow ablation. Reverting attack selection, combat-source selection, or the planner individually does not remove them. Omitting the new scheduled-source destruction resolution for ordinary anticipated shots removes 15, leaving 8499–8502. The relevant shared change is October 1's resolve(...source_target..., spawn_frame): a source killed before allocation can suppress its future shot. That is a physically justified rule, unlike creating Cannon rounds at every input pulse. These control differences alone do not establish another gameplay regression; do not revert the entire candidate evaluator or erase valid source-death modeling to force historical control equality.

Proposed production repair and acceptance

  1. Keep the September 23 boss improvements, including spider lane freedom, projectile dodging, centre entry, cash collection and shop policy. Restore live-threat and neutral-hold retention only in the early left-stream region Y=3360–4208. Recheck the retained action against current walls and hazards; release it when its original threat clears or the action becomes unsafe. Clear corridor retention on leaving its context.
  2. Deferred by the user on October 2: leave the current Cannon forecast enabled, including in this corridor. The observed-only Cannon policy above remains a diagnostic ablation, not the selected production repair. Its existing behavior has worked in other encounters; changing it now would expand this repair's scope.
  3. Future work, also deferred: prepare a proper future Cannon schedule from each captured attachment's animation phase/countdown and trigger latch. Advance that tiny state once per scene, independently of the forward/side intent schedule; reuse it across candidate rollouts. Admit it in the corrected corridor after allocation timing is checked against live observations. Enable it in other encounters only after their separate validation, rather than changing every boss's survival assumptions in the first repair.

Keep the fire-horizon correction and physically valid source-death rules. The uncommitted row-259 backscroll/pacing experiment is excluded from these diagnostic builds and should not be bundled with this repair: its earlier live attempts introduced later damage and a lost life.

Validation must rebuild both Hatari and its Python-observability DLL with the official script, then run resident C via validate_campaign.py native-run, visible, with both AVIs and 150-frame checkpoints. First replay from the clean Level 2 start, comparing the four current hits at 8851/8911/8915/8951 and the stall at 9005 with the baseline; follow world positions and threat identities if changed controls shift event frames. The archived r18 still has a later hit at 9165, so historical control equality is not itself a zero-damage criterion. Then run the three-eye and spider checkpoint suites and a complete Level 2 run, checking shields, lives, stalls, valuable pickups and boss cash. No production gameplay source or binary was changed in that diagnostic follow-up; all its DLLs are isolated under work/l2-source-comparison/. The October 2 production change and its validation are recorded below.

October 2: weapon-model findings and selected corridor repair

The user selected only the two corridor retention restorations. Cannon correction, disabling future Cannon rounds, and broader weapon-model work are postponed. The unaccepted row-259 backscroll/pacing experiment was removed from production before applying this repair; a copy remains at work/level2-row259-experiment-before-retention-20261002.c.

The production scope is level2_in_left_corridor: Level 2, player world Y=3360–4208, inclusive. Y=3360 is a navigation-region boundary, not an eye position. The broadly named eye approach is Y=3100–3360 and its arena is Y=2500–3100; ordinary landscape at Y=3360 is expected.

  • Retain the previous escape while its identified threat is still predicted to threaten the player. Unlike the boss commitment, this does not require a nonzero movement mask or a boss countdown.
  • Preserve a neutral least-risk hold in this corridor while the same threat remains relevant. Boss encounters still clear neutral retention.
  • Re-evaluate the retained action against the current map and hazard paths. A safe planned route releases the escape, as does a cleared threat. Outside the corridor the existing boss/other-region retention conditions apply.

No Cannon, fire schedule, boss route, shop, pickup, or cash rules are changed.

Deferred weapon forecast limitations

The combat estimator distinguishes already-existing projectiles from future projectile allocations. It is not yet a complete model of every attached weapon's behavior.

Mechanism Current implementation and limitation
Existing ordinary player bullets Reads each object's velocity and damage, adding the captured global damage bonus. Actual Double Shot bullets are therefore separate objects once fired.
Existing Cannon bullets Uses captured position/renderer centre, speed (0,-10), and fixed base damage 2 plus global bonus.
Future forward shots Creates one central surrogate at each configured FIRE intent; does not reproduce the two Double Shot lanes.
Future Cannon shots Uses each installed Cannon's captured X offset, up to two mounts, but shares the forward intent schedule instead of each Cannon's firing state.
Fire-rate upgrades Explicit regular-gun cooldown/reload/rate fields exist at $C60/$C62/$C64, but the future allocation schedule does not consult them.
Bullet shape Combat rollouts test projectile points against target rectangles; actual projectile width/height and upgrade-specific shapes are not reproduced.
Weapon power Attachment maximum/current power and shop type exist at object +$32/+$42/+$44; the forecast does not reproduce all power-dependent weapon behavior.
Other attachments Aiming includes Laser mount offsets, but the destruction rollout does not generate future Laser, homing-missile, Rear Shot, or Electroball streams as independent weapon models. Already-live ordinary shots are admitted when they use the supported handler.
Target health Supported ordinary damage callbacks expose health. Predicted hits subtract damage and health zero gives kill/disappearance credit; special boss vulnerability rules are not universally reproduced by this kernel.

Relevant code: xenon_autopilot_combat_prepare.c, xenon_autopilot_combat.c, xenon_autopilot_observation.c, and xenon_autopilot_session_drive.c. Aiming recognizes more weapon geometry than the destruction rollout currently simulates.

For Cannon, animation is internal firing state, not a visual heuristic. The original $4902 handler uses object +$16 (current sprite pointer), +$1A (animation countdown), and +$1C (animation-table cursor). Its sequence at $2FE2 is $29C0A wind-up, $29CBA firing, $29D38/$29DCA recovery, then $29B5A idle. The active entries have duration 1; idle has duration 0. Allocation is explicitly checked against $29CBA. $A6A is a trigger-edge snapshot copied from $A01, not simply the current held FIRE button.

A future correction should seed this small per-attachment state machine from RAM and model trigger/update ordering; sprite ID alone is insufficient. Prepare shared weapon schedules once per scene and reuse them across maneuver rollouts. The 501 next-tick checks and the 28-estimated-versus-12-real example above establish the current Cannon timing problem, but do not validate a complete future firing-state model. Do not replace it with an arbitrary five-frame timer or change all encounter behavior as part of this corridor repair. These findings are retained for later work.

October 2 validation: retention restored, corridor stall remains

Both production binaries were rebuilt from the scoped sources using xenon_tools/hatari_dev.ps1: build-kernel and build -BuildType Release. The first sandboxed DLL build left an idle Ninja with no compiler; only that build's Ninja and launcher were stopped before the documented unsandboxed retry. Neither the combat kernel nor its future Cannon generation changed.

Captured-history evaluation with the production DLL matches the previously isolated corridor-retention DLL on all 3547 r18 observations through 11683. Relative to unchanged HEAD, it changes 37 corridor controls and none of the 2691 observations outside that region. All 814 spider observations, including decision diagnostics, remain identical to HEAD. This checks scope on captured history; it does not establish identical later gameplay after an earlier trajectory change.

test_native_mission, test_native_driver, and test_native_session ran 142 tests: 138 passed and four failed. The identical four failures were reproduced against the isolated unchanged HEAD DLL:

  • test_early_level_pickups_precede_formations_and_only_rear_is_excluded
  • test_level2_eye_stages_in_upper_right_before_eye_enters_view
  • test_level2_selects_other_upper_pocket_when_route_is_blocked
  • test_level2_waits_at_upper_pocket_until_eye_route_is_clear

The first live attempt, level2-corridor-retention-20261002-r146, did not advance from 7912 and lost its control connection. Its process console was in Windows Select mode. This startup failure is not gameplay validation. The failed instance was closed; the retry used the official visible launcher with -NoWinConsole, then validate_campaign.py native-run --connect.

Actual gameplay recording: E:/xenon_runs/level2-corridor-retention-20261002-r147.validation/level2-corridor-retention-20261002-r147.x2events. It starts from the same early save as r145: E:/xenon_runs/full-campaign-native-release-20261001-r136.validation/checkpoints/f7912-periodic.sav. Hatari ran the resident C pilot in visible, Release, fast-forward mode. The validator recorded both AVIs and retained checkpoints at the requested 150-frame minimum interval. It stopped on the detected wall stall, before reaching either boss.

Frame Incident in r147
8904 Shield 39 -> 35, four points of damage.
8912 Shield 35 -> 31, another four points of damage.
9007 Repeated wall contact near world (138,4139); episode began at 8858.

No life was lost. The nearby HEALTH POWER 1 pickup was missed at 8895 after leaving the playfield; its closest recorded gap was 18 pixels at 8892. Restoring retention alone therefore does not resolve the corridor stall. The future Cannon model remains enabled at the user's request, and the unaccepted row-259 pacing experiment was not reintroduced to mask this result.

The recording finalized with no cleanup errors. Both AVIs contain 3771 frames, valid RIFF sizes, contiguous VBL sidecars, and decodable 640x400 first/last images. The final cleanup observation reports the pilot stopped; all 1095 gameplay records identify resident native control. The completed Hatari instance was closed after finalization. Audit: work/level2-corridor-retention-r147-audit.json. Trace: E:/xenon_runs/level2-corridor-retention-20261002-r147.trace.

Reproduction after rebuilding both binaries, from the repository root:

$env:XAP_LEVEL2_TRACE_FILE='E:/xenon_runs/level2-corridor-retention-repeat.trace'
powershell -NoProfile -ExecutionPolicy Bypass -File xenon_tools/hatari_dev.ps1 run -BuildType Release -Snapshot E:/xenon_runs/full-campaign-native-release-20261001-r136.validation/checkpoints/f7912-periodic.sav -ControlPort 6903 -NoWinConsole
python xenon_tools/validate_campaign.py native-run level2-corridor-retention-repeat --connect --output-root E:/xenon_runs --port 6903 --build-type Release --fast-forward --checkpoint-interval 150 --stop-at-level 3

This repair is uncommitted pending review. No further Cannon or tactical changes were made after the failed corridor validation.

October 2 follow-up: restrict retention changes to boss encounters

At the user's request, the narrow corridor exception above was replaced by positive boss-encounter scope. xap_level2_hazard_escape now applies the September 23 restrictions only when the three-eye arena mission is active (boss_active inside its authored arena), the spider controller reports an active timer, or the retained spider fight identifies the same encounter across a controller gap. Ordinary approach, maze and swarm-route sections use the original live-threat/neutral-hold retention behavior. The original timed boss movement commitments are preserved. No ABI or Cannon-model change is involved. The spider-controller test is cached within this function rather than scanning the object list again for the same decision.

Both the production DLL and Release Hatari were rebuilt with the official script. All 21 native driver/session tests pass. Captured-history comparison against unchanged HEAD shows identical controls and diagnostics for all 1530 three-eye arena observations in r18 and all 814 spider observations. The 47 changed r18 observations are in ordinary navigation regions, including four in the eye approach before entering the arena. These are scope checks on captured observations, not proof of a complete live Level 2 pass.

The visible native rerun is E:/xenon_runs/level2-boss-scoped-retention-20261002-r148.validation/level2-boss-scoped-retention-20261002-r148.x2events. It starts at 7912 from the same early save as r147. It reproduces r147's hits at 8904 and 8912, the missed health pickup at 8895, and the wall stall reported at 9007, near (138,4139). Final shield is 31 with three lives; the validator stops at approximately 9010. Both AVIs finalize without cleanup errors and have 3782 indexed frames, contiguous VBL tags, valid RIFF lengths and decodable endpoints. The completed emulator instance was closed. Trace: E:/xenon_runs/level2-boss-scoped-retention-20261002-r148.trace. Audit: work/level2-boss-scoped-retention-r148-audit.json.

The stall is an independent conflict between route geometry and corridor steering, not evidence that the boss restriction is still being applied here. The refreshed C geometry probe at r147 frame 9007 is work/level2-boss-scope-r147-stall-probe.json; reproduce it with:

python work/probe_level2_corridor_stall.py E:/xenon_runs/level2-corridor-retention-20261002-r147.validation/level2-corridor-retention-20261002-r147.x2events --first 8930 --last 9007 --ram E:/xenon_runs/level2-corridor-retention-20261002-r147.validation/checkpoints/f8930-periodic.sav.ram --output work/level2-boss-scope-r147-stall-probe.json

At that observation:

  1. The retained route's next point is approximately (140,4148), requiring a short Down retreat before crossing right. The live guard trace's proposed=2 is the incoming evaluated movement mask, not the mission's proposal field: the driver reaches the guard with Down selected. The consumed game input is 0x80 (Fire only).
  2. level2_set_corridor_pacing replaces the connector's next X with [260,268], while preserving the retreat Y [4147,4149]. It also installs screen band [144,164]. The goal therefore asks for a premature lateral crossing; it no longer describes the route's first leg.
  3. xap_level2_corridor_guard treats screen Y=176 as outside that band and replaces Down with Up if its span check passes, otherwise zero. Up points into the wall lip. Horizontal movement also fails the local clearance check. The selected terrain retreat is discarded before hazard arbitration.
  4. Hazard arbitration cannot restore it: level2_clamp_escape converts Down to Up when the hull bottom reaches screen Y=184. Its short span-only clearance check also expects an occupied start to become a free span cell immediately; it does not represent the complete camera-backscroll escape.

The refreshed C camera/terrain forecast proves that 16 consecutive Down inputs are terrain-clear, taking world Y from 4138 to 4152 and scroll from 3963 to 3975 while screen Y stays at 176. The final anchor is free. Backscroll is available; its initial camera movement cannot be judged by screen Y alone. This remains a terrain proof, not a proof that an arbitrary incoming volley can be ignored during the escape.

The appropriate subsequent repair is to preserve the connector's first leg and let a terrain- and hazard-validated retreat from this occupied start take precedence over the corridor band/lane preference. It must allow Down to backscroll at the camera edge and validate the multi-frame escape with the existing C wall evaluator. Restore normal pacing once the anchor has cleared the lip. Do not reinstate the rejected whole-row pacing change or replace it with an unconditional retreat that bypasses projectile safety. This follow-up applies boss scope and analyzes the stall; it does not claim to have fixed the remaining connector/guard conflict. Changes remain uncommitted.

Equipment and shops

The subsequent local connector repair and full Level 2 replay are recorded in LEVEL2_CORRIDOR_STALL_20261002.MD. The wall-lip stall is removed, and the continuation reaches Level 3, but there are still 17 shield-hit events and two lost lives. This is not a clean gameplay validation pass. The Cannon model remains unchanged.

The subsequent death investigation is in LEVEL2_LIFE_LOSS_ANALYSIS_20261002.MD. It separates the older unsafe three-eye transfer and post-shop swarm trap from the measurable effects of later shared combat changes on the approach.

Captured retirement beside the ship supports collection of both initial Speedups, POWERUP, Rear Shot, Zapper and Cannon in Level 1. Installed loadout changes independently confirm the weapon changes. The Cannon appears at 5000 and is collected around 5017; Zapper is collected around 3327.

Missed health pickups:

Frame retired Level Pickup Context
4356 1 HEALTH POWER 1 #7977 Shields were already full at 39.
8895 2 HEALTH POWER 1 #17037 Appeared at 8839; the ship was becoming trapped and taking damage.

The pickup audit reports 44 Level 1 cash pickups likely collected and 23 missed; Level 2 reports five likely collected and thirteen missed before the run stops. These classifications are observational: ordinary captures do not contain an explicit pickup collection event.

The existing shop-report script records:

Shop Frames Purchases Cash on entry -> exit
Level 1 first shop 2041-2919 None 750 -> 750
Level 1 second shop 6908-7899 Double Shot at 7496, replacing Forward Shot 3500 -> 500

Cannon and Rear Shot remain installed when entering Level 2. No Side Shot was obtained before this run's stop. The full shop report is work/full-campaign-r136-shops.md; the observational audit is work/full-campaign-r136-audit.json.

Recording integrity and reproduction

Both r136 AVIs finalized without cleanup errors. Each contains 41483 indexed frames and matching contiguous VBL tags; first and last images decode at 640x400. They correctly use two OpenDML RIFF segments, whose sizes cover their files. Original size is 1245929235 bytes; Sprite Stream size is 1248839972 bytes. The finalized Hatari instances were closed. Twelve targeted campaign option and incident-monitor tests passed after adding the continuation option.

Run from the repository root with a fresh campaign name:

$env:XAP_LEVEL2_TRACE_FILE='E:/xenon_runs/full-campaign-repeat-level2.trace'
$env:XAP_LEVEL5_TRACE_FILE='E:/xenon_runs/full-campaign-repeat-level5.trace'
python xenon_tools/validate_campaign.py native-run full-campaign-repeat --output-root E:/xenon_runs --resume assets/xenonplay.sav --port 6903 --build-type Release --build --fast-forward --checkpoint-interval 150 --continue-after-life-loss --continue-after-stall
python work/audit_native_campaign.py E:/xenon_runs/full-campaign-repeat.validation/full-campaign-repeat.x2events --output work/full-campaign-repeat-audit.json
python xenon_tools/report_shop_decisions.py E:/xenon_runs/full-campaign-repeat.validation/full-campaign-repeat.x2events --output work/full-campaign-repeat-shops.md

work/audit_native_campaign.py only reads captured telemetry and finalized videos. It does not invoke Python or C decision evaluation.