The second 240 s flight ended on GAME OVER with our hull untouched at
1500/1500. Nothing shot us down; the ACROPOLIS was sunk while the
pilot pursued an attacker two kilometres away. "Nearest hostile
fighter" is the wrong objective function for this stage.
The fix is available with what is already solved: the protected ship's
hull is readable with the same anchor as ours (position+0x154, maximum
= its own definition HP), so target priority can be "closing on the
asset" and the escort can be scored live.
Also records that a frozen log tail is what mission-end looks like
from outside the emulator, not a wedge.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Three measurements, then a pilot built on them.
* Hull is position+0x154. Found by anchoring on a field the definition
already had solved (HP = 1500) rather than scanning for a value that
falls: an undamaged craft must contain its own definition's number.
Confirmed by the trace across a death -- 30/60/90 per hit, negative
at 0, GAME OVER on screen.
* RT accelerates, LT brakes, and the throttle is a persistent setting
(488 -> 1510 -> 174 units/s, measured as displacement per second of
the craft's own position, so no speed field was needed). This
overturns the earlier "RT is not the throttle", which came from
assuming the control and hunting for a field.
* Shield is probably position+0x430 (== definition MaxValue 400), not
yet confirmed live -- nothing had damaged it.
pilot.py is a state machine on damage (ENGAGE / EVADE / RETIRE) that
treats turrets as keep-out zones instead of targets. It flew Stage 02
for 300 s with the hull untouched at 1500/1500 and took the first
confirmed kill (WARPLANES 0001); every run the day before was dead
inside 35 s.
Two bugs the live run exposed and this fixes: gating the guns on the
commanded direction keeps them cold whenever avoidance is steering
(gate on the target instead), and an orbit-plus-brake rule made it
circle one attacker for 40 s outside its own firing cone.
Also: boot to in-flight is now ~100 s unattended, because
wait_flight.sh waits for the HUD's own shield bar instead of a fixed
75 s sleep that lavapipe does not honour; entities2.py picks the
attitude block by matching the measured flight path (taking the first
orthonormal block gave a bone/camera frame); and the entity-heap scan
is numpy instead of a per-word Python loop.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Three findings turned the previous dead end into a working loop, each checked
against something independent rather than assumed:
* a live entity's definition pointer sits at position + 0x130, so one heap
scan types every craft in the scene -- which is what separates ~20 real
combatants from ~30000 moving particles. The result is coherent: wingmen,
enemy turrets and attackers, friendly capital ships, one Player.
* orientation is a 3x3 at position - 0x70 stored with a 16-BYTE ROW STRIDE
(a 4x4 whose translation row is the position). The earlier search for nine
contiguous floats could not find this by construction, which is why the
first pass wrongly concluded there was no transform. Confirmed by its row 2
matching measured direction of travel at cos = +1.000.
* RB is the fire button, established by consequence: of RB/LB/A/B/X/Y/RT/LT
it is the only one that makes the nose-ammo counter in RAM fall.
Control is PD on the aiming error with the derivative from body angular
velocity, and target selection weighted by off-boresight angle -- pure
nearest-first kept picking targets 90 deg off the nose, whose bearing rate then
outran the turn rate and held the craft outside its firing cone at a steady 27
deg pitch error.
Observed: distance to target closing monotonically, yaw error driven from -8 deg
to ~0, fire=1 once inside the cone, ammo counter falling.
Not solved: survival. There is no evasion, no shield/armour awareness and no
throttle control, so it flies a straight pursuit into defended space and is
shot down; every long run has ended in GAME OVER. Mission completion needs
those, plus objective-aware target priority.
Reads the live world out of guest RAM and drives the pad from it. Working:
loop-rate memory reads, whole-RAM float scanning with numpy (1270 orthonormal
3x3 blocks in 6.2 s), entity enumeration by unit type (116 live instances in
Stage 02), pad control written straight into the vgamepad FIFO (the CLI spawns
a process per command and its tap/hold sleep inside the server, so neither is
usable in a control loop), unattended mission entry, and the Hangar loadout --
the "Recommended" control is AUTO SELECT, which at 5 % progress is a no-op
because only two weapons are developed and both are already mounted.
Not working, and the reason the craft is not yet flown: the class 0x820af030
is NOT the live entity. It has one object per spawned thing and carries the
unit-ID string, which is why it looked like the entity list, but every one of
its 384 words is constant across a 29 s in-flight capture. No transform lives
in it or one pointer hop from it. Input correlation (hard left yaw vs hard
right, looking for a turn axis that reverses) does find self-like objects at
cos = -0.99, but they cluster in what looks like a camera volume rather than
the craft, and with no definition pointer near them the trick of learning one
entity's layout and applying it to the rest has nothing to anchor on -- so the
33418 moving triples in a firefight cannot be split into enemies, friendlies
and bullets, and there is nothing to aim at.
Two dead ends are recorded so they are not repeated: RT is not the throttle
(the two-state speed scan therefore found nothing), and comparing orientation
matrices 2 s apart is outside the small-angle regime, which is what produced
"angular velocities" of 30000.
Also corrects the claim in unit-struct-runtime.md that 0x820af030 holds live
state. The definition class 0x820af844 and every value derived from it are
unaffected.
autopilot2.py (a PD controller using body angular velocity from consecutive
rotation matrices) is committed but has never had a valid config to run
against, and is marked as untested.