Xenon 2

Autopilot · write-up

Autopilot architecture review: reduce decisions and queries before porting

xenondoc/AUTOPILOT_ARCHITECTURE_REVIEW.MD · 28 KB · updated 2026-09-17

2026-09-05. Analysis of working-tree revision 167661a3b5d5f7680d69961b35b8a4500eb01d8e. No production code changed. The proposal below changes the planning policy; it is not a claim of a measured speedup or a completed replacement controller.

Recommendation: retain the reverse-engineered movement/collision model and the accumulated encounter knowledge, but replace the default “fully score nine directions, then sometimes search again” loop with a persistent maneuver plan, one shared trajectory/collision service, and search invoked when that plan needs repair. Represent scripted formations as shared paths with member phases, and represent encounters as regions and timed crossings. Port the resulting bounded runtime after measuring it end to end.

Evidence and corrections to the existing diagnosis

I read the requested overview, autopilot, profiling, C-port, decision-cost and distance-field documents, then traced choose, _score, prediction, escape search, game_model.py, map navigation and the existing Level-2 navigation field. The documentation contains historical descriptions; current code takes precedence when they disagree.

The saved full-campaign-flow-field-1.validation/offline-profile.pstats confirms:

Work Count / instrumented time Implication
Entire profile 807,142,943 calls; 227.915 s Attribution data, not phone or uninstrumented timing.
_score 20,862 calls; 143.385 s cumulative; 32.265 s self Nine calls on each of 2,318 scored decisions. Millions of calls occur inside scoring, not to _score itself.
Contact tests directly from _score 11,259,243; 3.849 s cumulative About 4,857 tests per scored decision. The contact predicate alone is a small part of scoring.
math.hypot directly from _score 11,261,799; 1.146 s Removing square roots alone cannot solve the problem.
Scoring calls to collision-player hull helpers About 2.16 million; 12.748 + 8.288 s cumulative at these two call sites Repeated player geometry is significant despite cached physics. These totals include their children.
_formation_escape_decision 2,318 calls; 40.313 s cumulative Calls include gates and retained-plan validation, not necessarily fresh searches.
_advance_model_candidate 2,118,867 calls; 23.221 s cumulative Physics work is also multiplied by planning.
Script interpreter 5.409 s cumulative Sharing script prediction alone has limited leverage in this sample.
Navigation grid / A* 17.310 s cumulative grid; 7.016 s cumulative A* A separate cost axis; grid calls are mostly cache checks, not rebuild counts.

Do not add parent and child cumulative times. Even eliminating the whole script interpreter would remove only about 2.4% of this recorded interval. Even eliminating all scoring leaves roughly 85 seconds of the interval. Both recurring scoring and search/reconstruction tails need attention.

Several proposed optimizations already exist:

  • _predicted_anchor (20246) follows the actual linked formation relation and memoizes (object, horizon). A scripted leader's series is built once and its anchors are cached for all horizons (20311). This is already shared leader prediction, although downstream collision checks still visit members.
  • _scoring_hazard_sweeps (19190) shares geometry across all nine candidates. _scoring_wall_shot_sweeps (19261) does the same for generic anticipated shots.
  • The cost HTML overstates “every hazard gets the longest horizon.” The body-sweep builder already excludes many ordinary classes after prediction_frames. Long-lived swarm/formation classes still inherit the shared limit, and player projections and several other loops still run to that limit.
  • _score already materializes one-, four-, and eight-input ship trajectories (25237 onward). But it reconstructs collision hulls and scroll corrections on top of them; it also queries different assumed continuations for different hazards.
  • Escape search already gates work, retains action prefixes, revalidates them, shares predicted geometry, and caches geometry for converged states. It is not an entirely uncached search. The retained-plan path is nevertheless reached after all nine _score calls (choose: 5246, escape dispatch: 7115).
  • Tactical navigation windowing and bounded shot-path tile lookup are implemented. Level 2 already has an offline navigation-field reconnection path (9115). Build on these instead of proposing them as new features.

The C-port plan says every beam node needs its true distance to every hazard. That is too strong. Collision queries can reject spatially separated groups; clearance ranking is capped at 96 (node_rank: 15017); and the current loop calculates minimum clearance before rejecting an already-colliding child (16074–16164). Those rejected children need no comfort score at all.

A new work census on the current source

I sequentially replayed the same recording from its beginning, including its automatically loaded terrain seeds, and instrumented frames 1979–1994. This is cold-policy reconstruction from the recording start, not a branched playthrough. The instrumentation calls the original functions and does not change decisions.

