Xenon 2

Autopilot · write-up

Level 2 validation after pulling xenon2 — October 2, 2026

xenondoc/LEVEL2_MERGED_VALIDATION_20261002.MD · 12 KB · updated 2026-10-05

Follow-up C navigation changes and their separate live results are documented in LEVEL2_ROUTE_ARBITRATION_20261002.MD. The baseline recordings and findings below remain evidence for the unchanged merged commit, not claims about that later implementation.

Result

Level 2 is not a clean full-level pass. The run from the beginning takes three projectile hits and stops at a persistent corridor stall. A separate pre-boss start clears the three-eye boss without damage, then stalls after Shop 1. A third start from the saved Shop 1 exit defeats the spider and reaches Level 3 without damage or life loss, but misses Zapper and two boss cash drops. These continuations do not establish an uninterrupted successful full replay.

Branch: xenon2, commit 12d0abbf6aeed363de00ee0ef54e13def2adac27. The pull includes 62268dfd, our native Level 2 repairs. Both the Release Hatari executable and native replay DLL were rebuilt through xenon_tools/hatari_dev.ps1; the current native ABI is 100. No gameplay source was edited during this validation.

All three runs use validate_campaign.py native-run, the resident C pilot, visible Sprite Stream window, fast-forward, both AVIs, Level 2 tracing, checkpoints every 150 frames and --stop-at-level 3. Python observes and audits the captures; it does not make the live gameplay decisions. Both AVIs for each run finalized without cleanup errors, have continuous VBL indexes and decode at their first and last frames. The owned Hatari instances were closed after recording finalized.

Recordings

Run Recording Restored frame Last captured frame Outcome
r173 level2-merged-full-20261002-r173.x2events 8136 12085 Persistent early-corridor stall; 12 total shield damage, partly recovered by a health pickup; final shield 31, three lives retained
r174 level2-merged-remainder-20261002-r174.x2events 9990 15522 Three-eye boss cleared; post-shop corridor stall; no shield decrease or life loss
r175 level2-merged-postshop-20261002-r175.x2events 13040 17884 Spider cleared, Shop 2 passed, Level 3 gameplay reached; shield 39 unchanged, two lives retained

Each linked recording's folder contains level2-boss.trace, incidents.jsonl, incidents.md, driver-result.json, manifest.json, source snapshots, start.sav, start.ram, checkpoints and the -original.avi / -sprite.avi pair. Actual first captured game frames are 8138, 9992 and 13042 respectively.

The lives and loadouts at the start of each continuation belong to its source snapshot. The third run's two starting lives are not a life lost during these tests.

Problems to review

Run Game frame Problem
r173 9068 Directional projectile #17590 costs four shield, 39 -> 35.
r173 9154 Directional projectile #17830 costs four shield, 39 -> 35 after the intervening health recovery.
r173 9219 Directional projectile #18071 costs four shield, 35 -> 31.
r173 9244 Monitor reports repeated wall collisions near world (95, 4162), beginning at 9095.
r173 9981 Monitor confirms no meaningful position or score progress since 9082; the run eventually stops at 12085.
r174 12570 Monitor reports repeated wall collisions near world (106, 2395), beginning at 12421.
r174 13410 Monitor confirms no meaningful position or score progress since 12511; the run eventually stops at 15522.
r175 14359 Zapper #36211 expires uncollected; closest recorded collision-box gap was 11.7 pixels at 14321.
r175 16526 Spider cash #46088 expires uncollected despite a one-pixel gap at 16520.
r175 16551 Spider cash #46078 expires uncollected despite a two-pixel gap at 16546.

r175 also contains a collision-time callback at 14713. Shield stays at 39, and HEALTH POWER 1 #37655 retires beside the ship at that frame. This is not a measured shield-loss incident.

Native geometry evidence for the stalls

The probes replay captured observations into the just-built C DLL and inspect native routes and map queries. They do not run a Python planner.

At r173 frame 9244, the ship is near (95,4163) and the native physical and span tests both classify the current reserved hull position as blocked. The connector begins approximately (92,4164) -> (92,4168) -> (80,4176) before crossing right. The exported mission goal instead asks for X [260,268] at Y [4163,4165], with screen reaction band [144,164]. Every tested immediate hold or cardinal step fails the physical map check. The existing local xap_level2_corridor_retreat exemption requires the goal's minimum Y to exceed the player's Y by more than two pixels; this goal does not qualify. The verified retreat preservation therefore does not cover this observed corner.

At r174 frame 12570, the current pixel-mask anchor is blocked, while the rounded span anchor is free. The route asks for a retreat toward (104,2404) before moving right toward (180,2400). The native one-step probe accepts Down, but rejects Up, Left and Right. The live trace nevertheless reports:

frame=12570 y=2396.0 screen=(106,176) lane=168.0 scope=active planned=0 risk=0.0 first=-1 selected=0 risk=0.0 first=-1

The ship is already at the bottom screen edge. Route recovery needs to retain a safe downward/backscroll step before lateral movement; a zero-risk neutral hold does not clear this wall corner. The first lip's preservation rule is limited to world Y 4096..4208 and cannot protect this post-shop corner.

