re: eliminate an enemy squadron — no wave follows; add a stall witness

SYLPH_KEEPOUT makes the hunt keep-out a knob instead of a hard-coded 600. At
1400 the pilot both kills and survives: hull 1500 and escorted asset 100 % for
the whole run, ENGAGE throughout, eight loss events against seven in the run
where it died at 83 s.

That let the elimination test actually run. An e007 Turret squadron was ground
18 -> 10 -> 8 -> 4 -> 2 -> 0, reaching zero at t=163 s with deployed falling 41
to 40 -- an enemy squadron destroyed outright for the first time in nine runs.

No arrival followed. ARRIVALS=0 at every sample including all those after t=163.
The hypothesis that a wave is released when a squadron is wiped out rather than
merely damaged does not survive its first test. Two further losses occurred at
t=176 and t=202, so the mission was demonstrably still live and still processing
kills; it simply produced no arrival. This refutes elimination-of-one-squadron
as the trigger. It does not refute event-gating generally -- a threshold across
several squadrons, an objective completion, or a specific squadron could all
still be the gate.

The valid window is smaller than the log suggests, about 90 s after the
elimination rather than 143 s, because the guest stalled around t=255 s. The
pilot's telemetry gives it away: the last 400 log lines contain one distinct
speed value against 236 in the first 400, with no timestamp gaps -- the process
kept logging while the game stopped advancing. Nothing in the probe output
distinguished a stall from a quiet mission, and the same ambiguity affects the
trailing flat samples of earlier runs.

wave6_probe now locates a counter advancing at frame rate, samples it each tick
and prints GUEST STALLED when it fails to advance, so future runs validate
themselves. Implemented but not yet exercised in a run.
This commit is contained in:
Sylpheed RE agent
2026-08-24 15:41:21 +00:00
parent a884c62098
commit 44e9f8dc94
5 changed files with 121 additions and 7 deletions

View File

@@ -123,8 +123,12 @@ class Pilot:
CONE_MIN = math.radians(2.0)
CONE_MAX = math.radians(25.0) # close-in the target subtends a lot; let it
# Keep-out from things that shoot back. Under SYLPH_HUNT the turrets ARE the
# targets, so standing off 2.5 km from them just guarantees no kills.
TURRET_KEEPOUT = 600.0 if HUNT else 2500.0
# targets, so standing off 2.5 km from them guarantees no kills -- but 600
# cost the pilot its life at t=83 s and froze the mission for the remaining
# 220 s of the run (mission-arrival-watch.md). The usable observation window
# is player survival, so this is a real trade and not a free knob. Tunable.
TURRET_KEEPOUT = float(os.environ.get(
"SYLPH_KEEPOUT", "1400" if HUNT else "2500"))
EVADE_QUIET = 5.0 # seconds without damage before re-engaging
RETIRE_FRAC = 0.30 # hull fraction that sends us home
HZ = 8.0

View File

@@ -19,6 +19,11 @@ _w3 = importlib.util.spec_from_file_location('w3', SD + '/wave3_probe.py')
wave3 = importlib.util.module_from_spec(_w3); _w3.loader.exec_module(wave3)
ROSTER_VT = struct.pack('>I', 0x820AF030)
# A guest that has stalled produces flat samples that read exactly like a quiet
# mission -- that happened at t~255 in the keep-out run and was only caught by
# eyeballing the pilot log afterwards. timer_probe found counters advancing at
# frame rate; sampling one of them makes every run self-validating.
TICK_LO, TICK_HI = 0xBC000000, 0xBE000000
DELTA, WIN, LINK = 0x130, 0x400, 0x08
BASELINE = 116
@@ -36,6 +41,20 @@ def scan_vt(fd, size, vt):
pos += m
return sorted(out)
def find_tick(fd, dt=3.0):
"""One word that advances steadily: the guest-is-running witness."""
lo, hi = gmem.va_to_off(TICK_LO), gmem.va_to_off(TICK_LO + 0x400000)
a = os.pread(fd, hi - lo, lo)
time.sleep(dt)
b = os.pread(fd, hi - lo, lo)
best = None
for k in range(0, min(len(a), len(b)) - 3, 4):
va = struct.unpack_from('>I', a, k)[0]
vb = struct.unpack_from('>I', b, k)[0]
if va < vb and (vb - va) / dt > 5 and (vb - va) < 1 << 20:
best = (lo + k, (vb - va) / dt); break
return best
def sample(fd, defs, want):
lo, hi = gmem.va_to_off(entities2.ENT_VA_LO), gmem.va_to_off(entities2.ENT_VA_HI)
per = collections.Counter(); tot = 0; pos = lo
@@ -72,6 +91,10 @@ def main():
if va is None: continue
label[o] = wave3.resolve_id(f, w.size, o)[0] or '?'
want[va + LINK] = o
tick = find_tick(fd)
if tick: print('tick witness at %#x, ~%.1f/s' % (tick[0], tick[1]))
else: print('WARNING: no advancing counter found; stalls cannot be detected')
last_tick = struct.unpack('>I', os.pread(fd, 4, tick[0]))[0] if tick else 0
log = open('/tmp/wave6.tsv', 'w'); log.write('t\tkind\toff\tunit\tfrom\tto\n')
prev = None; t0 = time.time(); arr_n = loss_n = 0
while time.time() - t0 < secs:
@@ -85,8 +108,14 @@ def main():
arr = [(o, per[o]) for o in per if prev.get(o, 0) == 0 < per[o]]
los = [(o, prev[o], per.get(o, 0)) for o in prev if per.get(o, 0) < prev[o]]
arr_n += len(arr); loss_n += len(los)
print('t=%4ds craft=%d deployed=%d ARRIVALS=%d losses=%d (cum %d/%d)'
% (el, tot, len(per), len(arr), len(los), arr_n, loss_n), flush=True)
stalled = ''
if tick:
now = struct.unpack('>I', os.pread(fd, 4, tick[0]))[0]
if now <= last_tick: stalled = ' *** GUEST STALLED ***'
last_tick = now
print('t=%4ds craft=%d deployed=%d ARRIVALS=%d losses=%d (cum %d/%d)%s'
% (el, tot, len(per), len(arr), len(los), arr_n, loss_n, stalled),
flush=True)
for o, c in arr:
print(' ARRIVAL %-30s 0 -> %d' % (label.get(o, '?'), c), flush=True)
log.write('%d\tarrival\t%#x\t%s\t0\t%d\n' % (el, o, label.get(o, '?'), c))

View File

@@ -7,7 +7,7 @@ SECS="${1:-180}"; EVERY="${2:-10}"; HUNT="${3:-1}"
CFG=/tmp/nav-live.json
"$SD/launch_mission.sh" fly || { echo "BOOT FAILED"; exit 1; }
if python3 "$SD/entities2.py" self 0x130 "$CFG" >/dev/null 2>&1; then
SYLPH_HUNT="$HUNT" SYLPH_KILL_TURRETS=1 nohup python3 "$SD/pilot.py" "$CFG" "$SECS" \
SYLPH_HUNT="$HUNT" SYLPH_KILL_TURRETS=1 SYLPH_KEEPOUT="${SYLPH_KEEPOUT:-1400}" nohup python3 "$SD/pilot.py" "$CFG" "$SECS" \
</dev/null >/tmp/live-pilot.log 2>&1 &
PILOT=$!; echo "--- pilot (SYLPH_HUNT=$HUNT)"
else PILOT=""; echo "--- BIND FAILED, no pilot"; fi