Counter Observed
Scored frames / _score calls 16 / 144
Shared horizon slices / hazard entries 896 / 6,388
Scoring contact queries 57,492 = 9 × 6,388
Queries reporting contact 995
No contact and center distance >= 70 49,304 — 85.76%
No contact and either center-axis distance >= 70 46,691 — 81.21%
Repeated identical rectangle pair within a frame 25,703 — 44.71%

The shared entries comprise 2,464 swarm entries, 3,920 formation-follower entries, and four destroying-hostile entries. This is one short Level-1 sample, not a campaign-wide distribution. Repeated-pair counts ignore time and identity: they indicate reusable pure geometry, not permission to merge damage sources or exposure accounting. These categories overlap; their percentages cannot be added.

The important finding is that “off-screen culling is exhausted” does not mean pairwise relevance is exhausted. An object can be relevant somewhere in the playfield while being irrelevant to this maneuver at this time.

Reproduction: python xenon_tools/run_logs/architecture_audit_0905.py. The script and JSON report are under ignored run_logs; the report records the source SHA-256. These counters are not timings and do not establish that adding a Python spatial index will be faster.

Why the current planning question is expensive and sometimes misleading

One joystick choice is currently assessed using several futures: apply it once, apply it four times, or apply it eight times, then neutral inputs with continuing ship/camera dynamics. Different hazard classes see different player futures. The result is a useful accumulated heuristic, but it does not certify one action sequence that the ship will actually execute. The later beam solves that separate problem with real sequences, then next frame starts again with the nine heuristic evaluations.

This is where I would change the architecture. A mission should ask “can I keep following this route, hold this firing station, or execute this crossing?” The survival service should validate that concrete continuation. It should search for a replacement only when validation fails or a deadline/encounter transition makes the current continuation inadequate.

Long prediction remains necessary. The previous failures at corridor exits, swarm arcs and homing spawn gates are evidence against simply setting the horizon to eight. We should compress the work within the horizon and reduce how often we search it, not silently remove the future that caused those failures.

Proposed control flow

flowchart TD
    O[Canonical observation and semantic events] --> M[Retained mission: region, phase, deadline]
    O --> P[Shared prediction service]
    M --> V[Validate retained maneuver and continuation]
    P --> V
    V --> G{Valid and making progress?}
    G -->|yes| I[Issue next input]
    G -->|no| L[Try a small set of mission and escape maneuvers]
    P --> L
    L --> C{Verified continuation found?}
    C -->|yes| R[Retain plan and its dependencies]
    C -->|no| B[Budgeted local search with exact model]
    P --> B
    B --> R
    R --> I

This requires a real separation of the current policy code. Many tactic branches consume Decision values today. First split cheap immediate legality and mission preference from expensive future hazard evaluation; otherwise a supposed fast path will still pay _score indirectly. Preserve mission ownership, monotonic phase transitions and the installed-weapon semantics while changing their output to a region/continuation contract.

1. One prediction service, with explicit dependencies

Expose trajectory data through compact integer-indexed arrays and query methods, shared by policy validation, maneuver search and emergency search. Avoid passing a large tracked-object graph through every geometric operation.

Prediction family What can be shared What must stay conditional
Static walls/gates Whole-level topology; local collision mask Changed destructible gates and collision geometry revisions
Deterministic scripts, linked formations Script path, member phase/delay, bounds and time intervals Model validity, live membership, animation collision shape
Screen-relative scripted motion Intrinsic script coordinates Projection using candidate camera state; transform the query where valid instead of allocating translated hazards
Aimed-at-spawn shots Source schedule and flight model; frozen shot after allocation Aim sector/state at allocation; branches with different aims remain different
Retargeting/homing enemies Evolution until the next player-dependent event, when equivalent Retarget result, projectile state, exact collision order
Destructible missiles and Side Shot Exogenous geometry and schedules Health, bullet allocation/consumption, fire phase and ordered interactions
Unknown/RNG transition Verified prefix and conservative possible occupancy No invented deterministic suffix

Model dependencies can combine: an aimed emitter may also be camera-relative and its projectile destructible. This should be metadata/state, not five independent class allow-lists that drift between scoring, beam and revalidation.

Use a rolling horizon for verified deterministic trajectories: compare the next observed state with the forecast, advance the ring head, append a new tail, and invalidate only affected dependencies when they differ. Store simulation state, not just positions: fixed-point fractions, cursor, phase, animation and active state determine the suffix. Camera-relative paths need a separate coordinate transform; cached world rectangles cannot simply be shifted through all cases.

