Autopilot · write-up
Level 2 validation after pulling xenon2 — October 2, 2026
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.jsonwork/l2-source-comparison/r174-stall-probe.jsonwork/l5-boss-level2-merged-full-20261002-r173-9068.png— authentic AVI hit framework/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.