Autopilot · write-up
World preparation: scalar decoding and numeric kinds
Scope
ABI 21 adds xap_decode_object_fields, a standalone batch C API for 54 scalar
game-state fields and object classification. It is callable from Python and is
listed in the standalone WASM exports. No Hatari integration or WASM profiling
is part of this change.
The existing C world tracker supplies geometry, age and velocity directly to the decoder through its current track pointer. Python does not rebuild those inputs. The new decoder has one batch call per observation, not one call per object.
Organization
xenon_autopilot_world_decode.c: common and per-level scalar readers, followed by the batch entry point. The overlay decoder is selected once per batch.xenon_autopilot_world_classify.c: shared classification and separate level functions. Level 3 composite/formation rules are separate groups to preserve precedence relative to shared wall-shooter and projectile rules.xenon_autopilot_world_symbols.h: named game addresses and offsets, checked against the Python symbol catalog by a test.world_decode.pyandworld_classify.py: readable Python reference paths.native_world_decode.py: the ctypes structures and compatibility export.
The C result has named fields and a presence mask. Zero is valid data; a missing or truncated capture is not silently interpreted as a real zero. Integer fields also preserve unsigned 32-bit pointer values. Older recordings without level metadata retain handler-based decoding; known overlays skip other levels' scalar readers. Complex script/formation/controller decoding remains separate Python work for a later migration.
Waste removed
The previous update evaluated dozens of unrelated field expressions for every object and supplied their mostly-None results to the tracked-object constructor. The new path exports only populated fields; dataclass defaults provide the rest. The update function drops from roughly 1,340 to 620 lines. It is still a sizeable orchestrator: lifecycle filtering, draw aggregation and legacy decoration remain to be split or migrated, rather than being claimed as completed C work.
Enum membership groups are shared immutable constants. This avoids a Python trap: replacing constant string sets with enum-attribute set literals would otherwise allocate a new set for each query. Twenty-three distinct groups are hoisted and reused across world selectors and policy/prediction consumers.
The first C bridge still copied its entire result array and unpacked every field into Python tuples. The revised bridge reads its reusable ctypes buffer through a memoryview and visits only present fields. Its field dictionaries are needed by the remaining Python consumers; they are not the intended final C runtime representation. Buffer ownership remains with the Python bridge for now, and a plain C caller can supply its own output storage.
Classification contract
TrackedObject.kind stores the stable numeric enum. Existing enum values are
preserved and additional object kinds are appended. Native body validation
accepts the extended range. World-state selectors and literal classification
comparisons in the active planner/prediction code now compare enums.
classification is a compatibility descriptor: existing constructors, legacy
tactics and replay exports can still read/write labels, but the object stores
the enum. Label conversion remains at those old interfaces; this is not a claim
that every remaining Python consumer has been migrated. C classification uses
no strings. The one-frame destroying_hostile lifecycle decoration still runs
in Python after native base classification, as do equipment/pickup subtypes.
Validation and measurement
- Final full suite: 941 tests passed (
world-prepare-hoisted-tests.txt). - All 575 existing world-state tests also passed with updates forced through C.
- Focused tests cover every known handler across levels, short captures, signed values, enum/label compatibility, symbol drift and invalid batch bounds.
c-world-prepare-oldloads the update/classifier from commit9250ad66before timing starts, using the current enum-compatible tracked-object type. This is an update-algorithm ablation, not a complete old executable comparison.c-world-prepare-pythonuses refactored Python preparation with C tracking;cadditionally uses native scalar decoding/base classification.
The first two pinned opposite-order Level 1 repeats, frames 2367-10059, preserved
all compared verdicts. Means were 3.48191 ms (old update), 3.37959 ms (refactored
Python), and 3.39373 ms (initial C bridge). Thus refactoring saved 2.9%, while the
initial C bridge saved 2.5%; porting alone did not beat the refactored Python
path. Artifacts: world-prepare-timing-0908/comparison.json.
The final two pinned opposite-order repeats, including direct buffer access and
immutable enum groups, are in world-prepare-final-timing-0908/comparison.json:
| Preparation variant | Mean observation ms |
|---|---|
| Previous update algorithm, current compatibility type | 3.36513 |
| Refactored Python decoding/classification | 3.25735 |
| Native decoding/classification | 3.24783 |
Native preparation reduces observation time by 3.49% in this controlled update-algorithm comparison. The C and refactored Python variants differ by only 0.29%, below the variation between repeats: there is no demonstrated separate C speedup over the refactored Python path. The benchmark shares current planner code and the enum-compatible object type across variants; it does not measure the complete previous executable against the new executable. The direct buffer timing pass preceded the final small metadata-zero normalization fix; the final immutable-group timing pass includes that fix.
A separate instrumented pass before the final enum-group hoist measured
WorldState.update cumulative time at 9.088 -> 7.960 seconds (self time
4.847 -> 3.782). The new bridge's ObjectDecoder.decode accounts for 0.467
cumulative seconds, included in the latter. This locates remaining work; these
profile numbers are not interchangeable with uninstrumented speed estimates.
Artifacts: world-prepare-profile-0908/*.pstats.
The compared verdicts are unchanged on Level 1 and the established cross-level recordings:
| Level | Frames | Observations |
|---|---|---|
| 1 | 2367-10059 | 7693 |
| 2 | 32577-33537 | 961 |
| 3 | 49306-49530 | 225 |
| 4 | 63486-64385 | 900 |
| 5 | 91740-97391 | 5652 |
Cross-level reports: world-prepare-level{2,3,4,5}-0908/comparison.json.
These are recorded-observation comparisons, not a new live campaign or proof
that the previously observed boss damage is fixed.
Next boundary
Avoid spending the next iteration micro-optimizing the now-small decoder. Move
native prediction consumers onto these typed results, then make legacy
TrackedObject materialization optional. Lifecycle/draw aggregation and complex
script/formation state remain further world-preparation migrations. C-owned
tracking and caller-owned decoded fields must be joined deliberately before
claiming that the complete world state is independently C-owned.
World symbol audit (2026-09-08)
World classification and scalar decoding now name procedure addresses, pickup
codes, sprite range endpoints, state tags, and classification thresholds in both
xenon_symbols.py and xenon_autopilot_world_symbols.h. Ordinary byte/word
arithmetic, zero tests, and level numbers remain literal. The existing vector
table in XENON2.MD supplies direct/thunk identities; pickup names are checked
against the extracted shop/animation catalog. Pickup $5C remains explicitly
unidentified rather than receiving an invented effect name.
ReVa /mydumpat0 confirms $34EA loads the first animation sprite/duration and
advances the script pointer; it is not a destruction routine. It was removed
from both destruction groups. $34FA installs destruction handlers on self or
children. $3EEC is an allocator tail alias observed as a pickup-carrier hit
handler, so its name describes both the observed use and actual routine.
$4020 retains the documented silent group-despawn name; its exact gameplay role
is still provisional. No new handler aliases were added to classification groups.
Validation: standalone native build, Python/C symbol-value checks, extracted pickup-code membership checks, and world-state regressions. This is a readability and classification correction, not a claimed performance optimization.
C-owned fields and direct prediction (2026-09-08, ABI 22)
XapWorld now owns decoded scalar fields alongside canonical tracks. The batch
xap_world_decode returns a borrowed field view that expires on the next update,
restore, or free. xap_world_prepare_shell_paths and
xap_world_prepare_pickup_paths consume selections of those records directly.
Both reuse world-owned input scratch and fill caller-owned output paths in one
Python/C call per batch. The original packed-input kernels remain available.
There is no Hatari integration or transport change in this step.
Python's ObjectDecoder.decode(..., owner=world, export=False) populates native
state without creating compatibility dictionaries. The running Python planner
still requests exports because its policy, target selection and complex linked
models need TrackedObject fields. Thus this step does not remove full Python
object construction or claim that all prediction preparation is now C-owned.
Script bytes, linked-model setup and lifecycle decoration are still pending.
There is no borrowed C pointer in a retained TrackedObject; exported values
remain ordinary Python integers. Shell consumers capture the world stamp and
use retained packed-input preparation if the owner has advanced. Restore clears
native decoded validity until the next decode.
Ablations in compare_kernels.py:
c-world-owned-old: caller-owned decoder output and packed prediction inputs.c-world-owned-fields: world-owned decoder output, packed prediction inputs.c: world-owned fields and direct shell/pickup prediction inputs.
Two CPU-pinned, opposite-order Level-1 repeats over frames 2367�10059 measured:
| Variant | Mean observation time |
|---|---|
| Caller-owned fields / packed inputs | 3.24779 ms |
| World-owned fields / packed inputs | 3.26439 ms |
| World-owned fields / direct inputs | 3.29483 ms |
There is no measured performance win: ownership is about 0.5% slower here;
direct preparation adds about 0.9%, for about 1.45% total. Repeat spread reaches
2% for the direct variant. These controls share the new library; they isolate
the bridge choices, not the complete previous executable. Artifact:
run_logs/world-owned-timing-0908/comparison.json. A subsequent Python readiness
flag reset affects error handling only. This migration removes scalar repacking
from the two native consumers, but compatibility export still dominates the
boundary and record selection has a cost. Do not present these results as a
speedup or make further decoder micro-optimization the priority.
Recorded decisions remain unchanged on the established Level 1�5 windows
(listed above); cross-level artifacts are world-owned-level{2,3,4,5}-0908.
Cross-level single-pass timings are correctness checks, not performance claims.
The standalone native build and 730 world/planner/native/decoder tests pass,
including direct-vs-packed oscillator and compass trajectories, Python-free
field decoding, restore/growth invalidation and invalid selections. No new live
campaign was run.
Next remove the remaining Python script/link preparation and policy dependencies on full object views, then disable compatibility materialization for the native runtime. Merely making export optional at this low-level API does not make it optional for the existing Python planner.
Captured scripts and resolved links (ABI 23, 2026-09-08)
The existing xap_prepare_script_paths interpreter and
xap_prepare_formation_paths trajectory kernel are unchanged. New code in
xenon_autopilot_world_prediction.c owns the preparation around them:
- Decode fixed-point script state directly from validated object captures.
- Retain only nonempty script byte windows, not copies of whole object envelopes.
- Resolve object addresses once in C, preserving input indices and last-record-wins
lookup behavior. Ordinary successors, follow targets, composite parents and
predecessors, Level-4 predecessors and eye predecessors are exposed as named
XapWorldLinksfields. - Build selected script/ordinary-formation descriptors in reusable scratch and invoke the existing predictors directly into the shared point pool.
The opaque world layout lives in xenon_autopilot_world_internal.h; script/link
preparation is separate from track updates and from script execution. New symbols
are included in standalone WASM exports, but no WASM profiling or Hatari runtime
integration was performed.
Python no longer calls _scripted_motion_state for each object on the native
capture path. Instead, it exports the already-decoded state to immutable
ScriptedSineMotionState compatibility objects. Original game addresses and
script bytes remain available to reference models and recording/debug views.
With export=False, none of these Python objects or field dictionaries is built.
The normal Python-driven planner still needs those views; complete Python object
materialization has not yet been removed.
The policy's six ordinary link maps now borrow resolved C indices. This removes its object-address dictionary and repeated Python address resolution on the native path. Semantic chain grouping, specialized model eligibility and tactical policy still run in Python. C does not blindly treat the global next-object pointer as formation motion: only the ordinary follower handler uses that relationship.
All borrowed state expires on update, restore or free. Captures can be disabled
with the final prepare_prediction argument of xap_world_decode for scalar-only
comparison runs; this invalidates script/link access without disabling scalar
shell/pickup preparation. Direct preparation returns 0 for unsupported models and
-1 for invalid input; neither changes output. Retained packed-input preparation
remains available for Python-only worlds, stale snapshots and unsupported models.
Validation and measurements
- Standalone native build passed.
- 733 tests passed across world-state, native, planner and field-decoder suites.
- All 575 world-state tests also passed with native updates forced on.
- New differential tests cover every script handler across all overlays, short and legacy/current captures, appended pre-update envelopes, signed state, dormant chains, direct-vs-packed paths, reordered links, semantic predecessor filtering and restore invalidation.
- Recorded decisions are unchanged on the established Level 1-5 windows. Reports:
world-scripts-level{2,3,4,5}-0908/comparison.json. These were recorded-observation comparisons, not a new live campaign.
Two pinned opposite-order Level-1 repeats (frames 2367-10059):
| Variant | Mean observation time |
|---|---|
c-world-scripts-old: Python preparation, native capture disabled |
3.30483 ms |
c-world-scripts-decode: C capture/state export, old descriptor/link preparation |
3.26062 ms |
c: C capture plus direct script/formation and link preparation |
3.28771 ms |
The full change is about 0.52% faster; this is a small result, not a demonstrated
large performance gain. Decode-only saves about 1.34%, while direct policy exports
add about 0.83% relative to that intermediate variant. The latter still builds
Python maps for the current policy. Repeat spread ranges from 0.27% to 1.37%.
These controls share the current library and metadata layout, so they isolate the
preparation choices rather than comparing complete previous executables.
Final artifacts: run_logs/world-scripts-final-timing-0908/comparison.json.
The earlier world-scripts-timing-0908 run predates removal of the remaining
Python predecessor-address lookup and is not the final result.
The next migration boundary is the Python graph-to-model assembly and policy that still consumes these compatibility objects, not another rewrite of script execution.
Observation topology and model selection (ABI 24)
xenon_autopilot_world_models.c now builds formation risk groups, ordered
Level-3 composite chains and their member ordinals once per observation. It also
selects a XapPredictionModel enum from captured state and resolved links. Risk
grouping includes independently scripted formation followers; ordinary delayed
movement still uses only the ordinary follower link. These are distinct graphs.
The existing script interpreter and trajectory kernels remain unchanged. Shared scene preparation consumes the selected script roots and shell oscillators; anchor prediction skips irrelevant ordinary-follower/script attempts. Source lists are collected during the metadata export, eliminating two further full population scans. Composite sweep prediction reads its cached ordinal instead of repeatedly searching the member tuple.
xap_world_decode preparation flags are XAP_WORLD_CAPTURE_SCRIPTS,
XAP_WORLD_BUILD_GROUPS and XAP_WORLD_SELECT_MODELS. Normal native operation
uses all three; model selection requires grouping, and grouping requires capture.
The xap_world_models and xap_world_composite_members accessors expose borrowed
C arrays valid until update, restore or free. Python exports one immutable view
per observation, retaining no borrowed pointers. Reusable C arrays own the
metadata and ordered member indices; no per-object Python-to-C calls are added.
This is static common-model dispatch, not a port of all tactical prediction.
Pending-spawn checks, specialized/candidate-dependent models, ordinary follower
cache traversal and the specialized composite predictor still have Python
consumers. XAP_MODEL_POLICY explicitly leaves those decisions to policy.
Compatibility object materialization and six Python link maps also remain.
The Python grouping and eligibility implementations remain available. Fresh process comparison variants isolate the changes:
c-world-models-old: prior Python grouping and model dispatch.c-world-groups: native topology/ordinals, prior model dispatch.c: topology plus model selection and prepared source lists.
These controls share the current library and its storage layout; they compare preparation paths rather than complete historical executables.
Validation
- Standalone native build passed; ABI 24 is required by the ctypes bridge.
- 736 tests passed (575 gameplay policy, 88 native, 67 planner, 6 decoder).
- All 575 gameplay-policy tests also passed with native world updates forced on.
- New differential cases cover randomized risk groups, reordered links, cycles, composite order and ordinals, last-successor handling, missing capture, selected source lists and retained-view validity after restore.
- Level 2-5 recorded windows have zero changed decision verdicts. Reports are
run_logs/world-models-level{2,3,4,5}-0908/comparison.json; their single-pass times are correctness-run timings, not controlled performance claims. - No new live campaign, Hatari integration or WASM profiling was performed.
Controlled measurement
Two pinned opposite-order Level-1 repeats, frames 2367-10059:
| Variant | Mean observation time | Incremental change |
|---|---|---|
c-world-models-old |
3.32516 ms | baseline |
c-world-groups |
3.33536 ms | 0.31% slower |
c |
3.33932 ms | 0.12% slower than grouping-only |
Overall the mean is 0.43% slower, with per-variant repeat spread of 0.12-1.61%. This does not demonstrate a performance improvement. The static work moved to C, but the current Python policy still requires metadata dictionaries and linked object tuples. Removing those exports as their consumers migrate is the next boundary; merely moving these small loops cannot eliminate that bridge cost. All variants produced identical decision verdicts on both repeats.
Final report: run_logs/world-models-final-timing-0908/comparison.json.
The earlier world-models-timing-0908 run was rejected by the source fingerprint
guard after a test-source edit and is not used for performance conclusions.
Ordinary formation consumers (ABI 25)
Following ABI 24 (commit e14d2111), migration proceeds in this order:
- Ordinary formation-chain assembly and cached-predecessor lookup.
- Whole-path bounds and sampled swept-rectangle relevance checks.
- Later: specialized composite/follow-target preparation and its policy dependencies.
The first two consumers are implemented in this phase. The new
xap_world_resolve_formation_chain reads native model records and successor
indices, walks only the requested uncached prefix, detects cycles with a bounded
walk, and returns predecessor-first indices plus the required root. The existing
formation preparation kernel consumes these indices directly. A root needing
specialized policy remains a Python prediction callback. Each resolution uses
one C call and subsequent path materialization uses the existing batch call;
there are no per-member Python-to-C calls. Prepared index selections own their
storage because a root callback can recursively request another chain.
The native world owns graph/model data. The current scene consumer owns one population-indexed byte array marking completed formation-cache paths, plus reusable traversal scratch. These buffers are allocated lazily, and address selection reuses NativeWorld's existing identity-index table. Python continues to register path views for its consumers; a second registration loop and a redundant chain reversal have been removed.
xap_formation_path_relevance, in xenon_autopilot_formation.c, reads completed
shared native points directly. It computes anchor bounds once per path and
checks sampled swept rectangles without Python point tuples or per-point loops.
Anchor bounds are independent of live collision offsets. The Python bridge
packs the immutable future-region tuple once and reuses it across hazards.
The C function returns 1 for relevant, 0 for no sampled intersection, 2 for
whole-path rejection, and -1 for invalid ranges.
native_formations.py keeps these consumers separate from the retained Python
implementations. Python-only worlds, stale observations and unsupported roots
retain their prior paths. Native chain selection and native relevance have
independent fresh-process controls. Six policy link maps, compatibility objects
and specialized tactical prediction still exist: these exports cannot disappear
until their remaining policy consumers move. This phase does not claim that all
formation prediction or its cache now lives in C.
Validation
- Standalone native build passed; ctypes requires ABI 25.
- 92 native tests and 67 planner/shared-scene tests passed.
- All 575 gameplay-policy tests passed with native updates forced on.
- New tests cover cached branches, predecessor order, native path materialization, cycles, invalid indices/sample ranges, edge contact, random camera shifts and changing collision extents against Python swept-rectangle calculations.
- Recorded Level 2-5 decisions are unchanged. Reports:
run_logs/formation-consumers-level{2,3,4,5}-0908/comparison.json. These windows preceded a semantics-preserving removal of one chain reversal and merging two registration passes; native/planner suites were rerun afterward. - No new live campaign, Hatari integration or WASM profiling was performed.
Controlled measurement
Two pinned opposite-order Level-1 repeats, frames 2367-10059, with stable source fingerprints and no concurrent validation workloads:
| Variant | Mean observation time | Incremental change |
|---|---|---|
c-formation-consumers-old |
3.32741 ms | baseline |
c-formation-chains |
3.33268 ms | 0.16% slower |
c |
3.31056 ms | 0.66% faster than chains-only |
The overall mean is 0.51% faster, with repeat spreads of 0.18-0.74%. This is a
small result within the observed spread, not a demonstrated substantial speedup.
Chain traversal alone adds bridge/cache bookkeeping and does not improve overall
time. Native relevance removes Python point scans; the combined path is retained
for C migration. Both repeats have zero changed decision verdicts.
The first full run averages 0.931 native chain resolutions and 7.526 native
relevance calls per gameplay observation, confirming both consumers are exercised.
Report: run_logs/formation-consumers-timing-0908/comparison.json.
The next boundary is specialized composite/follow-target path preparation, then its tactical consumers, so that exported link maps and Python object views can actually be removed. Ordinary root policy, path-view registration and tactical selection remain Python work after this phase.
Same-frame links and composite consumers (ABI 26)
The preceding ordinary formation phase was committed as 3b6dce51.
This phase moves consumers in the following order:
- Same-frame follow-target and attached-part path preparation.
- Coupled active/collapsed composite-chain anchor prediction.
- Collapsed-chain uncertainty bounds and root lookup.
xenon_autopilot_linked.c reads C world links, fields, captured scripts and
composite ordinals directly. xap_world_linked_root resolves nested same-frame
links and accumulates attachment offsets with a bounded cycle check. The
resolved root forecast feeds xap_world_prepare_linked_path; frame zero remains
the observed object anchor. Specialized independent roots can still be supplied
by Python policy. Output goes into the shared scene point pool, with one call
per path or coupled chain, not one call per object per future frame.
xap_world_prepare_composite_paths advances all members as one coupled group,
reusing the existing game sine table and integration logic. The shared sine
substep is now an inline internal helper used by both scripts and composites.
No second script interpreter was added. Emitter phase/direction control the
extension and retraction; after collapse, unknown future RNG launches retain the
reference deterministic root prediction. Initially collapsed uncertainty is
handled separately by xap_world_prepare_collapsed_bounds using native ordinals.
The retained Python active-composite routine writes its anchor cache but does not read that cache on entry. The new consumer prepares and registers all members once at the requested shared horizon, avoiding those repeated frame/member simulations. Subsequent calls reuse the same native paths. Longer requests append new paths; prior views own their old allocation. Root forecasts are reused from the same scene, including roots prepared by another linked consumer. Unsupported composite captures are remembered for that observation to avoid repeated failed preparation attempts.
Collapsed envelopes are computed once per member/horizon in C, then indexed by Python collision consumers. Changing collision offsets invalidates that bound batch. The adapter's collapsed-root query uses C-derived root metadata where available, avoiding a predecessor walk for every sampled frame.
native_linked.py holds the optional Python bridge and fresh-process controls:
LINKS, COMPOSITES, COLLAPSED. Old paths remain available for comparison,
Python-only observations and unsupported data. Scene stamps reject stale owners.
Native APIs remain callable from Python and standalone C; no Hatari runtime
integration or WASM profiling was performed.
Remaining Python consumers
The six compatibility link maps are still exported. Camera-dependency analysis, specialized/reference prediction and policy eligibility still read them. Composite member tuples also register Python path views. This phase therefore removes repeated prediction work but does not claim complete removal of link-map exports or tactical policy. Migrate those last consumers before removing the exports.
Validation
- Standalone native build passed; ctypes requires ABI 26.
- 96 native tests and 67 planner/shared-scene tests passed.
- All 575 gameplay-policy tests passed with native updates forced on.
- New differential tests cover nested follow/attachment offsets, cycles, live frame-zero anchors, composite turns/retraction/collapse, uncertainty bounds, shared member reuse, horizon growth and stale-world rejection.
- Standard recorded Level 2-5 windows have zero changed decision verdicts:
run_logs/linked-level{2,3,4,5}-0908/comparison.json. - The separate
level3-composite-script-capture-0826-103.x2eventsencounter (frames 39321-39800) also has unchanged decisions. Its correctness report isrun_logs/linked-composite-capture-check-0908/comparison.json. - The later
level3-composite-single-trajectory-0826-133.x2eventsrecording could not be compared: the retained baseline fails inReactiveController.choosewithValueError: min() iterable argument is empty, before a native comparison. This pre-existing baseline issue was not changed as part of the port. - These are recorded-observation checks, not a new live campaign.
Controlled composite-encounter measurement
Two pinned opposite-order repeats on the 480-frame composite capture, with stable sources and no concurrent validation workloads:
| Variant | Mean observation time | Incremental change |
|---|---|---|
c-linked-old |
15.40394 ms | baseline |
c-linked-paths |
15.73216 ms | 2.13% slower |
c-linked-composites |
11.48317 ms | 27.01% faster than linked-only |
c |
10.61094 ms | 7.60% faster than composite-stage |
The full path is 31.12% faster on this encounter. Repeat spreads are 0.11-2.73%,
well below the full improvement. Every variant has identical decision verdicts
on both repeats. This is encounter-specific evidence, not a campaign-wide claim.
The correctness run recorded averages of 1.340 linked paths, 0.023 coupled
composite batches and 2.119 collapsed-bound batches per gameplay observation.
Link-only preparation adds work at the bridge; the group caching and uncertainty
batching provide the measured gain. Report:
run_logs/linked-composite-timing-0908/comparison.json.
General Level-1 overhead check
Two pinned opposite-order repeats on frames 2367-10059 measured 3.32293 ms for
c-linked-old and 3.34482 ms for c (0.66% slower). Repeat spreads were 0.33%
and 0.70%; this is a small overhead, not a demonstrated Level-1 improvement.
Both repeats have zero changed decision verdicts. Report:
run_logs/linked-level1-timing-0908/comparison.json.
The next removal boundary is camera-dependency analysis and remaining tactical link queries, then direct native consumption of the prepared paths/bounds. That would remove Python exports and rectangle materialization rather than introducing another parallel set of preparation objects.
Native linked collision-slice consumption (ABI 27)
The previous linked/composite phase was committed as cf35b8ad.
This step removes the remaining C-to-Python-to-C geometry round trip for eligible
linked/composite collision bodies. native_linked_slices.py registers native
point/bound allocations and compact XapLinkedSliceSource descriptors once, on
the first requested collision frame. xap_pack_linked_slices then fills only that
frame's assigned XapBodySlice slots in one C call. The existing collision and
candidate kernels consume those slices without Python point extraction,
rectangle construction, unions, numeric validation or rectangle repacking.
independent_slices emits a slot marker for native bodies. Other bodies and
anticipated shots retain their existing preparation. Slots preserve the original
mixed-source order, labels, camera-coupling flags and target indices; this avoids
changing which contact is reported when several bodies overlap. Numeric geometry
comes from the existing native predictors. The packer validates source/sample
and destination ranges before modifying any output.
For constant-hull linked paths, C computes the endpoint sweep directly from borrowed points and live collision offsets. The authoritative generic-hull prediction branch certifies eligibility once. Animated attachments and other specialized shapes remain on the retained path. This certification is enabled only with the new consumer, so the comparison includes its cost.
For an initially collapsed chain, the prepared reachable extension bound contains
the guaranteed emitter sweep. C can use it directly, avoiding both the extra hard
rectangle and the union. This also preserves the special frame-zero rule: the
guaranteed collapsed body is at the emitter, not at a potentially different
recorded segment anchor. LinkedPaths.collapsed_bounds exposes the existing
native bound allocation; collapsed remains its Python Rect adapter for other
consumers. No second prediction/bound cache is introduced.
The collision scene keeps owning references to all borrowed allocations, including old point-pool buffers after growth. Source forecasts retain their existing shared horizons, while output slices are still generated only for requested frames and cached once. There are no extra speculative collision frames.
Fresh-process c-linked-slices-old retains Python packing; c enables direct
consumption. Camera-dependency analysis and tactical link-map consumers are not
ported in this phase. The six compatibility maps remain until those users move.
Native APIs remain Python-callable and suitable for standalone C; no Hatari
runtime integration or WASM profiling was performed.
Validation
- Standalone native build passed; ctypes requires ABI 27.
- 98 native tests and 67 planner/shared-scene tests passed.
- All 575 gameplay-policy tests passed with native updates forced on.
- New checks cover native point sweeps, collapsed bounds, original slot order, untouched Python slices, target/camera metadata, invalid-range rejection before writes, mixed-source equivalence, buffer growth and repeated-frame reuse.
- Standard Level 2-5 windows and the targeted Level-3 composite capture have
unchanged decision verdicts. Correctness artifacts:
run_logs/linked-slices-level{2,3,4,5}-0908/comparison.jsonandrun_logs/linked-slices-composite-check-0908/comparison.json. - The composite correctness run averaged 3.127 native slice sources, 12.509 native packing calls and 175.132 directly filled slices per gameplay observation.
- Recorded-observation comparisons are not a new live campaign. The previously documented later composite-recording baseline error remains outside this phase.
Controlled composite timing
Two pinned opposite-order repeats on the 480-frame Level-3 composite capture:
| Variant | Mean observation time |
|---|---|
c-linked-slices-old |
10.58268 ms |
c |
8.87599 ms |
Direct native consumption is 16.13% faster on this encounter, with repeat spreads
of 0.50% and 0.26%. Both repeats have zero changed decision verdicts. This is the
incremental effect relative to ABI 26's already-native linked/composite predictors;
it is not an all-level speedup. Source fingerprints remained stable and other
validation runs were finished before timing began. Report:
run_logs/linked-slices-composite-timing-0908/comparison.json.
General Level-1 check
Two pinned opposite-order repeats on frames 2367-10059 measured 3.30816 ms for
c-linked-slices-old and 3.30517 ms for c (0.09% faster). Repeat spreads were
0.44% and 0.61%, so this is effectively unchanged. Both repeats have zero changed
decision verdicts. Report:
run_logs/linked-slices-level1-timing-0908/comparison.json.
No compatibility link maps were removed in this phase. Camera-dependency flags and remaining tactical link queries are the next ownership boundary; the prepared geometry now reaches native collision through borrowed buffers rather than Python rectangles.
Camera dependence and relationship eligibility (ABI 28)
C model records now include named camera_coupled and unattached fields.
The additional XAP_WORLD_RELATIONSHIP_FLAGS preparation flag enables their
calculation alongside model selection. Without it the fields remain -1 and
Python consumers retain the reference logic. Normal native preparation enables
it; c-relationships-old disables both native calculation and compatibility
flag export for comparison. Controls still share the expanded current ABI layout.
Intrinsic camera dependence uses the same screen-relative handler classifier as script capture, including when capture bytes are missing. The fully grown Level-5 tank central projectile is also camera-coupled. Only semantic follow-target links propagate that property. Attachment and ordinary formation links do not inherit it automatically: this preserves current predictor behavior. An iterative walk resolves each pending follow node once, using the existing scratch allocation. Cycles without an intrinsic camera-dependent source resolve false. No Python recursion or temporary visited set is needed on this native path.
unattached means no follow target, no composite parent and no ordered composite
chain membership. It is deliberately not a synonym for an independent trajectory:
ordinary follower and eye links have their own model rules. The shared flag now
serves formation broad-phase selection, neutral-lane swarm threat eligibility,
delayed-eye source eligibility and ordinary-chain root eligibility. Dynamic
pending-spawn checks and specialized model conditions remain at their consumers.
The maneuver adapter reads the native camera flag directly. Synthetic objects or reference-only observations fall back to the existing Python implementation. Boolean compatibility views are exported once per observation and retain no borrowed native pointers. Script execution, collision geometry and tactical movement decisions were not changed in this phase.
All six link maps still have remaining Python users in specialized/reference prediction and tactical queries. None can safely be removed yet; this phase removes repeated relationship computation, not the complete map-export boundary.
Validation
- Standalone native build passed; ctypes requires ABI 28.
- 100 native tests and 67 planner/shared-scene tests passed.
- All 575 gameplay-policy tests passed with native updates forced on.
- New differential tests cover random disconnected/cyclic follow graphs, missing captures, non-inheritance through attachments, tank extent thresholds, and disabled native relationship preparation.
- Standard recorded Level 2-5 windows have unchanged decisions. Reports:
run_logs/relationships-level{2,3,4,5}-0908/comparison.json. - No new live campaign, Hatari integration or WASM profiling was performed.
Controlled measurements
Two pinned opposite-order repeats per recording, run sequentially after tests completed and with stable source fingerprints:
| Window | c-relationships-old |
c |
Change |
|---|---|---|---|
| Level 1, frames 2367-10059 | 3.29108 ms | 3.26953 ms | 0.65% faster |
| Composite capture, frames 39321-39800 | 8.80631 ms | 8.94153 ms | 1.54% slower |
Repeat spreads range from 0.14% to 0.65%. All decision verdicts match on both
repeats. This is mixed performance, not a broad speedup. The native computation
eliminates repeated relationship walks, but compatibility flag dictionaries and
Python dispatch remain. Artifacts:
run_logs/relationships-level1-timing-0908/comparison.json and
run_logs/relationships-composite-timing-0908/comparison.json.
Next move remaining tactical/specialized relationship consumers to native indices, with export removal tied to each map's last Python user. Adding further parallel compatibility dictionaries alone is unlikely to improve performance.
Native fallback eye chains and Level-4 head paths (ABI 29)
xap_world_prepare_boss_paths() now consumes the owned world directly. It resolves
fallback eye predecessor chains, rejects cycles and excluded ancestors, chooses
independent roots, and prepares their delayed paths in one batch. Traversal uses
existing world scratch; Python no longer builds predecessor-first member lists or
packs an XapDelayedMember for each object on this path. Only independent fallback
roots need velocity fitting, from the last eight owned history samples.
Level-4 swinging heads use their predecessor's latest observed world velocity, limited to twelve future frames. They do not use fitted velocity. All paths retain the object's own observed anchor at frame zero, including delayed members and heads whose future anchor jumps to a predecessor. Camera scroll is added once at each eye link's one-frame delay, preserving the previous rule.
The Python bridge passes an articulated-eye exclusion mask and receives compact path slots. C writes directly into the existing shared scene pool. Anchor, fractional-displacement, collision-step and fallback-eye body/combat consumers reuse these paths. Preparation is lazy: an observation with no requesting boss consumer does not build a batch. A longer requested horizon may append a new batch; completed views remain valid even if the pool grows. The usual full scene horizon is prepared on the first request to avoid repeated short extensions.
The articulated controller forecast remains separate and takes priority. Unsupported
scripted/attached eye chains retain reference prediction. The Python reference
assembler is retained explicitly as reference_delayed_entries(); set
native_boss_paths.ENABLED = False, or use c-boss-paths-old in
compare_kernels.py, to reproduce the previous path. The legacy numeric delayed
kernel remains callable. The new API is callable through ctypes and exported for
a future standalone C caller; no Hatari build integration was added.
Eye and Level-4 predecessor maps remain because unsupported and reference queries
still read them. Their removal is not complete in this phase. Articulated-eye
controller discovery/member assembly in eyes.py also remains Python work; it
should not be confused with the fallback delayed-eye chains migrated here.
Validation
- Standalone native build passed; ctypes now requires ABI 29.
- 105 native tests, 67 planner tests, and 575 forced-native gameplay tests passed.
- New tests cover reversed/branched chains, fitted root velocity, cycles, excluded ancestors, Level-4 live velocity and the twelve-frame cap, fractional and collision consumers, shared-batch reuse, stale owners and invalid output requests.
- Standard Level 2-5 decision comparisons passed unchanged:
run_logs/boss-paths-level{2,3,4,5}-0908/comparison.json. - Boss recordings passed unchanged on both repeats:
run_logs/boss-paths-eye-0908/comparison.json(frames 6835-8965) andrun_logs/boss-paths-level4-boss-0908/comparison.json(frames 67456-68655). - These are recorded-decision comparisons, not a new live campaign. No WASM profiling was performed.
Controlled measurements
Two sequential, CPU-pinned repeats in opposite orders, with stable source fingerprints within each comparison:
| Window | c-boss-paths-old |
c |
Change |
|---|---|---|---|
| Level-1 boss, frames 6835-8965 | 2.31055 ms | 2.31831 ms | 0.34% slower |
| Level-4 boss, frames 67456-68655 | 3.25004 ms | 3.18055 ms | 2.14% faster |
| Standard Level 1, frames 2367-10059 | 3.27434 ms | 3.32545 ms | 1.56% slower |
The gain is local and modest. The main removed work is Python chain assembly and repeated linked prediction; tactical policy and other compatibility exports remain. The one-repeat Level 2-5 runs establish correctness, not controlled speed claims.
The standard Level-1 comparison also has unchanged decisions on both repeats:
run_logs/boss-paths-level1-timing-0908/comparison.json. Its new-path repeats
spread by 2.58% (3.368 versus 3.283 ms), while the baseline spreads by 0.16%.
The mean is a modest regression with run-to-run variation; it is not evidence of
an overall speedup. The targeted Level-4 improvement and remaining Python hot
paths should be considered separately. All five standard levels passed.
Native articulated-eye assembly and shared output (ABI 30)
The articulated controller forecast is now assembled from C-owned world state.
The new XAP_WORLD_CAPTURE_EYES preparation flag captures Level-1 controller
parameters and resolves successor indices during the existing observation pass.
This is separate from the fallback delayed-eye predictor added in ABI 29.
xap_world_eye_capacity() supplies a conservative output-size bound; it returns
zero when no captured controller can own an eye chain. That avoids the previous
Python scans even on observations without an articulated eye. The batch API,
xap_world_prepare_eye_paths(), traverses controller chains using world scratch,
preserves last-successor selection, and calls the existing numerical predictor.
It emits compact path slots plus double-coordinate points directly into the shared
scene pool. Each member retains its observed frame-zero anchor.
The numerical eye forecast has one implementation with two output forms: the
original integer API remains available, while the world batch writes shared points
without an intermediate integer buffer. Python exports lightweight NativePath
views instead of constructing a tuple for every future point. Body/combat scene
construction can borrow these already registered paths. Pool growth preserves
completed views; update/restore invalidates captured C state, and stale native
owners fall back to the reference assembler.
reference_eye_trajectories() preserves the previous Python assembly. Controller
snapshots still exist in Python because boss tactics and candidate preparation
read them. Generic predecessor exports also retain users. Neither export was
removed merely because prediction assembly moved to C.
Controls and validation
c-eye-assembly-old: previous Python assembly and tuple results; new C eye capture is disabled, so its observation cost is included in the comparison.c-eye-assembly: C capture/assembly with Python tuple results retained.c: C capture/assembly and direct shared-buffer views.- Standalone C build passed; Python ctypes expects ABI 30. Both old and new native APIs remain Python-callable. No Hatari integration was added.
- 111 native tests, 67 planner tests, 18 shell/eye tests, and 575 gameplay-policy tests with native world updates forced on passed (771 total).
- New tests compare every output point against the Python numerical reference, including reordered chains, multiple controllers, duplicate successors, unarmed/missing controllers, disconnected cycles, observed anchors, shared-view lifetime, invalid capacity, stale observations and disabled capture.
- The targeted Level-1 boss recording (frames 6835-8965) retained all decision verdicts in both opposite-order repeats across all three variants.
Controlled measurements
CPU-pinned workers ran sequentially with stable source fingerprints. Times below are mean recorded-decision time, not live game completion time.
| Window | Previous assembly | C assembly, tuples | C assembly, shared points |
|---|---|---|---|
| Level-1 boss, frames 6835-8965 | 2.25802 ms | 2.26477 ms | 2.22455 ms |
| Standard Level 1, frames 2367-10059 | 3.27704 ms | 3.27808 ms | 3.24867 ms |
On the boss window, assembly alone was 0.30% slower. Buffer sharing improved that
intermediate result by 1.78%, for a combined 1.48% improvement. The added C capture
and remaining Python controller exports help explain why assembly alone is not a
speedup. Artifact: run_logs/articulated-eye-final-0908/comparison.json.
No new live campaign or WASM profiling was performed. The next compatibility boundary to remove is the Python controller-state use in boss tactics/candidate preparation; the core articulated forecast itself is already native.
The standard Level-1 window retained all verdicts in both repeats across the three
variants. Assembly alone was 0.03% slower; shared output improved that intermediate
result by 0.90%, for a combined 0.87% improvement. Repeat spreads were 0.57%, 0.11%
and 0.42% respectively. Artifact:
run_logs/articulated-level1-final-0908/comparison.json.
The final build also rejects negative dimensions before doing capacity arithmetic in the retained integer API; this invalid-input guard does not change forecasts. Native tests were rerun after that check was added. The performance reports retain their exact source/DLL fingerprints from the measured valid-input implementation.
Standard Level 2-5 decision comparisons also passed unchanged. Reports:
run_logs/articulated-level{2,3,4,5}-0908/comparison.json. These one-repeat runs
were correctness checks; they are not used for controlled performance claims.
Native boss-core selection and tactical geometry (ABI 31)
C now selects the first captured Level-1 controller whose hit handler is
XENON_HIT_PROC_BOSS_EYE_CORE and computes its target top and aim X during the
observation pass. XAP_WORLD_BOSS_CORE enables this work alongside eye capture.
The handler address formerly written as $503DA now has matching named Python
and C constants.
xap_world_boss_core() returns a small borrowed XapBossCore record containing
the selected population index, target top and aim X. The adapter resolves the
selected Python object once without inspecting controller states. Observations
without a selected core do not allocate a Python selection snapshot.
XapCandidateRequest.core optionally points directly to this owned record. Both
prepared and streamed candidate evaluation read its top in C. The original
core_top scalar remains the NULL-pointer reference fallback. The Python bridge
supplies the native pointer only when the mission target is the selected current
object and its owner still has the captured observation stamp. Other targets and
stale snapshots use the reference geometry. Non-core missions no longer compute
unused controller geometry when preparing candidates.
The normal native world path no longer eagerly decodes a Python
BossEyeControllerState. It retains the original immutable controller bytes and
uses a localized descriptor to decode/cache the compatibility value only on an
explicit read. Reference prediction, inspection and retained snapshots therefore
still see the original boss_eye_controller interface. The bytes do not borrow C
memory, so an old snapshot remains usable after world update/restore. Assigning a
controller value clears any deferred bytes. The Python reference world continues
to decode eagerly.
This removes the eager full-controller export from the normal native path, not all observability support or every predecessor map. The remaining Python planner fallback reads the cached compact top when available; C candidate evaluation consumes the native record directly. Full tactical state machines remain Python.
Validation and comparison control
- Standalone native build passed; ctypes now requires ABI 31. No Hatari integration.
- 116 native tests, 67 planner tests, 18 shell/eye tests and 575 forced-native gameplay-policy tests passed (776 total).
- New tests cover first eligible controller selection, missing captures, overlay restrictions, direct C candidate consumption instead of the fallback scalar, absence of Python decoding during native queries, lazy decode caching, explicit assignment, and retained snapshots after native world advancement.
c-boss-core-olddisables native core preparation/selection and retains eager controller decoding.cenables the new path. Both use the current shared eye predictor and compatibility interface.- Two CPU-pinned opposite-order repeats of the boss recording retained all recorded decision verdicts. Workers ran sequentially with stable fingerprints.
| Window | c-boss-core-old |
c |
Change |
|---|---|---|---|
| Level-1 boss, frames 6835-8965 | 2.24238 ms | 2.23282 ms | 0.43% faster |
| Standard Level 1, frames 2367-10059 | 3.29341 ms | 3.29369 ms | Effectively unchanged |
Artifact: run_logs/boss-core-eye-timing-0908/comparison.json. This is a small
measured difference, not a substantial performance gain. The principal change is
native ownership of tactical inputs and removal of eager Python decoding.
No new live campaign or WASM profiling was performed.
The standard Level-1 comparison also retained every verdict in both repeats.
The 0.008% timing difference is negligible; repeat spreads were 0.32% and 0.35%.
Artifact: run_logs/boss-core-level1-timing-0908/comparison.json.
Standard Level 2-5 correctness comparisons passed unchanged:
run_logs/boss-core-level{2,3,4,5}-0908/comparison.json. Their single-repeat times
are not used for controlled performance claims. Overall this phase closes a
native ownership boundary with essentially neutral broad-window performance.
Candidate fire-scene preparation (ABI 32, 2026-09-08)
Step 1 of the remaining decision-scene migration is independently validated.
xap_prepare_candidate_fire constructs shell-release descriptors, anticipated
shot descriptors and player fire masks in one call. All sources borrow immutable
C velocity tables, instead of allocating three/eight Python points per source.
The shell and shot descriptors shrink from 56/152 to 16/32 bytes on the tested
64-bit native build. Shared table storage is excluded from per-scene byte metrics.
The table pointers remain valid for the loaded library lifetime. Input seeds are
not retained; output arrays remain caller-owned. The Python comparison path owns
its separate velocity arrays through ctypes references.
Source selection, predicted shot-origin anchors, pickup metadata, entry bounds and homing initialization still have Python preparation. This is not yet a wholly native scene constructor. The standalone library remains callable from Python; no Hatari integration or WASM profiling was performed.
Validation: standalone build and 780 tests passed (120 native, 67 planner,
18 shell/eye, 575 forced-native gameplay-policy). Added coverage checks source
ordering, shell directions, composite offsets, radial speeds, aimed thresholds,
fire masks, shared table lifetime and rejection before output mutation.
c-scene-fire-old retains Python descriptor construction for ablation.
All standard Level 1-5 verdicts and the targeted boss/composite verdicts match.
These are recorded-observation comparisons, not a new live campaign.
Two CPU-pinned opposite-order repeats, sequential workers:
| Window | Python preparation | Native batch | Interpretation |
|---|---|---|---|
| Level-1 boss, 6835-8965 | 2.27095 ms | 2.28168 ms | 0.47% slower; approximately flat |
| Standard Level 1, 2367-10059 | 3.35951 ms | 3.28710 ms | Reference repeat spread 4.14%; no reliable speedup claim |
| Level-3 composite, 39321-39800 | 8.89180 ms | 8.88051 ms | 0.13% faster; approximately flat |
Artifacts: run_logs/scene-fire-eye-timing-0908/comparison.json,
run_logs/scene-fire-level1-timing-0908/comparison.json,
run_logs/scene-fire-composite-0908/comparison.json, and
run_logs/scene-fire-level{2,3,4,5}-0908/comparison.json.
The confirmed benefit is less descriptor allocation/copying and native ownership
of constant tables; broad runtime remains effectively unchanged.
Next bounded step: initialize native combat directly, removing the temporary Python CombatRollout and its health/bullet containers before native packing. Keep each subsequent step separately validated, documented and committed.
Direct native combat initialization (ABI 33, 2026-09-08)
Native candidate workspaces and non-reused native candidates now initialize combat from the captured native world. They no longer construct a temporary Python CombatRollout, health dictionary, animated-target set and Bullet objects only to copy them into ctypes arrays. Empty supported-target sets skip setup. The separate Python simulation remains available for reference and the generic Python trajectory path.
The previously reserved ObjectFields word now holds named numeric combat flags: ordinary damage callback, complete captured player shot, and cannon shot. These are classified during the existing decode pass. The native constructor reads health and velocity fields directly and preserves canonical bullet order. Enemy bullets are excluded. Cannon shots retain renderer-centre placement (anchor fallback); missing ordinary projectile captures do not invent a shot. Target animation margins and damage-callback restrictions are unchanged.
The caller sizes storage with xap_world_combat_bullet_count, then initializes with xap_world_prepare_combat. Output is caller-owned and remains valid after world advancement. The bridge checks the canonical owner stamp and retains a reference fallback for non-native observations. No input pointers are retained. The capacity count uses cheap native scans rather than Python object creation; there is no second persistent health/bullet mirror in Python.
Standalone build and 784 tests passed: 124 native, 67 planner, 18 shell/eye, 575 forced-native gameplay-policy tests. New tests compare initial health and player bullet values against the Python reference, missing captures, disabled future fire, stale owners and output capacity rejection before writes. Ablation: c-combat-initial-old retains reference construction; c enables direct initialization. No Hatari integration or WASM profiling.
Two CPU-pinned opposite-order Level-1 repeats (frames 2367-10059) retained every
verdict: 3.28617 ms reference initialization versus 3.27309 ms direct native
initialization (0.40% faster). Repeat spreads were 0.35% and 0.18%; this is a small
measured change, not a large speedup. Artifact:
run_logs/combat-initial-level1-timing-0908/comparison.json.
Standard Level 2-5 recorded-observation comparisons also retained all verdicts:
run_logs/combat-initial-level{2,3,4,5}-0908/comparison.json. Single-repeat timings
are correctness checks, not controlled speed claims. No new live campaign run.
Combat range preparation and empty-scene waste (ABI 34, 2026-09-08)
Candidate scenes now request all frame ranges as a batch. When native bounds are complete, xap_prepare_combat_ranges emits their regular frame-major ranges without Python frame calls, temporary tuples or dictionary entries. When there are no extra blockers, the zeroed range array is already complete and no C call is needed. Empty blocker scenes no longer allocate 256 unused XapCombatBounds. Mixed scenes and requests beyond the prepared horizon retain lazy reference geometry preparation; later individual frame queries remain compatible.
CandidateWorkspace also avoids restoring bytes immediately after capturing a freshly initialized rollout. Later trials still restore mutable state normally. The ablation c-combat-ranges-old independently restores the previous range loop, empty buffer allocation and redundant first restore.
Standalone build and 788 tests passed (128 native, 67 planner, 18 shell/eye, 575 forced-native gameplay-policy). New tests cover full native batches without Python frame queries, subsequent lazy access, empty storage, mixed-scene fallback and dimension-overflow rejection before writes. No Hatari or WASM profiling.
All standard Level 1-5 recorded verdicts matched. Two opposite-order pinned
Level-1 repeats measured 3.32855 -> 3.28356 ms, but reference/new repeat spreads
were 4.48%/1.52%; the first pair was 3.256/3.259 ms. Do not treat the aggregate
as a reliable speedup. Reports: run_logs/combat-ranges-level1-timing-0908/comparison.json
and run_logs/combat-ranges-level{2,3,4,5}-0908/comparison.json. No live campaign.
Native homing initialization (ABI 35, 2026-09-08)
xap_prepare_homing_states initializes observed Level-2/5 enemies and predicted Level-5 launcher allocations in one batch. It replaces temporary Python homing state objects and their offset/bounds conversion. Python still selects sources, predicts launcher anchors and supplies compact observed fields; these inputs are not yet selected directly from XapWorld. Positive spawn frames identify launcher allocations. Empty source sets skip seeds and native calls; launcher inputs omit unused observed hull, age and animation fields.
The immutable Level-2 animation tables now live in C, with documented reference data mirrored in xenon_symbols.py. xap_default_homing_tables returns a borrowed pointer valid for the library lifetime. Candidate scene consumers retain that borrowed view. Python reference initialization and tables remain available under c-homing-initial-old. Observed sprite headers, direction fallback envelopes, rounding, mirrored launcher offsets, target indices and source order are covered by direct state comparisons. No Python callbacks occur inside the C initializer.
Standalone build and 791 tests passed: 131 native, 67 planner, 18 shell/eye, 575 forced-native gameplay-policy. The new tests compare every Level-2 animation sprite, an unknown-sprite fallback, observed Level-5 state, mirrored launchers, ignored beyond-horizon spawns, immutable table values and invalid-model rejection. No Hatari integration or WASM profiling.
Two opposite-order pinned Level-2 repeats (32577-33537) retained every verdict:
6.02185 -> 6.03082 ms, 0.15% slower, effectively flat relative to the 1.47%/1.19%
repeat spreads. Artifact: run_logs/homing-initial-level2-timing-0908/comparison.json.
All standard Level 1-5 verdicts also match:
run_logs/homing-initial-level{1,2,3,4,5}-0908/comparison.json. These single-repeat
windows are correctness checks. No new live campaign was run.
Pickup scene batching and list-scan removal (ABI 36, 2026-09-08)
Candidate construction reads WorldState.pickups once, instead of repeatedly filtering the complete object population for allocation, flags and paths. xap_world_prepare_candidate_pickups prepares paths through the existing kernel and reuses its compass decision to fill first-rectangle, scroll-coupling and incompatibility descriptors. This adds no extra native call to the path workflow. Empty pickup sets return empty arrays immediately. Python retains the tactical check for replacing a valuable attachment and passes its exclusion flags.
The non-owned-world fallback shares the cached list while retaining existing path preparation. c-pickup-scene-old preserves the previous repeated scans and Python descriptor construction for an independent comparison. C output remains caller-owned, with the same path order and per-pickup horizon layout.
Standalone build and 794 tests passed: 134 native, 67 planner, 18 shell/eye, 575 forced-native gameplay-policy. Added coverage verifies combined output against existing paths for linear and compass motion, tactical flags, capacity rejection before either output changes, and a single property read/no native call for empty scenes. No Hatari integration or WASM profiling.
Two opposite-order pinned Level-1 repeats retained every verdict: 3.25001 ->
3.26129 ms (0.35% slower), effectively flat relative to the 1.01% reference spread.
All standard Level 2-5 verdicts also match. Artifacts:
run_logs/pickup-scene-level1-timing-0908/comparison.json and
run_logs/pickup-scene-level{2,3,4,5}-0908/comparison.json. No new live campaign.
Scope after these independently committed preparation steps
C now constructs fire descriptors, initial combat buffers, complete combat frame ranges, homing states/animation tables and owned-world pickup descriptors/paths. The Python reference and ablations remain available. The changes reduce repeated preparation and advance the C interface; measured total runtime remains broadly flat apart from the small combat-initialization change. The individual timing results must not be added together as a cumulative speedup.
Python still owns scene allocation/lifetime and source selection, predicted launcher/shot anchors, simple bottom-entry metadata, tactical pickup exclusions, reference geometry fallback and the generic Python trajectory/combat path. Mission/navigation orchestration, candidate scheduling, retained-plan policy and runtime integration are also not fully native. These commits do not make the whole autopilot runnable without Python. The next larger migration boundary is native scene source selection and ownership, rather than adding tiny wrappers for isolated scalar calculations. Tests and replay observability can remain Python.
Native scene source selection (ABI 37, 2026-09-08)
xap_world_select_scene_sources routes the scored canonical selection using owned object fields and captured scripts. One C pass identifies Level-2/5 homing sources, independent bodies, supported-destruction models, camera flags and first launcher, periodic, barrier and composite allocations. Field presence already records handler/overlay eligibility; the selector does not repeat level checks or use classification strings. Delays use exact byte-carry arithmetic and preserve zero increments/no-shot cases. Caller output follows scored selection order.
Periodic accumulator/rate fields now belong to the shared native/Python scalar decoders. Their inline decoding was removed from WorldState.update. The radial leader's fixed rate remains available even when its accumulator capture is absent, matching the old constructor. Python builds the remaining consumer lists/maps in one pass; it still owns risk grouping and broader scene orchestration. Unavailable native owners/scripts retain the original reference selector.
Ablation c-source-selection-old disables native routing. Both variants share the new scalar-field decoding location, so the measured difference isolates routing, not that accompanying world-update cleanup. The native batch executes once per Level-1 gameplay observation (mean 11.81 scored sources in the comparison).
Standalone build and 803 checks passed: 137 native tests, 67 planner tests,
18 shell/eye tests, 575 forced-native gameplay-policy tests and 6 decoder tests.
New selection coverage includes overlay-specific spawners, zero/aimed rates,
homing/independent partitions, supported damage models and invalid selection.
All standard Level 1-5 recorded verdicts matched. Two opposite-order pinned
Level-1 means were 3.25354 -> 3.26289 ms (0.29% slower); repeat spreads were
1.27%/0.57%, so the result is effectively flat. Reports:
run_logs/source-selection-level1-timing-0908/comparison.json and
run_logs/source-selection-level{2,3,4,5}-0908/comparison.json.
No new live campaign, Hatari integration or WASM profiling.
The retained Python gameplay suite also passed (575 tests), as did the compact
world-preparation check. A separate instrumented 1001-frame profile is saved as
run_logs/source-selection-profile-0908/c-1.pstats; its timings are not comparable
to uninstrumented benchmark means. It shows 56,000 BodyScene.frame_slices calls
(0.281 s cumulative), despite only 0.545 remaining body sources per observation,
of which 0.466 already have native linked geometry. That identifies a concrete
next waste-removal step: batch empty/fully native slice ranges instead of running
the Python frame preparation machinery for every horizon frame. Candidate-scene
construction itself was only 0.056 s cumulative in this instrumented window.
Batched native body-slice horizons (ABI 38, 2026-09-08)
The instrumented profile showed the driver repeatedly entering frame_slices even when no Python geometry was needed. Maneuver scenes now request a full range table. After the first frame establishes source ordering, empty scenes use zeroed ranges without further frame calls or a 256-slice allocation. They also skip the empty LinkedSlices wrapper. Fully linked scenes pack remaining frames with xap_pack_linked_slice_frames, checking complete dimensions/paths before writes. No trajectories are re-predicted and no first-frame packing is repeated.
The original single-frame packer and the batch share one small sweep helper. The regular range helper is now named xap_prepare_slice_ranges because both body and combat consumers use it. Its old ABI-34 name was xap_prepare_combat_ranges. Native ranges preserve collision order, target indices, camera flags and labels. Python repeats immutable label references in one operation and retains a compact regular range description instead of a dictionary tuple for every horizon frame. Later individual-frame queries remain valid. Mixed sources, anticipated-shot fallbacks and already irregularly prepared scenes retain the original frame loop.
c-body-slice-batches-old disables batching, lazy empty storage and empty-wrapper removal. Its frame loop retains the original field assignments, so it does not artificially add temporary ctypes objects to the comparison control.
Build and 806 checks passed: 140 native, 67 planner, 18 shell/eye, 575 forced-native gameplay-policy and 6 decoder tests. New coverage compares batched and per-frame packing, untouched output slots/earlier frames, invalid capacity rejection, complete and empty horizons, label order and later lazy access. The existing mixed-scene test now also exercises whole-horizon preparation. No Hatari or WASM integration/profiling was performed.
All standard Level 1-5 recorded verdicts matched. Two opposite-order CPU-pinned
Level-1 repeats measured 3.23509 -> 3.16617 ms (2.13% faster). Both pairs improved;
reference/new repeat spreads were 1.02%/0.38%. Reports:
run_logs/body-slice-batches-level1-timing-0908/comparison.json and
run_logs/body-slice-batches-level{2,3,4,5}-0908/comparison.json.
A separate instrumented run of the same 1001-frame diagnostic window reduced
frame_slices calls from 56,000 to 4,630 (91.7% fewer). The new frame_ranges wrapper
used 0.058 s cumulative, including 0.049 s in remaining frame_slices calls; the
old frame_slices alone used 0.281 s. Do not add these nested times or compare
instrumented timing directly with ordinary benchmark means. Prepared slice counts,
linked-slice counts and logical slice bytes were unchanged. 93.4% of candidate
scenes used a horizon batch, including 82.9% with no remaining slices. Profile:
run_logs/body-slice-batches-profile-0908/c-1.pstats.
Remaining boundary after this preparation series
These steps moved initial combat, homing initialization, pickup metadata, routing within the scored subset and native horizon packing into C, while retaining reference controls. Python still chooses the scored population, owns consumer containers/scene lifetime, performs risk grouping and specialized fallback preparation, and drives mission/navigation/planner/runtime orchestration. The standalone API remains Python-callable and is not integrated into Hatari yet. The next substantial migration should move orchestration/ownership across that boundary; the profile does not justify more isolated scalar-wrapper ports.
Fixed native scene slice storage (ABI 39)
The first ownership step gives C a fixed slice allocation for each observation. It is reserved once from the configured horizon and partitioned sources: at most one slice per remaining body, one shot per ordinary wall shooter, and eight shots per Level-5 destructible tile pair, per frame. These are bounds on the current slice generators, not a guessed maximum number of visible game objects. Empty scenes allocate nothing. Changing a generator to emit additional slices requires updating this bound; exceeding it raises an error rather than overwriting memory.
There is no native growth, relocation, old-buffer list, or whole-buffer clearing. Only initialized slices are exposed through frame ranges and slice_count. Python views retain the native owner until the last view expires. Shared path geometry continues to be borrowed from its existing owner; copying it would add waste. The maneuver header is reused, refreshing mutable slice pointer/count fields.
The retained c-scene-storage-old control uses Python buffers and header packing. Reference preparation without partitioned sources retains Python growth. This step does not yet transfer complete scene assembly or persistent cross-observation buffer reuse to C; consumer selection, frame-range arrays and headers still have Python owners. The C API remains separately buildable and Python-callable.
Build and 803 checks passed: 143 native, 67 planner, 18 shell/eye and 575 forced-native gameplay-policy tests. All five standard recorded-level verdicts matched. Opposite-order CPU-pinned Level-1 means were 3.17767 -> 3.17110 ms (0.21% faster), within reference/new repeat spreads of 0.48%/0.30%; no reliable speedup is claimed. Reports: run_logs/scene-storage-level1-timing-0909/comparison.json and run_logs/scene-storage-level{2,3,4,5}-0909/comparison.json. Later levels were single-repeat correctness checks, not controlled speed claims.
Native candidate outputs and shared header (ABI 40)
The candidate scene now owns its generated output arrays in C: pickup descriptors, pickup rectangles, shell release descriptors, bottom-entry descriptors, fire masks and anticipated-shot descriptors. xap_candidate_scene_create reserves these fixed capacities from the selected source counts and horizon, initializes the shared header and attaches the immutable native homing tables. Empty source arrays have NULL pointers. Preparation fills the generated arrays directly; they are not zeroed before being overwritten. Fire preparation still clears its sparse mask once, because frames without an edge must contain zero.
Python retains typed views of those buffers for the existing preparation APIs and reference variants. The views keep the native allocation alive. Destruction frees only the generated outputs; shell paths, body geometry, wall data and deferred combat buffers remain borrowed from their existing owners. Generated pointer fields must not be replaced before xap_candidate_scene_free. Native callers can fill the same named fields and call the existing coarse preparation/evaluation APIs.
CandidateScene.packed now returns the same C header for all trials. Body geometry is attached once, and combat is attached only if a trial needs it. Subsequent lazy slice/bound additions refresh the affected pointer/count only when the initialized count changes. This relies on the existing append-only preparation within an observation; buffers must not be replaced with unrelated same-count contents.
Shot selection was extracted into a documented helper and still predicts each selected spawn anchor once. The pickup list is read once and passed with output views to preparation. No generated geometry is copied to migrate ownership. c-candidate-scene-old retains Python output allocation and per-trial header assembly for comparison. It shares the source-selection refactoring with the new path so the control isolates output ownership and packing.
This is per-observation ownership, not a cross-observation pool. Initial homing states, combat buffers, maneuver/frame-range ownership and higher-level source selection/orchestration remain migration work. There is no Hatari integration or WASM profiling in this step.
Build and 807 checks passed: 147 native, 67 planner, 18 shell/eye and 575 forced-native gameplay-policy. New tests cover fixed output dimensions and invalid capacities, retained view lifetimes and borrowed paths, preparation directly into native pickup outputs, and shared headers with deferred/extended combat bounds.
Two opposite-order CPU-pinned Level-1 repeats measured 3.18877 -> 3.17579 ms (0.41% faster). Both pairs improved, but this is a small change, comparable in scale to the 0.38% reference repeat spread (new spread 0.06%). It is primarily an ownership migration, not a substantial overall speedup. Timing artifact: run_logs/candidate-scene-level1-timing-0909/comparison.json.
All five standard recorded-level verdicts match. Level 2-5 reports are run_logs/candidate-scene-level{2,3,4,5}-0909/comparison.json; those single repeats are correctness checks rather than controlled speed claims.
The separate 1001-frame diagnostic profile confirms the removed repeated work: maneuver-header packed calls fell from 4,938 to 1,000, and combat-header packed calls from 2,771 to 94. CandidateScene.packed still runs 4,938 times, but now returns the shared header; its cumulative instrumented time fell from 0.0500 s to 0.0107 s. Candidate scene construction rose from 0.0582 s to 0.0965 s, largely offsetting that saving while Python still creates native output views and their ownership references. Total instrumented time was essentially flat (6.2200 -> 6.2156 s). Do not compare these instrumented timings with ordinary benchmark means. Profiles: run_logs/candidate-scene-profile-0909/{c-candidate-scene-old,c}-1.pstats.
Further migration should remove the Python view/assembly bridge itself by letting C preparation and evaluation share this scene directly. Repeatedly moving small allocations across the FFI without removing their Python consumers is unlikely to deliver a substantial speedup.
Direct preparation-to-evaluation handoff (ABI 41)
xap_prepare_candidate_scene accepts compact source inputs, prepares pickups and fire descriptors directly into the owned candidate buffers, constructs bottom-entry rectangles, and attaches borrowed body/shell geometry. Evaluation receives this same native pointer. Generated pickup, shell, entry, shot and fire-mask output arrays and the candidate header no longer require Python views in the direct path.
native_direct_scene.py is separate from the retained preparation implementation. Workspace allocation uses known pickup/shell counts rather than len() on generated output views. Deferred combat is attached by xap_candidate_scene_bind only when needed; subsequently extended body/combat buffers are rebound when their initialized counts change. Required input objects and borrowed geometry remain Python-owned where their producers have not yet migrated.
DirectCandidateScene.inspect() is an explicit diagnostic operation. It creates a borrowed header view retaining the scene and its geometry owners; callers must use it as read-only. It is never called by driving. A test runs actual maneuver and candidate evaluation with inspect(), CandidateStorage.header(), and CandidateStorage.array() patched to fail, ensuring the direct driving path does not materialize diagnostic/output views. Replay UI currently consumes its existing recorded diagnostics, so no new per-frame inspection call was added to recording or replay rendering. Native outputs can be inspected explicitly by tests or a future UI diagnostic adapter while the scene remains retained.
The c-direct-scene-old control retains ABI-40 Python view/assembly preparation. Explicit reference-model controls, stale/non-native pickup owners and shell paths not covered by the shared predictor retain compatibility preparation. Counters native_direct_candidate_scenes and native_candidate_reference_scenes expose actual coverage without materializing outputs. Initial homing state and remaining maneuver/combat preparation are functional inputs, not optional diagnostics; their remaining Python consumers are not claimed to have migrated in this step.
The default direct path also omits empty input arrays: absent shot/shell seeds, bottom entries, firing edges and pickup-exclusion inputs are passed as NULL with zero counts. c-direct-empty-inputs-old isolates that removal from the preceding direct handoff. Input counts and inexpensive budget counters remain available in driving; diagnostic geometry/header materialization does not run implicitly.
The initial untrimmed handoff timing was noisy (3.11645 -> 3.09687 ms, with reference/direct spreads of 2.99%/0.91% and opposite pair directions), so it is not a reliable speedup claim. Artifact: run_logs/direct-scene-level1-timing-0909. Final controlled timings and replay/profile results are recorded below.
Build and 810 checks passed: 150 native, 67 planner, 18 shell/eye and 575 forced-native gameplay-policy tests. The explicit no-observability driving test executes real native candidate evaluation rather than merely checking a flag.
Final opposite-order CPU-pinned Level-1 means were 3.22833 ms for the retained view bridge, 3.22754 ms for direct preparation before empty-input removal, and 3.24317 ms for the final direct path. Repeat spreads were 0.32%, 1.70% and 2.98%. The final mean is 0.46% slower, but pair directions disagree and variation is much larger than that difference: no reliable overall speedup or regression is claimed. Empty-input removal similarly has no independently established timing gain. All Level-1 verdicts match and every candidate scene uses direct preparation. Artifact: run_logs/direct-scene-final-level1-timing-0909/comparison.json.
All five standard recorded-level verdicts match, and all candidate scenes in those windows use direct preparation with zero compatibility fallbacks. Reports: run_logs/direct-scene-level{2,3,4,5}-0909/comparison.json. Those single-repeat later levels establish coverage/correctness, not standalone speed claims.
The separate 1001-frame diagnostic profile confirms zero inspection calls and removal of 6,000 CandidateStorage.array calls, 1,000 CandidateStorage.header calls and 4,938 CandidateScene.packed calls. Direct native_pointer ran 4,938 times without constructing generated views. Total instrumented calls fell 14,035,296 -> 13,859,541; instrumented time was 6.3713 -> 6.2641 s. This verifies removed Python work, but is not a substitute for the inconclusive ordinary wall-clock comparison. Profiles: run_logs/direct-scene-profile-0909/{c-direct-scene-old,c}-1.pstats.
Native candidate workspace and trial reset (ABI 42)
xap_workspace_create owns fixed-capacity mutable homing states, sample ranges, homing bounds, collection flags and shell-release flags for one candidate scene. The scene remains borrowed and must outlive the workspace. Initial homing state is captured once from the scene's existing preparation; its producer has not yet moved into this workspace. Empty arrays are not allocated. Only the sample-range template is cleared at allocation; homing bounds/scratch are overwritten by evaluation.
xap_workspace_run resets and evaluates in one call, selecting fused forecasting when a trial input is present and pre-forecast evaluation otherwise. Homing resets occur on every run, including deferred-combat retries and wall rechecks. Collection and release flags are not reset twice: the existing evaluator already clears them.
Combat remains lazy. Target selection and native world capture run only when combat is first requested. C captures immutable initial targets and player bullets, sizes live bullet capacity from captured bullets plus the scene's in-horizon fire masks, and derives scratch capacity from the prepared combat ranges and homing count. The initial bullet snapshot contains only captured bullets, not future empty slots. Each combat trial resets target health/deaths and counters, copies only that bullet prefix, and reuses scratch without clearing it. No Python bytes snapshots, temporary NativeCombatRollout buffers or per-trial memmove calls are needed on this path.
native_workspace.py retains only the scene owner, handle and planner-facing combat metadata/results. Initial target membership and final damage/death/pickup results are functional planner inputs, so reading them is not optional observability. Internal homing/sample/release/scratch arrays are never materialized in Python. Borrowed live combat arrays and collection flags are readable after a successful run; initial target membership is available before the first run.
The old CandidateWorkspace remains separate, selected by c-workspace-old or when native direct-scene/world inputs are unavailable. Barrier/dependent-reference paths still use their existing non-reusable preparation. C workspace ownership is not a claim that all planner orchestration or initial homing preparation has migrated. No Hatari integration or WASM profiling is included.
Build and 815 checks passed: 155 native, 67 planner, 18 shell/eye and 575 forced-native gameplay-policy tests. New checks exercise consecutive driving candidates without a Python workspace, invalid inputs, and combat resets after mutating health, death frames, counters, flags and bullets. A sentinel in unused bullet capacity remains untouched, proving that only the captured prefix is copied.
Health capture precedes collision-geometry preparation. A zero-health result stops before building combat bounds or allocating scratch. The diagnostic profile caught an intermediate ordering mistake that prepared 160 combat scenes instead of the reference's 94; that extra work was removed before the final measurements. Scratch is allocated on the first active run, after combat geometry has been attached. A dedicated zero-health test forbids geometry preparation, and failed capture retries remain errors rather than silently disabling combat.
Final Level-1 opposite-order CPU-pinned means improved 3.20754 -> 3.12448 ms (2.59% faster). Both pairs improved; reference/new repeat spreads were 1.37%/0.12%. All five standard recorded-level verdicts match, and the tested scenes select the native workspace. Reports: run_logs/workspace-final-level1-timing-0909/comparison.json and run_logs/workspace-final-level{2,3,4,5}-0909/comparison.json. Later-level single repeats are correctness checks, not controlled timing claims. Final cleanup also skips the collection-result accessor for zero pickups and keeps capture/attachment failures visible; these do not alter successful trial decisions.
The final 1001-frame diagnostic profile removes 4,938 Python reset_homing calls, 1,000 Python CandidateWorkspace constructions, and 160 calls through the old native_combat_initial.create/capture bridge. PrefixBudget construction remains in Python and must not be confused with the removed workspace constructor. Combat geometry preparation is 94 calls in both variants, confirming the zero-health gate. The old prepare_combat wrapper took 0.0258 s cumulative versus 0.0141 s for the new wrapper; reset is now inside xap_workspace_run, not a separate Python operation. Total instrumented calls fell 13,877,399 -> 13,801,253 and instrumented time was 6.2069 -> 6.0572 s. Do not compare instrumented times with ordinary means. Profiles: run_logs/workspace-final-profile-0909/{c-workspace-old,c}-1.pstats.
ABI 43: homing outputs stay in C
Direct scenes now supply homing seeds to xap_workspace_create_seeded; C initializes
its reset snapshot in place. DirectCandidateScene.homing is a lazy compatibility
property, unused by native driving. Source selection/seed assembly remain Python
migration work. Validated with 156 native tests, 67 planner tests and the Level-2
homing replay window; no changed verdicts.
ABI 44: homing observation inputs come from the world
The native world captures homing counters and animation fields during its existing
observation pass. xap_workspace_create_world initializes directly from selected
native indices; only future launcher anchor overrides cross from Python. Reference
seed packing and explicit initial-state inspection remain available. 158 native tests,
67 planner tests, and Level-2/5 replay comparisons passed. Source selection and launcher
anchor orchestration still need migration; this does not complete scene preparation.
ABI 45: immutable maps embedded in C
The standalone kernel now provides all reviewed resident tiles and destructible-group
metadata through xap_level_asset (see xenon_autopilot_maps.h). Regenerate with
python xenon_tools/generate_native_level_maps.py; verify with --check. Generated C
is checked in, so native builds do not run Python or load JSON. Native live/controller-
owned maps use an initialization-only compatibility export while mutable map consumers
remain Python. Original tile words, partial destruction metadata and arena scratch
semantics are preserved. Full C map ownership remains a separate migration step.
ABI 46: wall rasters stay in native storage
Native navigation assembles wall patterns through xap_tile_raster_create and shares
that immutable raster directly with candidate preparation. Python reference consumers
can request rows lazily; native queries do not export/re-upload them. Headers retain
owners across map revisions. All five standard replay windows matched, with 165 native
and 67 planner checks passing. Mutable tile observations, tile-mask source selection,
remaining scene assembly and the planner/controller still need migration.
Driving-only map details are optional
Native driving disables per-cell last-observed timestamps and observation totals; unchanged geometry therefore needs no fresh MapCell. Destruction evidence is still updated, and replay/reference maps collect full details by default. Map exports mark disabled details explicitly. All five replay windows matched; controlled Level-1 replay processing improved 4.07%. Mutable map observation handling is still Python migration work.
ABI 47: C owns mutable tile observations
XapMapState now owns gameplay cells and row history; one observation call maintains
static-wall monotonicity and two-frame destructible clearing. Native level initialization
uses embedded assets directly. The Python adapter exports changed cells only and
reimports copied checkpoint cells when restored; mutable native history is never shared
between a live map and its checkpoint. Optional detailed observation metadata keeps the
reference path. All five replay windows matched; 169 native, 67 planner, 575 policy and
six terrain-observation checks passed. Python span search, mask selection, higher-level
map tactics, scene assembly and controller orchestration still remain.
ABI 48: C owns corridor graphs and route search
XapSpanGraph consumes a free-cell grid once and owns row spans, a bounded
decrease-key heap and route output storage. One xap_span_route call performs the
complete search; adjacent spans are derived from overlapping intervals instead of
stored edge lists. Python exports only the selected route for remaining consumers.
Graphs are cached on the persistent map, not the per-decision navigation wrapper.
Checkpoint copies share graphs through separate cache dictionaries; Python serializes
search and result export because the graph's scratch storage is mutable.
The reference span search remains available through c-span-search-old.
This removes Python graph/search execution, not the remaining route-goal policy, tile-mask selection, scene assembly or planner/controller orchestration.
ABI 49: destructible-group status is maintained, not rescanned
Native map creation counts each group's known and solid members. The existing cell
update adjusts these totals; xap_map_group_is_open is a constant-time numeric query.
Python retains the authored-name adapter and reference scan. Unknown/empty groups
are closed, reopened tiles make a group closed again, and checkpoint imports rebuild
their own totals. This removes repeated complete-map scans from gate tactics.
Level-3 and Level-5 replay windows matched in both timing orders, improving 2.91%
and 56.87% respectively; these are window-specific, not whole-game measurements.
ABI 50: barrier volleys no longer force Python trial preparation
Native candidate scenes accept compact directional/radial barrier seeds. C advances their aim until the allocation frame and checks each reached frame's volley inline. The normal barrier path now uses the native workspace, without a Python geometry pass or a second candidate evaluation. Explicit comparison and unsupported custom sources retain the old preparation. Active-barrier replay decisions matched in both orders; mean processing improved 31.46%, with candidate calls halved from 64 to 32 per observation. World-to-seed selection and high-level scene/planner orchestration are still Python consumers; this does not complete a Python-free controller.
ABI 51: barrier inputs come directly from captured world state
Normal preparation now passes selected object indices to
xap_candidate_scene_set_world_barriers. C reads anchors, headings and fire counters,
filters captures and the horizon, and fills its own scene storage. Optional source
slots retain Python contact identities without exporting gameplay fields. Custom
callers can still provide compact seeds. The active replay matched both reference
paths in opposite-order repeats; 179 native and 67 planner tests passed. This finishes
the native barrier forecast path, while overall scene/planner/controller migration
remains outstanding.
ABI 52: deterministic wall-shot timing is decoded in C
Level-3/4 next-shot descriptors now read native captured status and fire counters. The existing source selector identifies eligible shooters once; empty scenes issue no descriptor call. Coverage includes recognized shots beyond the requested horizon. Python descriptors remain a reference/custom path. All supported phases and both orientations match, and both affected replay windows matched in opposite run orders. No end-to-end speedup is claimed from descriptor migration alone; output-buffer copy removal is a separate step. Overall scene and planner/controller assembly remain Python.
Scheduled paths write directly into the shared point buffer
Native generation now fills the shared pool's final destination, and body assembly borrows the registered path without a temporary-array copy. Reference storage remains selectable; cached paths are retained for reuse across candidates. 183 native and 67 planner tests pass, including copy prohibition and owner lifetime after pool growth. Affected Level-3/4 replay windows matched; removed copying averages 2,736 / 1,988 bytes per observation. Timing differences are under 1% and do not establish a reliable speedup. Higher-level scene and planner/controller orchestration still remain Python. All five standard replay windows matched; unaffected levels were checked with one repeat for correctness only.
Native controller migration: search ownership (ABI 53)
ABI 55 completes C wall/escape queries, including the missing-sprite rectangle mode. All active observations in the six standard validation windows now run the C planning loop. The Python reference sampler was corrected to translate its endpoint hull at each intermediate sample. Raster/footprint query owners remain stable for borrowed scenes and are replaced after observed destruction; checkpoints do not share mutable escape caches. Whole scene preparation, mission selection and lifecycle still require Python; native-loop coverage does not mean the full controller is self-sufficient.
ABI 54 adds xenon_autopilot_planner.c, which runs search and evaluation together.
Only the selected trajectory crosses into Python; trial caches and outcomes stay in
C. Lazy cash/attack selection and first combat preparation remain observation-level
adapter events. Missing native scene/terrain inputs and occupied-start escape retain
an explicit compatibility bridge. Full scene, mission and controller ownership are
still required before transport-free C driving is complete.
The bounded maneuver search now lives in src/autopilot/xenon_autopilot_search.c.
Its request/feedback protocol owns proposal order, ranking and repair selection;
the Python reference remains in ManeuverPlanner.choose_reference. Nearby pickup
and attack selection stay lazy. native_search.py still connects each requested
trial to the existing scene/evaluator adapter. This bridge is temporary: the next
boundary is a native search-to-workspace loop, followed by complete C scene and
mission ownership. The controller is not yet callable without Python preparation.
Native optional proposals (ABI 56)
The native planner now handles nearby-pickup and optional attack events internally.
XapPlannerTactics borrows the current world plus the scene's existing ordered
pickup and supported-target indices. Pickup exclusions come from the candidate
scene; combat and tactics reuse the same target selection. Python still supplies
mission eligibility until high-level mission policy is migrated.
Attack proposals read the path or linear motion already prepared for collision checks. They do not request another policy forecast, fit velocity again, or sort all targets: C keeps the best two stations with identity as a stable tie-breaker. Targets represented only by conservative swept slices are omitted from optional attack proposals because a swept rectangle is not an exact firing anchor. Required boss/gate missions retain their specialized tactics. Candidate evaluation still checks safety and predicted destruction before accepting a proposed attack.
Validation: 195 native tests and 67 planner tests pass. All six standard replay
windows matched the previous proposal bridge (native-tactics-level1 through
level5, plus levelbarriers, one comparison repeat). These are correctness
checks, not evidence of a meaningful speedup. c-proposal-bridge-old retains the
Python selection for ablation. The remaining lazy combat-geometry event and
Python scene/mission/controller assembly still prevent a standalone C autopilot.
Shared native prediction owner (ABI 57)
XapPredictions now owns the observation's shared point pool, path slots, and
recursive dispatch for captured script roots, ordinary delayed followers,
same-frame follow/attachment links, coupled composites and shell oscillators.
The Python shared-scene adapter borrows these buffers; body/combat preparation
still addresses the same point indices. A tail request resolves and prepares its
root in C, and later member requests reuse that root. Script roots are prepared
on demand instead of forecasting every captured script at the first request.
Completed point views remain valid across pool growth. C retains old allocations until the observation owner is released; unused reservation tails can be rewound but completed native paths cannot be overwritten. Native dispatch rejects changed observation identities, changed camera conventions and cyclic links. The world must outlive the prediction owner and remain unchanged while queried.
The existing Python-owned pool and dispatch remain available with
c-prediction-owner-old. Generic/specialized roots still return unsupported and
use the existing bridge. Articulated eye/boss assembly and the independent shell
consumer have not yet been unified with this dispatcher. Consequently this is
shared ownership and recursive dispatch migration, not full C scene preparation.
Validation: 197 native tests, 67 planner tests, and all six replay windows match
(native-predictions-verified-level1 through level5, plus levelbarriers, one
repeat). Unit coverage includes complete recursive path comparison against the
existing C kernels, repeated root reuse, cycle rejection, protected reservation
rewinds and old borrowed views after growth/longer forecasts. The short timings
do not establish a substantial speedup; this stage removes a C runtime dependency.
Independent roots and shared eye/boss paths (ABI 58)
The shared C dispatcher now owns ordinary fitted-velocity roots, articulated eye assembly and delayed eye/Level-4 head batches. Linked/composite requests can resolve an independently moving root entirely in C. Fits occur only when such a root is actually requested. The independent directional-projectile root remains unsupported here until its exact direction/scale capture is retained in C.
Eye and boss consumers now borrow the same pool and path slots used by recursive links. The existing kernels are reused; this does not introduce a second script interpreter or a second eye model. Python still exports requested eye dictionaries for policy consumers. They will become optional observation views after mission policy migration. General scene classification, specialized collision envelopes, combat-only blockers, and high-level controller/mission assembly remain Python migration dependencies.
Validation: 198 native tests pass, including independent-root history comparison
and existing articulated/boss differential suites. All six replay windows matched
(native-root-dispatch-level1 through level5, plus levelbarriers, one repeat).
Timings were similar; no substantial speedup is claimed for this ownership step.
Captured directional roots (ABI 59)
The native object decoder retains validated signed direction-table index and speed scale. Shared root dispatch uses the exact 16-direction game table when these fields are present; malformed or incomplete captures retain the ordinary history fallback. A known directional projectile never needs velocity fitting. Python world updates now consume the common decoder result instead of parsing these words again. The Python decoder remains the reference implementation.
Validation: 199 native, 67 planner, 575 forced-native policy and 6 decoder tests
pass. Direction tests cover all 16 headings at positive/negative speed with
misleading history and prohibit the redundant Python decoder call. All six
replay windows match (native-direction-roots-level1 through level5, plus
levelbarriers, one repeat). No substantial speedup is claimed. Remaining
specialized collision envelopes and scene/mission assembly still require migration.
Native animated collision envelopes (ABI 60)
XapEnvelopes prepares complete collision-sweep batches from the captured world.
Level-4 laser jaws and diagonal sweepers live in xenon_autopilot_level4_envelopes.c;
tank growth/fall lives in xenon_autopilot_level5_envelopes.c. The world now retains
the observed update handler alongside status so these dispatchers need no Python
classification or raw-byte adapter. Generated bounds stay C-owned and enter the
existing native slice packer through borrowed pointers.
Each model advances once through the horizon. Diagonal sweepers request the shared generic root forecast only if the horizon reaches the reset boundary; laser and tank models need no fitted forecast. Post-reset sweeper behavior and post-trigger jaw RNG approximations retain the established reference rules. Candidate camera correction remains with the existing source metadata.
Validation: 202 native tests and 6 decoder tests pass. Targeted tests compare
80-frame sweeps across orientations, phase/accumulator states, reset boundaries,
tank extent and vertical speed, and prohibit Python hull prediction in the
production slice adapter. Six replay windows match (native-animated-envelopes-
level1 through level5, plus levelbarriers, one repeat). The tank window uses
the new path; standard Level-4 wall windows contain no admitted animated source,
so the targeted differential tests provide jaw/sweeper coverage. No substantial
whole-window timing gain is claimed. c-animated-envelopes-old retains the old
shape preparation. Level-3 animated/reflecting/expanding shapes, safety envelopes,
combat-only blockers and full mission/controller assembly remain to migrate.
Level-3 collision envelopes (ABI 61)
C now prepares animated attachment/projectile collision headers, exact reflecting
projectile fractions and the conservative expanding-formation hull in a dedicated
Level-3 module. Sprite headers are generated by generate_native_envelope_data.py
from the extracted Python asset tables. Formation words are decoded once by the
Level-3 decoder, rather than reparsed during each world update. Known overlays
now restrict formation decoding to Level 3; unknown-level decoding remains supported.
Python retains the reference implementations and optional observation views.
Validation: 206 native, 67 planner, 575 forced-native policy and 6 decoder tests
pass. Differential tests cover all animation frames/countdowns, reflection turn
boundaries and signed fractions, and controller/follower formation states. All
six standard replay windows match (native-level3-envelopes-*, one repeat).
The short diagonal-sweeper recording also matches. These runs do not establish a
substantial speedup. A separate older composite recording cannot complete even
with the old envelope backend: the Python mission controller selects the minimum
of an empty tactic-constrained action set. This is a remaining controller defect,
not evidence of envelope parity for that recording.
Remaining driving dependencies include special safety envelopes, pending synthetic sources, lazy combat scene assembly, mission/controller policy and transport. The autopilot is not yet self-sufficient in C.
Lazy world-owned combat assembly (ABI 62)
XapWorldCombatInput supplies canonical source indices and flags, the shared
prediction owner, target selection and damage bonus. The planner now requests
health, player bullets and combat geometry inside C only when a trial needs
combat. The workspace owns frame ranges and endpoint buffers; unchanged body
paths and linear descriptors remain borrowed from the same scene. Tank growth
and expanding fronts reuse their level-specific envelope functions. Ordinary
sources use shared root paths, and offscreen/unscored blockers retain the
conservative union with their observed hull.
Python no longer constructs CombatScene or primes per-source prediction for this path. Pending synthetic sources and legacy ablation configurations retain the compatibility callback. The optional workspace status API lets Python tests and inspection reuse C-prepared health/results without a second initialization. No Hatari integration was added; the API remains callable from Python.
Validation: 211 native, 67 planner and 575 forced-native policy tests pass.
Targeted tests compare 80-frame endpoints and blocker unions, exercise Python
inspection after C preparation, and prohibit the Python combat callback during
a native planner session. All six replay windows match in both execution orders
(native-world-combat-isolated-*). Levels 1/2 reduce mean planner calls from
1.16/1.148 to 1 per active observation. Timings are largely unchanged; the tank
window improves about 6%, not a substantial overall speedup.
Earlier native-world-combat-level5 and native-world-combat-verified-* timings
are INVALID: the first comparison variant unintentionally disabled other native
optimizations. Only the corrected isolated reports support this comparison.
C still needs complete scene/source assembly, pending/safety models, mission/controller policy and the driver boundary before standalone operation.
Composite replay arbitration repair
The older composite recording exposed an empty preferred action set when its
latched staging gap became unreachable. Survival arbitration now falls back to
physically legal actions (or the complete action set if all are blocked) instead
of calling min() on an empty set. This does not relax collision scoring.
Validation: the new targeted regression and all 575 policy tests pass. The
203-frame composite recording now completes and both combat variants match
(native-composite-empty-tactic-fixed, frames 40321-40523).
Timed emitter safety zones (ABI 63)
The Level-2 decoder now captures validated emitter age once. C stores missing age as -1 in a typed field (valid ages are 0..20); Python exports it only when present. The world update no longer invokes a second raw-byte age parser. Known overlays restrict this handler to Level 2; captures without level metadata remain supported.
The level-specific envelope module reserves the launch area from the predicted
spawn frame through its reaction window. It does not fit or invent an enemy
trajectory. The existing C envelope owner and slice packer consume these bounds;
include_safety makes the policy zone explicit and the old adapter remains an
ablation. Replay counters distinguish safety from animated-envelope sources.
The obsolete WORM_HOLE enum branch is not emitted by current classification;
current wormhole objects use Level-2 boss-eye classification. Its legacy Python
branch was not duplicated in C.
Validation: 212 native, 67 planner, 575 forced-native policy and 6 decoder tests
pass. Targeted tests cover every launch-window boundary, invalid ages and short
captures while prohibiting duplicate Python age parsing and safety prediction.
The Level-2 homing recording matches (native-emitter-envelopes-level2, one
repeat). This is a dependency-removal step; no substantial speedup is claimed.
Full scene assembly and mission/controller/driver migration are still required.
Canonical body-scene assembly (ABI 64)
xap_world_bodies_create assembles canonical source selections into a complete
C-owned XapManeuverScene: analytic linear bodies, shared root paths, animated
or timed safety slices and frame ranges. Constant-velocity sources stay analytic;
they do not allocate horizon-sized point arrays. The scene can be passed directly
to C candidate preparation. Python's current adapter borrows descriptors and
translates source identities; frame labels are lazy instead of replicated lists.
The factory deliberately retains the compatibility assembler for scheduled-shot sources, synthetic spawns, collapsed chains and destruction transitions. The last case exposed a still-Python world rule: an enemy installing its destruction handler remains hazardous for one observation. Treating that as ordinary motion extended its body through the horizon. The explicit guard restores the established rule; its native world-state migration is a follow-up dependency.
Validation: 215 native and 67 planner tests pass. Targeted tests forbid Python hull
and anchor preparation, check analytic collision queries against Python, verify
C safety frame ranges/lazy labels, and protect destruction-transition routing.
All six replay windows match: native-world-bodies-destruction-guard-level1 and
native-world-bodies-guarded-* for the remaining levels/barriers (one repeat).
The earlier native-world-bodies-level1 report contains the now-fixed destruction
regression and must not be treated as the final result. Timings are similar; this
is an ownership/dependency migration rather than a substantial measured speedup.
Native driving still needs the remaining source assemblers, complete world postclassification, mission/controller policy and the driver boundary.
Native destruction transitions and lazy track checkpoints (ABI 65)
C tracks retain current and previous semantic kinds. Classification preserves hostile contact for exactly the first destruction observation, including after checkpoint restore or a Python-to-C backend switch. Repeated decoding uses the stable previous kind. The normal native world path skips Python's transition postprocessing; the reference/ablation path remains available.
The canonical C body scene now owns the final stationary contact hull and leaves empty collision slots after that frame. It never extrapolates old scripted velocity through the destroy handler. This removes the destruction-transition scene fallback. An intermediate replay found that extrapolation mistake; the stationary implementation is covered by a moving-history regression.
NativeWorld no longer copies every track and its twelve history samples into a checkpoint buffer on each update. Ordinary compatibility headers are read directly from C storage. Immutable snapshots are copied lazily when requested, after classification, and remain safe across replay forks and restores.
Validation: 216 native, 67 planner, 575 forced-native policy and 7 decoder tests
pass. Tests prohibit eager snapshots, restore semantic history, check every
hostile-transition kind, and verify stationary one-frame collision geometry.
The Level-1 comparison matches (native-world-destruction-stationary-level1,
frames 2367-3367). The earlier native-world-destruction-level1 report records the
fixed velocity-extrapolation error. No substantial timing gain is claimed.
Scheduled-shot/collapsed/synthetic scene assembly and mission/controller/driver
migration still prevent fully independent C driving.
Shared scene wall shots (ABI 66)
xap_world_bodies_create owns deterministic small-wall and directional-wall shot
assembly. Shot paths borrow the shared point pool, carry their spawn frame, and
have no target-death index: a bullet already emitted remains hazardous when its
shooter dies. The Python scene adapter disables duplicate scheduled-shot assembly
and resolves shot identities only through its compatibility metadata.
Composite scenes
The envelope owner recognizes initially collapsed composite members from the C
model's emitter and ordinal. It calls xap_world_prepare_collapsed_bounds against
one shared emitter forecast. The body factory also accepts active members through
the existing shared chain predictor. Python linked/envelope adapters remain for
reference tests and scenes that still contain noncanonical sources.
Remaining Level 3 wall shots
xenon_autopilot_level3_shots.c prepares oscillating and large-wall shot envelopes
from captured fields. Body and anticipated-shot slices share the C scene's frame
ranges; bullet slices have target index -1 and empty rectangles before allocation.
The large gun retains a bounded set of firing states and advances each forecast
once, instead of reconstructing the state tree for each future-frame query.
Canonical maneuver-dependent shots (ABI 67)
The world candidate factory prepares periodic/composite spawn origins and fire metadata directly from C fields and the shared prediction owner. C callers supply source indices and normal scene inputs; Python's bridge retains source labels for optional result inspection. Unsupported roots still use the explicit reference source constructor. Shot paths and bullet evaluation remain shared existing C code.
Native waypoint connection (ABI 68)
The C span graph now provides a complete world-coordinate connector. It retains both search scratch and final route storage; only the selected route is exposed to Python mission/replay consumers. The actual ship anchor and any nearby legal escape waypoint are preserved. Row limits and occupied-corner checks match the reference connector. A C mission controller can call this operation directly.
Homing launcher origin ownership (ABI 69)
Canonical workspace initialization now receives the shared prediction owner and resolves each future launcher origin internally. Python supplies source identity, spawn frame and damage-target mapping, but no predicted geometry. Explicit inspection and the retained compatibility initializer remain available.
Complete canonical scene ownership (ABI 70)
xap_world_scene_create accepts selected canonical sources and borrowed world,
prediction and wall owners. It assembles bodies, shell releases, future shots,
barriers and homing state and owns both candidate scene and workspace. Combat
metadata belongs to this owner, while expensive combat preparation stays lazy.
Optional accessors expose borrowed candidate, workspace and combat handles.
The Python adapter resolves result identities only when requested and retains
the previous assembler for reference or unsupported synthetic inputs.
Canonical maneuver preparation borrows the C body header without constructing
Python geometry arrays. WorldBodyScene geometry views, contact labels and
compatibility query scratch are lazy. Tests and replay consumers can still inspect
the same buffers explicitly, while ordinary scene handoff performs none of that
inspection work. The C owner and shared prediction pool retain their lifetimes.
Shared source membership (ABI 71)
The C selection owner accepts the current scored world indices and provides one read-only view of source flags, target slots, independent bodies, damage targets, unmodeled bullet blockers and pickups. Targets use stable identity order; the remaining arrays preserve source/capture order. Both body and complete-scene preparation borrow these arrays. Complete-scene creation accepts optional source flags from that same selection, avoiding a second classification pass.
Selection buffers remain valid until their owner is freed and must only be used with the original observation. Python retains identity views for remaining policy, tests and replay consumers; blocker-object resolution is lazy. Scoring-hazard selection, equipment policy, synthetic inputs and the game driver remain Python dependencies outside this API. No Hatari integration was added.
Player and camera initialization (ABI 72)
XapPlayerTelemetry carries captured scalar state and explicit presence flags.
xap_prepare_player_state combines it with the canonical player to produce
XapPlayerState: ship/camera origin, speed, queue state, initial collision union
and steering-dependent hulls. Live sprite offsets take priority over historical
global bounds for the next-update hull. The observed global bounds, when present,
retain their captured memory-scroll origin; these two collision phases are not
interchanged.
The output owns its values and remains usable without retaining the world. Native maneuver preparation consumes it directly; Python camera/rectangle objects are compatibility views. Raw telemetry collection and high-level mission selection remain external to this API, and Python reference initialization is retained for comparison and unsupported observations.
Player-fire intent (ABI 73)
The native driver can call xap_prepare_fire_schedule with pulse period,
fire-on duration, enabled state, normalized phase, command horizon and spawn
delay. The output borrows caller-provided frame storage and includes the current
joystick fire bit. The caller may retain and reuse the output for an unchanged
phase/configuration; the Python driver caches a bounded set of these immutable
schedules and shares them with replay and native scene preparation.
Shot times still express intended player fire. They do not override cooldown, weapon state or enemy-shot prediction. Explicit diagnostic/custom time lists use the compatibility assembler. Driver counters and existing recording fire metadata remain authoritative for phase resets and reconstruction.
Complete decision ownership (ABI 74)
The production native boundary is now xap_decision_run(session, input, output,
status). Its input combines external world-scene references, XapPlayerState,
a semantic XapDecisionMission, planning limits, captured player position,
observation frame and a retention-reset flag. C constructs the scene, planner
requests, transition tree, tactics/combat inputs and complete evaluated winner.
No intermediate scene/workspace handle has to travel through Python.
XapDecisionSession owns the bounded retained action sequence and mission/frame
eligibility. A new mission identity, reset, frame gap, refresh deadline or unsafe
winner invalidates it. The retained suffix is always checked against the new
observation. The host currently translates its mission key into an identity
counter; a future native mission controller can provide that counter directly.
A returned XapDecision owns its scene, planner and prefix budget. Selected
frames and masks are borrowed until xap_decision_free; later decisions and
session destruction do not invalidate them. Underlying world, prediction, reused
body and wall owners must still remain valid for any scene access. The session
itself keeps no observation geometry or world pointers.
xap_decision_view and xap_decision_scene provide optional inspection. Python
uses the result returned by the execution call and resolves scene handles lazily
for tests/replay or dependent-contact labels. Clear decisions do not execute the
scene-inspection accessors. The old component pipeline remains available for
comparison and unsupported scenes; the normal native path no longer builds
Python motion certificates or reimports retained action suffixes.
This completes scene-to-decision orchestration, not raw observation capture, equipment/pickup policy, synthetic pending sources, high-level level tactics, or the game/shop/transport driver. Python remains a supported host and test harness. The C API is not yet integrated into Hatari's build.
Captured frame to canonical world (ABI 75)
xap_world_observe now owns the complete observation-normalization stage. It
accepts captured object headers/anchors and raw byte spans, draw events,
destroyed identities, frame/namespace/run identifiers, camera metadata and an
optional prior-player collision correction. It performs lifecycle filtering,
identity-and-address draw matching, render-bound merging, visible-wall extraction,
coordinate-policy selection, collision-word decoding, canonical track updates,
field classification and enabled prediction-model preparation in one C call.
The draw lookup uses both identity and object address, so a reallocated slot cannot inherit a stale occupant's draw. Multiple draws for the same live object share one merged rectangle. Wall draws keep their original order and source-row metadata. Updates and draw geometry keep their distinct camera phases. Destroyed identities are sorted once and searched during live-population selection.
Observation scratch belongs to XapWorld and is reused across frames. The
returned XapObservationView borrows canonical tracks/fields, original record
indices, render geometry and visible walls; views expire on the next world
update/restore/observation. C prediction preparation retains the script data it
needs, so input object/draw/raw arrays need only survive the call. C does not
require the output views to be repacked by the host to finish normalization.
Python's native_observation.py is the single captured-event adapter. It packs
one object-header batch and one raw payload, calls C, then exports the fields
still required by existing Python tactics and replay. The object decoder consumes
the already-prepared field array instead of rebuilding metadata or calling C a
second time. Geometry compatibility export reads borrowed rows in bulk instead
of constructing temporary ctypes rectangle/track wrappers for each object.
The previous lifecycle/draw/tracking preparation remains isolated in the
reference branch of WorldState.update, selectable with c-observation-old.
Standalone tracker/decoder APIs remain available for tests and ablations. Tests
exercise direct C input structs without calling Python world-state normalization,
all named handler coordinate policies across levels, camera changes, destroyed
objects, stale/duplicate draws, namespace resets, empty populations, buffer reuse
and rejected raw spans.
This is a completed input-normalization subsystem, not a claim that the whole world/controller is C-only. Python still constructs TrackedObject compatibility views, interprets equipment telemetry, supplies synthetic pending-spawn policy, chooses high-level missions and runs the game/shop/transport driver. Those consumers, especially mission policy, must move before compatibility export can be eliminated from normal Python-hosted driving. No Hatari build integration or WASM profiling was added.
ABI 76 mission consumer
The Level 1 mission controller now consumes the C world directly, including its selected boss core and shared prediction paths. Decoded object fields retain the pickup code so C pickup policy does not reread raw object bytes or compare Python classification strings. Equipment capture and map graph preparation still enter through the host boundary. See mission migration for the split between common navigation, level tactics and maneuver evaluation.