Files
Sylpheed/docs/re/pilot-never-fires.md
Sylpheed RE agent b2c85e60bf docs+tools: why the pilot never fires - the target is behind it, 100% of the time
fire=1 appears in 0 of 13521 samples. The gate that closes is measured rather
than guessed: over the 3004 samples that had a target, |aim yaw| is EXACTLY 90.0
degrees every time, which is the `if ez < 0` branch in sticks() - the target is
astern - with pitch near 180 and a range that grows 22km -> 49km and plateaus.
The craft flies away from what it is chasing for fifteen minutes and the turn
never completes.

What is NOT established is why, and the attempt is withdrawn rather than kept:
aim_probe.py reported the forward vector pinned at [-1,0,0] with 0.00 deg/s under
neutral, full-left and full-right stick, which looks like a stale attitude
matrix - but the guest had FROZEN partway through the probe, confirmed after the
fact by frozen.py and by the player position being identical across 3 s. A dead
world holds every matrix still. The probe is committed because it is the right
experiment; its numbers are not evidence.

One confusion resolved: today's entities2.py "0 moving triples" bind failures are
the freeze, not a tool defect - moving() types entities by position CHANGING, so
a frozen world yields nothing by construction.

live_delta.py gains a per-1MB-region summary; a flat list is useless at 236000
hits.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PMRJjbxLqZtsb5Vb7KunPE
2026-08-23 21:27:27 +00:00

3.3 KiB

pilot.py never pulls the trigger — the proximal cause, measured

Status: the gate that blocks it is identified and measured; 🔴 the root cause is not, and one attempt to find it was invalidated by a freeze.

The observation

Over a 900 s Stage 02 run, fire=1 appears in 0 of 13 521 logged samples. The loop flies, chases, evades and retires; the guns are never fired. Every "kill something and watch X" experiment in this corpus has therefore been leaning entirely on the wingmen.

Which gate closes

pilot.py's trigger needs all of: mode ENGAGE/DEFEND, a target, the aim error inside the firing cone, range < FIRE_RANGE (SHELL_MAX_RANGE = 4 000), and a small avoidance push. Over the 3 004 samples that had a target:

quantity median min inside its gate
|aim yaw| 90.0° 90.0° 0 of 3 004 below 25°
|aim pitch| 179.2° 166.4° 0 of 3 004 below 25°
range 49 634 45 078 0 below 4 000, 0 below 10 000

|aim yaw| being exactly 90.0 in every sample is not a coincidence, it is a branch: sticks() contains

if ez < 0:                        # target behind: commit to a full turn
    yaw = math.copysign(math.pi / 2, ex if ex else 1.0)

So the committed target was behind the ship in 100 % of samples, with a pitch error near 180° — directly astern — and the range to it grew monotonically:

t=  0 s   22 265        t= 231 s   33 632
t=510 s   49 475        t= 884 s   48 200   (plateau)

The craft flies away from the thing it is chasing for fifteen minutes and the turn never completes. Nothing else needs to be wrong for the trigger to stay cold.

What is NOT established

Three explanations fit that shape and this pass did not separate them:

  1. the attitude matrix the pilot reads is stale, so the ship turns and the controller cannot see it;
  2. the matrix is live but fwd_row/fwd_sign name the wrong axis;
  3. both are fine and the yaw stick sign is inverted, so the loop turns away from the error it is nulling.

🔴 An attempt to separate them was invalidated and is withdrawn. aim_probe.py watched the forward vector under neutral, full-left and full-right stick and reported it pinned at [-1, 0, 0] with |turn| = 0.00 °/s in every phase — which looks like explanation (1). It is not evidence: the guest had frozen partway through, confirmed immediately afterwards by frozen.py (max pixel delta 0) and by the player's position being byte-identical across 3 s. A dead world holds every matrix still. The probe is committed because it is the right experiment; its result is not.

One confusion resolved on the way

Today's repeated entities2.py self failures — # 0 moving triples, "player entity not found" — are the freeze, not a tool defect. moving() types entities by their position changing between two samples, so a frozen world yields nothing by construction. Runs where the bind failed were runs that had already stopped.

Next step

Re-run aim_probe.py on a run confirmed to be animating at the end of the probe as well as the start — the tool should check frozen.py itself and discard the phase otherwise. If the matrix does move under stick, the question becomes the sign; if it does not, the binding is what to chase.