An unknown script command or RNG branch ends the verified prefix. The current script interpreter falls back to its last tangent on unknown transitions (26161 onward); that is a forecast, not a sound long-term safety certificate. Capturing an RNG seed alone would not solve this: other game events consume the shared stream. Keep speculative simulation independent of live game mutation.

2. Treat formations as shared trajectories and spatial groups

The user's leader/follower observation is valuable, but there are two distinct mechanisms to preserve:

  • $502C2 followers copy their linked successor's earlier anchor. Predict a root once and represent each member as a path reference plus a delay, with the correct screen/world phase adjustment. Seed the initial delayed positions from the actual live chain; leader history may be incomplete when capture starts.
  • Independently scripted swarm_enemy objects are not necessarily pointer-linked followers. Share a script template/phase only when captured script state, offsets, update convention and phase prove that relation. Sharing a sprite or risk label is insufficient.

The new saving is downstream: put the members behind a hierarchy of bounds over short time intervals. Query a formation first; reject all its members if the player maneuver is separated. Descend to subgroups and exact member rectangles only near a possible encounter. Preserve member identities, alive intervals, collision hulls and gaps. A single solid AABB around a winding worm would close safe passages; use it only as a broad rejection test.

For an established firing/refuge lane, project members onto that region and compute blocked time intervals. Delayed copies of one path give shifted occupancy intervals, so a “cross behind the tail” decision can be evaluated as a crossing window rather than hundreds of independent distance scores. Intervals still have to incorporate hull size, motion during the crossing and the actual camera path.

This can change root motion simulation from member-by-member work toward groups × horizon, but exact collision work does not become independent of member count in dense overlaps. Count group rejections and member visits to find where the representation earns its cost.

3. Replace exhaustive scoring with staged feasibility and bounded preference

Use separate answers for:

  1. Physical contact and first contact time, with exact collision phases.
  2. Required reserve / whether enough escape space remains.
  3. Mission progress, firing opportunity and pickup benefit.
  4. Optional comfort and diagnostics.

For a collision-free maneuver, aggregate safety over the whole path and rank mission progress. Do not compute overlap exposure for every alternative merely to discover that a retained maneuver is still acceptable. If no collision-free continuation can be verified, use the existing time-to-contact/exposure logic as an explicit degraded decision policy; preserve separate body damage sources.

There is also a useful transitional optimization without redesigning the entire rank. Current soft proximity costs vanish beyond center distance 54; firing-lane relaxation asks nearest < 70. The exact minimum beyond 70 is only diagnostic in the inspected controller. Query contact geometry and a bounded center-distance neighborhood separately. Inside the neighborhood, preserve the existing distance and per-group accounting; outside it, an exact numeric nearest distance need not be part of the control loop. An on-demand diagnostic can still calculate it.

Do not confuse those center-distance thresholds with hull clearance. A large rectangle can contact the player while its center is farther than 70. A valid rejection must prove both no hull contact and no relevant proximity signal. Likewise, keep anticipated shots and camera corrections in the query.

Start with cheap interval/AABB bounds around the actual maneuver and short time blocks. For small nearby sets, a flat array scan may beat a tree/grid. Build a spatial index only when measured query reuse amortizes its construction. For linear fixed-shape segments, relative-motion interval tests can skip long runs of separated frames; split at steering changes, camera clamps, bounce, spawn and retarget events, and reproduce discrete/inclusive game contact at the boundary.

In the existing beam, reject proven collision before calculating comfort, and stop nearest-clearance search when a bound proves remaining hazards cannot improve the capped result. Geometry can be shared across equal rect pairs while the caller still attributes each contact to its own identity and time.

4. Search meaningful maneuvers, with an executable continuation

Try the shifted retained plan first. Then try a small ordered library: follow the next route bend, hold/reacquire the weapon lane, move left/right to a verified refuge, cross behind a formation, or bait an aimed shot then change direction. An initial experimental library of perhaps 6–12 alternatives is a tuning choice, not a completeness guarantee. Each has actual joystick steps and a termination condition; it is not another hazard-specific guessed player projection.

Use macro edges between decision events/regions to reduce repeated search branching and sorting. Still validate every intervening canonical collision phase unless a conservative interval test proves the whole segment separated. This reduces decision points, not collision coverage. Precompute movement fragments only where initial steering, speed and camera/boundary conditions make translation valid; use the exact model at clamps and walls.

