Xenon 2

Autopilot · write-up

Native world driving and optional Python views (ABI 84)

xenondoc/AUTOPILOT_NATIVE_WORLD_VIEWS.MD · 7 KB · updated 2026-09-17

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:

  1. Pack captured raw objects and draws once for xap_world_observe().
  2. C normalizes the population and collects a compact player/attachment/projectile-direction summary.
  3. Publish one small player view and host status needed by existing lifecycle/transport code.
  4. 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.json
  • native-world-view-level3-0913-final/comparison.json
  • native-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.