This establishes two route/recovery failures. It does not establish that the merge introduced them: a controlled old-source/new-source replay from the same initial state has not been performed for these two trajectories.

Was the earlier lip fix lost?

No. The fix is in 62268dfd, merged by 95911401. The retreat recognition, corridor-pacing exemption, corridor-guard exemption and driver preservation remain in the current sources. On the exact original r147 trapped observation at frame 9007, replayed with its checkpoint RAM from frame 8930, the current ABI-100 DLL still chooses Down (controls=2), preserves goal (140,4148) from player (138,4138), and confirms that 16 downward forecast steps clear the lip. Evidence: work/l2-source-comparison/r147-trapped-current-dll-probe.json.

The new r173 trap is a different starting anchor in the same wall region: player (95,4163), first connector (92,4164). That approximately one-pixel initial retreat does not satisfy the shared goal.y_min > player.y + 2 recognition check, even though later connector legs retreat farther. Corridor pacing consequently installs X=264 again. The current reserved hull anchor also fails physical map checks, exposing an additional recovery requirement. The earlier successful run verified one retreat geometry; the fix was too narrow to establish a general solution for this lip.

The full r150 replay started at frame 7912 from the October 1 campaign save; r173 started at 8136 from the September 16 Level 2 entry save. They are not identical-state before/after tests. This explains why their route anchors cannot be assumed identical, but does not by itself prove why the approach diverged. No gameplay code was changed while checking this question.

Probe artifacts:

  • work/l2-source-comparison/r173-stall-probe.json
  • work/l2-source-comparison/r174-stall-probe.json
  • work/l5-boss-level2-merged-full-20261002-r173-9068.png — authentic AVI hit frame
  • work/l5-boss-level2-merged-full-20261002-r173-9244.png — authentic AVI wall-stall frame

Bosses, equipment and cash

r174 confirms the critical POWERUP #21803 is collected at 10014: the forward Double Shot power changes from 0 to 1. All three eyes are cleared by 11187, without a shield decrease. All ten cash pickups released at that frame are classified as likely collected before Shop 1.

r175 confirms SIDE SHOT acquisition at 14726, replacing the existing rear attachment. No rear-cannon pickup is collected. The spider is confirmed defeated at 16442; its twenty cash pickups appear at 16441. Eighteen are classified as likely collected and two are missed, listed above. The ordinary post-shop route also misses eleven cash pickups. The early r173 corridor misses sixteen cash pickups and likely collects two. Loadout data does not show an unintended lasting weapon loss; the empty slots during Level 3 loading are restored when gameplay begins.

Pickup collection is inferred from retirement near the ship and checked against inventory transitions where applicable. Expiry below the viewport supports the missed-pickup findings directly.

All other missed cash in the recorded intervals:

Run Expiry frame Cash identity
r173 8728 16030
r173 8739 16055
r173 8765 16071
r173 8801 16211
r173 8832 16441
r173 8834 16496
r173 8843 16333
r173 8845 16528
r173 8856 16417
r173 8909 16733
r173 8913 16758
r173 8929 16817
r173 8958 16847
r173 9002 17072
r173 9003 17144
r173 9050 17213
r174 10016 21640
r174 12430 27985
r174 12563 28476
r175 13913 34430
r175 14138 35307
r175 14267 35802
r175 14441 36527
r175 14530 36881
r175 14607 37134
r175 14626 37211
r175 14821 38095
r175 15009 38864
r175 15083 39299
r175 15129 39433

Shop reports were generated with xenon_tools/report_shop_decisions.py:

  • r174 Shop 1, frames 11255..12221: cash 1350 -> 850, attachments unchanged; shield is restored from 23 to 39 during the visit. The attachment-only transaction reporter does not name this consumable purchase.
  • r175 begins during Shop 1's exit, so it is not a second complete Shop 1 purchase test. Shop 2, frames 16552..17525: cash 4500 -> 500; Laser is bought at 17146, alongside the retained Double Shot, Cannon and Side Shot.

Audit JSON and shop reports: work/l2-source-comparison/r173-audit.json, r174-audit.json, r175-audit.json, r174-shops.md and r175-shops.md.

Reproduction

The full-start command was:

$env:XAP_LEVEL2_TRACE_FILE = 'E:/xenon_runs/level2-merged-full-20261002-r173.validation/level2-boss.trace'
python xenon_tools/validate_campaign.py native-run level2-merged-full-20261002-r173 --output-root E:/xenon_runs --resume D:/src/hatari/xenon_tools/run_logs/native-level2-full-20260916.validation/checkpoints/L2-f8136-start.sav --build-type Release --build --fast-forward --checkpoint-interval 150 --stop-at-level 3 --continue-after-life-loss --continue-after-stall --port 6903

For another run, choose a new output name and corresponding trace path. r174 restores E:/xenon_runs/level2-upper-crossing-20261002-r169-f9990.validation/start.sav; r175 restores E:/xenon_runs/level2-postshop-regression-20261002-r172.validation/start.sav. Both use the same options, omitting --build because both binaries had already been rebuilt from this commit.

The next priority is the two corridor recovery failures, followed by Zapper and the final two spider cash pickups. Neither boss requires a speculative combat rewrite based on these recordings.