Autopilot · write-up
Remove unnecessary velocity fitting
Changes
Known motion now feeds closing-speed estimates and targeting through the selected trajectory predictor instead of the standalone velocity fitter. The controller was missing the standalone predictor's exact shell-oscillator branch; it now handles phase and reversals there as well. The maneuver adapter samples existing shell, formation and script paths, preserving world-coordinate/camera handling.
Closing-speed calculations use the next predicted displacement as their velocity estimate. Aim positions continue to use the trajectory at the estimated flight time. This is not a new exact interception solver; estimating flight time itself remains approximate. Branches that return a fixed firing station do not request an unused velocity first.
Unknown movement retains least-squares fitting, computed lazily and cached against the exact immutable history tuple. Changed histories invalidate the derived value; dataclass replacement does not carry the private runtime cache. Direction-table projectiles retain their exact velocity. The experimental eager C fitter remains off by default: this change does not compute fits for every observed object.
Replay/live display arrows sample predicted displacement at their displayed horizon instead of extrapolating a fitted line. Selection details use the predicted next-frame displacement. This uses the active controller's existing paths where available. Observability remains Python and is outside the replay timing scope.
Timing and work counts
Two opposite-order repeats, CPU 4, Level 1 frames 2367–10059:
| Metric | Previous fitting path | Exact consumers + lazy fallback |
|---|---|---|
| Mean observation milliseconds | 3.20223 | 3.22978 |
| Estimator calls (profile) | 72,157 | 26,558 |
| Actual least-squares fits (profile) | 65,828 | 10,786 |
| Estimator cumulative seconds (profile) | 0.6784 | 0.1976 |
Actual fits are counted from the six sum calls per complete reference fit.
This removes 83.6% of fits and 70.9% of profiled estimator time. Overall timing is
0.86% higher, but the new variant's repeats differ by about 3.3%; there is no
proved overall speedup or slowdown from these timings. Exact path sampling and
other pipeline work offset part of the local saving. Do not equate fewer fits
with a measured end-to-end win.
compare_kernels.py --variants c-fitting-old,c retains the previous fitting and
linear dispatch for comparison. Deferring unused fixed-station calculations is
common to both variants, so the comparison excludes that small additional saving.
Artifacts under xenon_tools/run_logs/:
- exact-motion-timing-0908/comparison.json: repeated timings.
- exact-motion-profile-0908/comparison.json: separate profile and .pstats.
- exact-motion-level{2,3,4,5}-0908/comparison.json: cross-level decisions.
Behavior validation
The new trajectory consumers intentionally change some decisions:
- Level 1: 13 verdict frames, 2400 and 2402–2413, in combat_hold.
- Level 2: 15 verdict frames, 32895–32898, 33427, 33429, 33434–33436,
33438–33442 and 33519, in scroll_spawn_pacing.
- Level 3: no changed verdicts.
- Level 4: frame 63609, still swarm_sweep with the same action and predicted
contact at frame 44; another compared result changed.
- Level 5: no changed verdicts.
Level 1/2 changed verdicts report no predicted contact in either variant. This is an inspection of recorded observations, not proof of shield/life outcomes in a fresh closed-loop run. The comparison tool exits with a decision-difference error for the affected recordings; those reports were inspected, not counted as identical replays.
Targeted tests verify shell reversal without a fitter call, prepared-script path
reuse, and lazy fallback/history invalidation. The final full suite passes 933 tests; the log is
exact-motion-ui-tests-0908.txt. No C source was changed in this waste-removal
step; the earlier optional ABI-20 fitter remains in the working tree.
Fresh live validation (2026-09-08)
Run exact-motion-live-0908-02.validation used the native kernel, spans navigation,
normal speed and no cheats, starting from assets/xenonplay.sav. The intended
stop was Level 2 entry. Progression failed: the user observed shots passing
to the left of a shell, and the run was stopped at trace frame 7734.
- No lives lost; shield 39 to 35 from one directional-projectile hit at frame
5410 (
swarm_escape). This corresponds to baseline frame 7246 after the initial frame-number offset of 1836. - First shop: 600 cash; purchased SPEEDUP for 500 at frame 3418.
- Shell #9435 was targeted continuously over frames 5635-7734. Samples from 5800 onward show ship x=239, station x=240, aim x=244.5, unchanged shell health 2 and unchanged score 26200. At the last frame aim x=243.5 and the shell collision bounds span x=240-247. The retained maneuver oscillates vertically rather than correcting horizontal alignment.
- Inspect frames 5600-5900 for entry into the stall, or 7400-7734 for the stable failure. This is not a successful level validation or evidence that the latest change alone caused the stall; the earlier live baseline predates several changes.
The recording is exact-motion-live-0908-02.x2events inside that validation
folder, alongside paired AVIs and periodic emulator/controller checkpoints.
The console interrupt ended the Python processes before normal finalization;
PAUSE, STOP_AVI, STOP_LOG, SAVE_STATE and RELEASE_INPUT were subsequently sent
successfully through the control API. The final emulator save has no matching
final controller sidecar; use the complete periodic checkpoint at frame 7652
for a resumable controller state.
Next correction: reconcile the firing station with the actual projectile lane and target hitbox, including movement tolerance, and invalidate a retained attack when sustained firing makes no health/progression change.