Motion primitives are an established way to encode feasible motion and reduce search redundancy; their application here is a proposal, not a transferred performance guarantee. Pivtoraiko and Kelly, 2011.

For deterministic formation crossings, use safe time intervals on a small graph of useful regions rather than a full space × time raster. This follows the time-compression idea in SIPP. Do not transplant ordinary SIPP unchanged: Xenon's neutral input does not imply stationary world position; steering, camera leash and enemy retarget state affect future feasibility. Waiting must itself be an executable holding maneuver. An earlier arrival does not automatically dominate a later arrival with different camera or projectile state.

Keep full one-frame branching as a local fallback around an unresolved conflict. Retain alternatives that go around opposite sides of a threat or use different crossing times; a narrow beam containing near-duplicates is less useful than a small set of distinct escapes. Do not deduplicate by (x,y,time) alone: current code correctly carries homing histories, shot aim, health and bullet state for good reasons. Homing prediction can share prefixes until a retarget, or share branches with identical full sufficient state; it cannot share solely because their ship endpoints coincide.

A safe first step is insufficient. Require a checked continuation through the threat to a usable route/refuge region, or a conservatively verified holding maneuver with enough time to replan. Track the last verified horizon and next decision deadline. Do not repeatedly accept prefixes whose unsafe endpoint stays just outside a fixed horizon—the old swarm failures demonstrate this trap.

5. Retain missions and plans; react to events

Revalidate near-term execution each canonical frame. Rebuild mission plans on actual events: target death/phase change, opening gate, spawn/retarget, unexpected wall contact, prediction mismatch, approach to a crossing deadline, or failed progress. Frame count alone should not require recomputing every plan.

A retained plan should contain actions/continuation, predicted state sequence, mission version, terrain dependencies, hazard dependencies, validity horizon, and replan deadline. An exclusion certificate must cover newly entering hazards too: inspect new spawns and possible emitters against its region, not only the objects originally included in the plan. Removing a target can change mission or emit children, so it is more than deleting a collision rectangle.

This is an extension of existing commitment, not a proposal to add commitment for the first time. Move its validation ahead of exhaustive scoring, retain longer semantic continuations, and repair only what changed.

Level-specific reductions worth pursuing

Encounter Cheaper decision model Guard that must remain
Level-1 scripted waves Shared path/phase; firing-lane occupancy; cross after the tail; validated refuge Correct member relation, delayed followers and collision shapes
Level-2 corridor/homing gates Existing backbone + reconnection field; stage one wave at a time; branch at retarget/crossing events Finite backscroll leash, exact 16-frame retarget and old/new hull order
Level-3 composite/periodic fire Hold region, observe allocation/aim event, execute a checked crossing; phase-specific gate routes Hidden projectiles, unknown branch/extension, frozen aim history
Level-4 bosses Existing monotonic phase order and refuge/attack excursion contracts Separate damaging face/chain, retraction interval, delayed shot allocation
Level-5 tank Station-specific attack/evade/return maneuvers with a local projectile solver Indestructible column/straight shots; exact consumable Side Shot defense; reacquisition and DPS progress

These mechanisms already have substantial policy support. The change is making them the planner's state/action vocabulary rather than repeatedly translating them into large competing scores.

Level pacing also reduces the hazard count itself: do not trigger another wave until the current encounter is under control when the verified camera mechanics permit it. This is conditional on actual scroll/spawn state, not an indefinite DOWN command. For bosses, preserving a productive station can reduce projectile accumulation and encounter duration; “survive while never firing” is not success.

For static navigation, extend offline topology and local connectors to the other known levels as justified by profiles. Preserve full-level knowledge of dead ends. Separate immutable terrain from gate overlays, and repair the affected connector/phase when a gate changes. Do not recreate the failed globally truncated strategic search. Fixed wall-mask geometry and animation-dependent object collision hulls must remain separate.

Why another full distance field is not the first experiment

The existing prototype reports a valid live regression: 35.5 ms mean baseline versus 46.0 ms after two fixes. Its attractive isolated result omitted parts of the integrated workload, including multiple player queries and hull expansion. Its subsequent cProfile explanation was retracted because the replay path did not enable the feature. The detailed cause of the remaining regression is therefore not established; it is not evidence that every distance field must fail.

