This repository has been archived on 2026-09-16. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
Syplheed-Reborn/docs/re/pilot-never-fires.md
Sylpheed RE agent 0ddeffa9a6 tools+docs: the flight pad was writing into a dead file - every autopilot input went nowhere
flight_probe.Pad opened /tmp/sylph-vgamepad.fifo, "the vgamepad server's FIFO".
That server was removed when the uinput pad was replaced by Canary's --hid=file
driver, because a uinput device is not namespaced and scripted presses leaked to
the host's desktop. The FIFO is now an ordinary 91-byte file nothing reads, and
/tmp/xenia_pad.txt - the file the emulator polls - was 0 bytes while the
"autopilot" was flying. So every axis, trigger and button from pilot.py,
autopilot3.py, aim_probe.py and flight_probe.py went nowhere, silently.

The corpus already carried this trap for the SHELL scripts
(canary-scripted-input-traps.md, "every call here failed silently"). This class
was missed, and every flight tool imports it.

Verified against the oracle rather than by inspection, on runs confirmed
animating at both ends of every phase: before, full stick produced 0.00 degrees
of heading change over 4 s while the ship travelled 350-735 units, and the
attitude matrix at pos-0x70 was byte-identical; after, LX=-1 turns 12.72 degrees
and LX=+1 swings the flight direction from [1,0,0] to [0.13,-0.14,-0.98], with
the matrix moving 0.44/0.31 under stick and 0.0000 at neutral.

That also REFUTES the "stale attitude matrix" suspicion from the earlier pass -
pos-0x70 is live and tracks the ship; it only looked dead because nothing was
turning the ship.

Still open and said plainly: with the pad fixed a 150 s pilot run still fires 0
times, with |aim yaw| still exactly 90.0 and the target 36-43 km away. Steering
works now, so what is left is the stick SIGN against the pilot's error convention
or a target selection that commits to something too far to close. Both are
finally testable.

findrot_global/findself/findspeed/selfstate still write to the dead FIFO and now
say so in place; they are not repaired because none has been re-run since.

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

6.8 KiB
Raw Blame History

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

Status: ROOT CAUSE FOUND AND FIXED (2026-08-23, later the same day) — the flight tools' pad wrote to a FIFO nothing has read since the uinput pad was removed, so every stick, trigger and button they sent went nowhere. The gate measured below is real; it was open because the craft was never being flown at all. 🟡 The pilot still does not converge on a target in 150 s with the input working — see the last section.

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.


ROOT CAUSE — the flight tools' pad was writing into a dead file

flight_probe.Pad opened /tmp/sylph-vgamepad.fifo and wrote axis LX 0.850-style lines to it, described in its own docstring as "the vgamepad server's FIFO". That server does not exist any anymore: the uinput pad was removed because a uinput device is not namespaced and every scripted press leaked to the host's desktop, and it was replaced by Canary's --hid=file driver. The FIFO is now an ordinary 91-byte file that nothing reads.

Meanwhile /tmp/xenia_pad.txt — the file the emulator actually polls — was 0 bytes while the "autopilot" was supposedly flying.

So every axis, trigger and button from pilot.py, autopilot3.py, aim_probe.py and flight_probe.py itself went into a dead file, silently, and the craft flew on its own for the whole of every run. That is why the target sat astern at 90.0° forever and the range grew to 49 km: the ship was never being turned.

The corpus already carried this trap — for the other half of the toolkit. canary-scripted-input-traps.md records "pad.py, NOT vgamepad: that command no longer exists … and every call here failed silently, so the whole scripted route pressed nothing". The shell scripts were fixed then. This class was not, and every flight tool imports it.

Measured, before and after

Same probe, on runs confirmed animating at both ends of every phase (aim_probe.py brackets each phase with frozen.py now, after a freeze voided the first attempt):

heading change under full stick attitude matrix pos-0x70
before 0.00° in every phase, over 4 s, while travelling 350735 units d 0.0000 — unchanged
after 12.72° on LX=-1; LX=+1 swings the flight direction from [1,0,0] to [0.13,-0.14,-0.98] d 0.4438 / d 0.3108 under stick, 0.0000 at neutral

🔴 So the "stale attitude matrix" reading is refuted, and with it the earlier suspicion that the binding was wrong: pos-0x70 is live and tracks the ship. It only looked dead because nothing was turning the ship.

🟡 Still open — the pilot does not converge

With the pad fixed, a 150 s pilot.py run still fired 0 times: over its 86 targeted samples |aim yaw| is still exactly 90.0° and the range is 3643 km. Steering now works, so what remains is one of:

  • the sign of the yaw stick against the pilot's own error convention — a controller that turns away from the error would hold ez < 0 forever, which is exactly the observed signature;
  • target selection that keeps committing to something 40 km away, which at ~300 units/s cannot be closed inside the window.

Both are now testable, because the input path finally works. The measurement to make is the direct one: command the pilot's own sx for a known error and see whether |yaw| falls or rises.

Not fixed, and flagged in place

findrot_global.py, findself.py, findspeed.py and selfstate.py still write to the dead FIFO. Each now carries a comment saying so and pointing here. They are not repaired because none of them has been re-run since, and quietly changing the input plumbing under a tool whose results are unverified would only manufacture new unverified results.