tools: add SYLPH_WEAKEST target scoring; record that assert_stage != in flight

pilot.py gains SYLPH_WEAKEST=1, which scales a target's score by its remaining
hull (pos+0x154) so the pilot finishes what is already hurt instead of
re-engaging whatever is nearest.  Motivated by 00dc016: over ~8 minutes the pilot
damaged 14 of 16 e010 attackers (hulls 360..500) and killed none, because 500 HP
spread across a squadron kills nobody.

STATUS: the flag is implemented and its targeting works -- 3105 of 3105 target
samples selected e010 -- but it is UNVERIFIED in combat, because the run it was
written for was lost.

That loss is the second half of this commit.  The run printed "READY ROOM / >>>
HUD / Stage 02 OK" and I began the experiment; there was no FLIGHT: line, because
the flight check failed three times and fell through silently while the next line
read like success.  The game was frozen on a near-black screen (screen_id `other`,
mean 10.8/2.8/2.1, frozen.py max_pixel_delta=0) and the pilot's every sample from
t=0.0 to t=406.1 is byte-identical with speed 0.

assert_stage.py could not have caught it: it reads the DEFINITION table, which is
populated when the STAGE loads, independently of whether the mission is running.
Recorded in nav-guards.md with the rule -- enforce the flight gate with a non-zero
exit, and run the three-crop TIME liveness check before any experiment.
This commit is contained in:
Sylpheed RE agent
2026-08-27 03:27:35 +00:00
parent 00dc0162aa
commit a25ddabb11
2 changed files with 43 additions and 0 deletions

View File

@@ -104,3 +104,36 @@ this confirms it visually and pins the cursor's start position.)
unit-tested, but "the route is now reproducible" rests on a single success.
* Why the game window leaves the window tree during a load at all was not
investigated — only guarded against.
---
## ⚠️ `assert_stage` passing does NOT mean the run is in flight
Measured the hard way 2026-08-27. A run printed:
```
MENU confirmed
loaded
READYROOM
>>> HUD
Stage 02 OK
```
…and I started an experiment against it. There is no `FLIGHT:` line in that
output. The flight check ran, failed to match three times, and fell through
**silently**; the very next line said `Stage 02 OK`, which read like success.
It was not. The game was frozen on a near-black screen — `screen_id` `other`,
mean `(10.8, 2.8, 2.1)`, `frozen.py max_pixel_delta=0` — and the whole 460 s
experiment ran against a static world. The pilot's own log is the clearest
evidence: **every sample from t=0.0 to t=406.1 is byte-identical**, speed `0`,
the ship never even started moving.
**Why the stage assertion did not catch it:** `assert_stage.py` reads the
DEFINITION table, and definitions are loaded when the *stage* loads — before,
and independently of, the mission actually starting. It answers "is the right
stage loaded", never "is the mission running".
**Rule:** the flight gate must be *enforced*, not attempted — exit non-zero when
`screen_id` never reads `flight`, and take the three-crop TIME liveness check
before any experiment. Both were skipped here, and the run was lost.

View File

@@ -115,6 +115,12 @@ KILL_TURRETS = os.environ.get("SYLPH_KILL_TURRETS") == "1"
HUNT = os.environ.get("SYLPH_HUNT") == "1"
PREFER = os.environ.get("SYLPH_PREFER", "")
# SYLPH_WEAKEST=1 scores a target by REMAINING HULL as well as range, so the
# pilot finishes what is already hurt instead of re-engaging whatever is
# nearest. Measured 2026-08-27 (ob-counts-marked-attackers.md): over ~8 minutes
# the pilot damaged 14 of 16 e010 attackers -- hulls 360..500 -- and killed
# NONE, because each carries 500 HP and the fire was spread across the squadron.
WEAKEST = os.environ.get("SYLPH_WEAKEST", "") not in ("", "0")
class Pilot:
@@ -399,6 +405,10 @@ class Pilot:
# pointed at the thing the mission actually asks for.
if PREFER:
score *= 0.05 if PREFER in nm else 4.0
if WEAKEST:
h = self.f32(off + HULL_OFF)
if h and h > 0.0:
score *= max(h, 1.0) / 500.0
if score < bestscore:
best, bestscore = (off, nm, lead, lead - me_p, d, r), score
return best