But it is evidence against building another dense field before specifying the actual query contract. At approximately 13 relevant objects, creating thousands of cells to replace a few rectangle tests can lose. Path-dependent homing, camera variants and per-source damage also cannot be faithfully flattened into one unconditional scalar field. Optional occupancy bitsets for a heavily queried exogenous subproblem can be revisited after the query service exists and its complete build-plus-query cost is measured. A 70-pixel frame-global current-only proximity test would change the old future/candidate-dependent aiming policy; label it a policy experiment, not an exact optimization.

Implementation sequence and acceptance

Stage Concrete change Evidence required before expanding it
A. Establish work budgets Per-stage counters/timing; separate fresh beam, retained validation, projection, contact, comfort and grid misses Same recordings, source/asset hashes, seeds and controller state; live and replay feature configuration demonstrably identical
B. Remove provably unnecessary work Reject colliding beam children before comfort; shared hull arrays; bounded proximity queries; active emitter lists Differential actions/contact/exposure on identical observations; deliberately changed nearest-distance diagnostics identified separately
C. Unify prediction/query service One dependency-aware trajectory source for scorer, beam and validator; grouped exact geometry Model and collision-phase parity, including camera changes, spawn/death/link changes and incomplete observations
D. Replace Level-1 default planner Mission maneuver + retained validation before scoring; group crossing intervals; legacy search fallback Closed-loop Level-1 runs with comparable damage, completion and weapon outcomes, plus end-to-end latency tails and fallback frequency
E. Bound search and extend encounters Maneuver primitives, timed region search, incremental plan repair; specialized homing/combat branches Difficult Levels 2–5 checkpoints and whole campaign; no horizon loss disguised as a speed fix
F. Port the stable runtime Small data-oriented model, query and search modules in C/WASM; compact mission assets Same golden model observations, actual target-phone timing, memory and sustained thermal behavior

Start B with the beam's collision-before-clearance change because its rejected branches cannot affect selection; then prove the maneuver-validation fast path on Level 1. Do not first spend weeks building a general spatial field or porting the complete controller.

Maintain two acceptance tracks. Pure work-removal changes should preserve decisions under the old policy. Architectural policy changes should instead pass collision-model tests and closed-loop gameplay outcomes; exact equality with every old joystick choice would forbid the redesign itself. Replay evaluates the observed trajectory and cannot prove what the new actions would cause next. Use existing game-model, scripted-motion, controller and campaign validators. Include all-blocked starts, wall rollback, corridor dead ends, death/spawn chains, unknown scripts, camera clamp/leash, simultaneous damage and consumed bullets.

The Level-1 prototype and old campaign documents are not proof that the whole campaign is currently solved. The documented tank reacquisition/damage problems remain acceptance cases for the new architecture, not a known-perfect oracle.

Phone runtime target and the eventual C boundary

An initial engineering target—not a measured capability—is mean autopilot work around 1–2 ms and p99 around 4–8 ms on an explicitly chosen low-powered reference phone. Tighten it against the measured emulator/render/audio budget. The roughly 80 ms canonical-game interval in the existing example is not 80 ms of free CPU; rendering and audio have their own cadence, and long lockstep stalls remain visible. Track maximum stall and sustained run behavior as well as averages.

Bound expanded nodes, query work, memory and plan-validation work. Keep a verified incumbent and a deadline-aware fallback; stop optional ranking/search before the budget expires. If no safe action can be verified, report that state and use the explicit degraded policy. A budget cannot create a safety guarantee in an unavoidable collision, and neutral input is not automatically safe. Keep exploration fallbacks visible so phone performance is not bought by hidden stalls.

The intended cost becomes approximately:

observation/event updates
  + changed independent trajectory suffixes and group bounds
  + a few concrete maneuver validations over potentially interacting groups
  + occasional budgeted local search

This replaces the unconditional nine × horizon × members scoring pass, plus a second search pass, with work proportional to retained-plan changes and local conflicts. Dense interacting homing combat still has real combinatorial cost; there is no justified promise of an orders-of-magnitude speedup everywhere.

Eventually place the compact model/query/search runtime beside Hatari in WASM, consume a stable canonical observation buffer, and return one input. Preserve the existing input-latch ordering. Avoid per-object/per-query bridge calls and avoid carrying replay/UI/logging object graphs into production. Worker placement can help scheduling isolation, but it does not reduce the amount of CPU work.

The valuable C port is then a small, measured controller with stable contracts. Translating today's monolithic scoring and escape machinery first would make individual operations faster while retaining most of the unnecessary questions.