Autopilot · write-up
Native world driving and optional Python views (ABI 84)
Ownership and execution
The C observation already owns canonical tracking, fields, motion history and script models.
Ordinary C driving no longer reconstructs a Python TrackedObject for every live object.
WorldState.update() dispatches to autopilot_v2/native_world_view.py; the eager/reference
assembly remains in world_state.py, with object construction isolated in _build_objects().
Set XENON_NATIVE_WORLD_VIEW=0 to retain eager assembly for comparison.
The normal boundary is:
- Pack captured raw objects and draws once for
xap_world_observe(). - C normalizes the population and collects a compact player/attachment/projectile-direction summary.
- Publish one small player view and host status needed by existing lifecycle/transport code.
- The C driver consumes the canonical world, including attachment positions, directly.
XapDriverEquipment now contains inventory enable flags, not Python-computed attachment positions.
Laser and cannon offsets stay with the native observation. The component-test path can collect
those facts from canonical fields when it has no captured observation; ordinary driving reuses
the prepared summary. Player identity from that summary also removes another mission population scan.
This does not add a Python callback or a per-object C call. The standalone DLL remains callable from Python. No Hatari integration or WASM profiling was performed.
Optional inspection and lifetimes
WorldState.objects, previous_objects and removed materialize only when requested.
Current object export reads the already-decoded C fields and scripts. Historical inspection
uses the tracker's existing prior-population buffer, rebuilding old renderer/field diagnostics
from the retained raw capture only when needed. It does not copy the full population every frame.
The C accessor xap_world_previous_tracks() borrows that buffer; its count is the previous
observation count, and the pointer expires at the next update/restore/free. The host rebinds
it after buffer growth, rather than retaining an address that may have been relocated.
These deferred owners are internal: public object access returns an ordinary, materialized dict or list. Exported objects therefore remain stable after later frames. Replay checkpoints explicitly materialize the views they retain and clone the existing native tracking/driver state. Already inspected histories are reused instead of being reconstructed from C on every UI frame.
CapturedFrame.tracks and .renders are explicit diagnostic exports. One player object remains
for host lifecycle compatibility. Raw source records and bytes remain available because transport,
reference consumers and diagnostics still use them.
Unrequested live work removed
The live loop no longer collects behavior families when --catalog is absent. It retains basic
shield, life and score metrics; object_details_collected identifies whether detailed catalog
metrics were collected. Target-priority calculations and native decision inspection run only
when a trace or a scheduled UI update needs them. Explicit trace/catalog/UI output still incurs
its diagnostic costs.
Canonical event replay no longer iterates every object merely to apply an empty set of legacy trace overrides. Legacy traces with actual overrides retain their original behavior.
Measurement
profile_autoplay.py replay --driving-only and compare_kernels.py --driving-only omit object
inspection and replay checkpoints. Default profiling retains the inspected replay workload.
Driving-only timing rows report player presence rather than exporting full object counts;
object_counts_scope records this distinction. ReplayModel supports cache_frames=0 for this
headless mode; ordinary UI configuration still requires a positive checkpoint cache.
Ablation: --variants c-world-view-old,c. Both variants use the current C kernels, map sessions
and navigation environment. Each comparison uses two fresh-process, CPU-pinned repeats in reverse
order, without concurrent test workloads. Times include sequential capture normalization, world/map
updates and decision processing; they exclude emulator execution, sockets, AVI and Tk rendering.
They are not whole-game FPS or phone/WASM measurements.
| Recorded sample | Eager Python views | Optional views | Reduction |
|---|---|---|---|
| Level 1, frames 2367–3367 | 0.817 ms/frame | 0.531 ms/frame | 35.0% |
| Level 3, frames 49751–50020 | 1.230 ms/frame | 0.738 ms/frame | 40.0% |
| Level 5 barriers, frames 90160–90459 | 1.261 ms/frame | 0.668 ms/frame | 47.0% |
All compared driving decisions were identical. Active Level-1 measured frames report
world_view_backend=lazy and world_objects_exported=false throughout.
Reports under xenon_tools/run_logs/:
native-world-view-level1-0913-final/comparison.jsonnative-world-view-level3-0913-final/comparison.jsonnative-world-view-level5-0913-final/comparison.json
Fully inspected Level-1 replay was also compared with two reversed repeats:
0.974 → 1.021 ms/frame, about 4.8% slower, with identical decisions. This explicit-inspection
tradeoff is separate from ordinary driving. Report:
native-world-view-inspection-0913-validated/comparison.json.
Validation
- 294 native tests, including six new view/ownership tests.
- 575 gameplay tests with the reference path and again with native world updates forced.
- 67 planner tests, 24 replay tests, 15 resident-map tests.
- New tests prohibit population, track and script export while driving all five levels; verify multi-weapon offsets, scalar-only catalog metrics, delayed history, capacity growth, removals, namespace reset, backend switching and eager/lazy diagnostic equivalence.
- Actual recording: seek 105 → 96 → 105 with inspection/checkpoints; controls, winning path, current/prior population sizes and removals matched after restoration.
- Standalone native build passed. Existing compiler conversion warnings remain.
One initial combined test command used the build toolchain's Python, which lacked Pillow; resident-map tests were rerun successfully with the normal Python environment. No new live campaign acceptance run was performed for this migration; the gameplay comparisons above are recorded-input validation.
Remaining Python boundary
Python still parses protocol/game-memory captures, supplies inventory enable flags and scalar lifecycle state, handles shops/transitions/transport, and provides optional diagnostics/reference objects. C already prepares the planning player/camera state. The remaining small player view and status publication can disappear when the host lifecycle/input boundary moves as one coherent chunk; another series of per-field wrappers would add little value. Full standalone C driving is therefore not yet claimed.