From a3463ef4d95e6dac9f98e92fd018d8838f57a2b6 Mon Sep 17 00:00:00 2001 From: Sylpheed RE agent Date: Mon, 24 Aug 2026 14:42:36 +0000 Subject: [PATCH] re: six runs with no arrival, and an accidental no-pilot control wave6_probe refuses to interpret a run whose roster count is not the reproduced baseline of 116, per the discard rule. This run passed and its first sample is identical to the earlier link run: 116 records, 300 craft, 41 deployed, strength histogram 2x24, 4x1, 8x4, 18x12. The deployment is deterministic at mission start. The pilot failed to bind, which accidentally supplied the control condition the kill-versus-no-kill experiment needed. With nobody flying, not one craft was destroyed in four minutes -- exactly 300 across all 22 samples -- against 16-20 losses in each piloted run. So losses are attributable to the player being in the fight, and NPC crossfire destroys nothing by itself. That was an open question two iterations ago. Still no arrival. Zero 0 -> n transitions in either condition, across roughly fifteen minutes of cumulative Stage 02 flight and windows up to 240 s. The 75 records holding no craft at mission start still hold none at the end. Against a route table scheduling phase-1 arrivals at t = 90/120/170/210/240, that is now a strong negative rather than a null result. Three readings survive: the timetable's t is not seconds (at 30 Hz the whole phase-1 schedule finishes inside 8 s, before any first sample); arrivals are event-gated and no run supplied the trigger; or the mission is not advancing its phase clock at all. The third has never been checked and is the cheapest to eliminate, so it is now the prime suspect. Nothing in six runs has confirmed that mission time advances: the craft count freezes without a pilot, REMAINING OB has never read as a counter, and no clock has been located. Every "no arrival" observation is consistent with a scheduler that simply is not running, in which case the arrival results so far are measuring a stopped clock. --- docs/re/BACKLOG.md | 12 +++- docs/re/mission-arrival-watch.md | 70 ++++++++++++++++++++ tools/re-capture/wave6_probe.py | 104 ++++++++++++++++++++++++++++++ tools/re-capture/wave6_session.sh | 16 +++++ 4 files changed, 201 insertions(+), 1 deletion(-) create mode 100644 docs/re/mission-arrival-watch.md create mode 100755 tools/re-capture/wave6_probe.py create mode 100755 tools/re-capture/wave6_session.sh diff --git a/docs/re/BACKLOG.md b/docs/re/BACKLOG.md index e1bd15aa..c3d696c1 100644 --- a/docs/re/BACKLOG.md +++ b/docs/re/BACKLOG.md @@ -353,7 +353,17 @@ search cannot find a *schedule*. distinct VAs (116 == 116), so the VA-aliasing explanation is 🔴 refuted too. 🟡 The single 42/170 run stays unexplained; rule adopted: **discard a run that disagrees with 116 rather than interpreting it, and reproduce any finding in - ≥2 runs.** ❔ No arrival seen in any run yet. + ≥2 runs.** +* ✅🔴 **Six runs, no arrival — plus an accidental control** + ([`mission-arrival-watch.md`](mission-arrival-watch.md), 2026-08-24). Deployment + reproduces byte-for-byte (116 records, 300 craft, 41 deployed, strengths + 2×24/4×1/8×4/18×12). ✅ **Losses require the player**: an unpiloted run held at + exactly 300 craft for 240 s / 22 samples, vs 16–20 losses in each piloted run — + so NPC crossfire destroys nothing on its own. 🔴 **Zero `0→n` arrivals in either + condition**, ~15 min cumulative. ❔ **Prime suspect: the phase clock may not be + advancing at all** — never checked. **Next: find the mission timer** (HUD digits + or a fixed-rate counter); if it is stopped, every arrival result so far is + measuring a stopped clock. Earlier framing: [`mission-per-record-strength.md`](mission-per-record-strength.md) — one run gives 116 records/300 craft, the next 42/170, same disc, save and script. Save diff --git a/docs/re/mission-arrival-watch.md b/docs/re/mission-arrival-watch.md new file mode 100644 index 00000000..98832484 --- /dev/null +++ b/docs/re/mission-arrival-watch.md @@ -0,0 +1,70 @@ +# Six runs, no arrival — and an accidental control + +Status: ✅ the deployment structure reproduces exactly; ✅ losses require the +player; 🔴 **no arrival has ever been observed**, across six runs; ❔ whether +mission/phase time is advancing at all is now the prime suspect and is untested. + +## ✅ The deployment structure reproduces byte-for-byte + +`tools/re-capture/wave6_probe.py` refuses to interpret a run whose roster count +is not the reproduced baseline of 116 (the discard rule from +[mission-per-record-strength.md](mission-per-record-strength.md)). This run +passed, and its first sample is *identical* to the earlier link run: + +``` +roster records: 116 craft=300 deployed=41/116 +strengths [(2, 24), (4, 1), (8, 4), (18, 12)] +``` + +Same 41 deployed of 116, same discrete strength histogram, same total. The +deployment is deterministic at mission start. + +## ✅ Losses require the player — an accidental control run + +The pilot failed to bind this run (`BIND FAILED, no pilot`), so the craft sat +unattended. That is the control condition the kill-versus-no-kill experiment +needed, and it arrived by accident: + +| condition | duration | losses | +|---|---|---| +| hunting pilot | 168 s | 300 → 288 | +| hunting pilot | 168 s | 296 → 280 | +| **no pilot** | **240 s** | **300 → 300, zero** | + +With nobody flying, **not one craft was destroyed in four minutes** — the count +held at exactly 300 for all 22 samples. So the 16–20 losses in the piloted runs +are attributable to the player being in the fight, and **NPC crossfire does not +by itself destroy anything**. That was an open question two iterations ago and +it is now answered. + +## 🔴 No arrival, in either condition, in six runs + +Zero `0 → n` transitions. Not with a hunting pilot, not without one, across +roughly fifteen minutes of cumulative Stage 02 flight and windows up to 240 s. +The 75 records that hold no craft at mission start still hold none at the end. + +Set against `Route_S02.tbl`, which schedules phase-1 arrivals at +t = 90, 120, 170, 210, 240 in groups of 3, 3, 3, 2, 1, this is now a strong +negative rather than a null result. Three readings survive: + +1. **The timetable's `t` is not seconds.** At 30 Hz the whole phase-1 schedule + completes inside 8 s — before any probe's first sample — and everything that + was going to arrive already had. +2. **Arrivals are event-gated** and no run supplied the trigger. The control run + supplied nothing at all; the piloted runs killed 16–20 craft, which may be + below a threshold or of the wrong squadrons. +3. **The mission is not advancing its phase clock**, so no schedule ever fires. + +## ❔ The prime suspect is now (3), and it is untested + +Nothing in six runs has confirmed that mission or phase time is advancing at +all. The craft count is frozen without a pilot; `REMAINING OB` has never read as +a counter; no clock has been located. Every "no arrival" observation is +consistent with a mission whose scheduler is simply not running under these +conditions — and that possibility has never been checked, which makes it the +cheapest thing to eliminate next. + +**Next: find the mission timer.** The HUD shows elapsed mission time, so a +digit-recognition read of the clock region, or a memory scan for a counter that +advances at a fixed rate, would settle whether phase time moves. If it does not, +every arrival conclusion so far is measuring a stopped clock. diff --git a/tools/re-capture/wave6_probe.py b/tools/re-capture/wave6_probe.py new file mode 100755 index 00000000..92b6b16e --- /dev/null +++ b/tools/re-capture/wave6_probe.py @@ -0,0 +1,104 @@ +#!/usr/bin/env python3 +"""Per-record arrival/loss watch against the verified 116/300 baseline. + +Supersedes wave5_probe.py, which reported 42 records in a run nobody could +reproduce. Changes: + * refuses to interpret a run whose roster count is not the reproduced + baseline (mission-per-record-strength.md: discard, do not interpret); + * keys records by file offset rather than by primary VA; + * samples faster and for longer, since losses appear at ~1 per 10 s and no + arrival has been seen in five runs. + +An arrival is a record going 0 -> n; a loss is n -> n-1. +""" +import os, sys, time, struct, collections, importlib.util +SD = __file__.rsplit('/', 1)[0] +sys.path.insert(0, SD) +import gmem, gworld, entities2 +_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) +DELTA, WIN, LINK = 0x130, 0x400, 0x08 +BASELINE = 116 + +def scan_vt(fd, size, vt): + out = [] + for a, b in gmem.extents(fd, size): + pos = a + while pos < b: + m = min(1 << 24, b - pos) + blob = os.pread(fd, m, pos) + i = blob.find(vt) + while i != -1: + if (pos + i) % 4 == 0: out.append(pos + i) + i = blob.find(vt, i + 1) + pos += m + return sorted(out) + +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 + while pos < hi: + m = min(1 << 24, hi - pos) + blob = os.pread(fd, m, pos) + for k in range(0, len(blob) - 3, 4): + if blob[k:k+4] not in defs: continue + tot += 1 + head = os.pread(fd, WIN, pos + k - DELTA) + for j in range(0, len(head) - 3, 4): + (p,) = struct.unpack_from('>I', head, j) + if p in want: + per[want[p]] += 1; break + pos += m + return per, tot + +def main(): + secs = int(sys.argv[1]) if len(sys.argv) > 1 else 240 + every = int(sys.argv[2]) if len(sys.argv) > 2 else 10 + w = gworld.World(); fd = w.fd + defs = entities2.definitions(w) + if not defs: print('NOT IN A MISSION'); return 2 + roster = scan_vt(fd, w.size, ROSTER_VT) + print('roster records: %d (baseline %d)' % (len(roster), BASELINE)) + if len(roster) != BASELINE: + print('DISCARD: roster count is not the reproduced baseline. ' + 'Per mission-per-record-strength.md this run is not interpreted.') + return 3 + f = os.fdopen(os.dup(fd), 'rb') + label, want = {}, {} + for o in roster: + va = gmem.primary_va(o) + if va is None: continue + label[o] = wave3.resolve_id(f, w.size, o)[0] or '?' + want[va + LINK] = o + 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: + per, tot = sample(fd, defs, want) + el = round(time.time() - t0) + if prev is None: + print('t=%4ds craft=%d deployed=%d/%d strengths %s' + % (el, tot, len(per), len(roster), + sorted(collections.Counter(per.values()).items())), flush=True) + else: + 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) + 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)) + for o, a, b in los: + print(' loss %-30s %d -> %d' % (label.get(o, '?'), a, b), flush=True) + log.write('%d\tloss\t%#x\t%s\t%d\t%d\n' % (el, o, label.get(o, '?'), a, b)) + log.flush() + prev = per + time.sleep(max(0, every - (time.time() - t0 - el))) + log.close() + print('\nTOTAL arrivals=%d losses=%d over %ds' % (arr_n, loss_n, secs)) + return 0 + +if __name__ == '__main__': + sys.exit(main()) diff --git a/tools/re-capture/wave6_session.sh b/tools/re-capture/wave6_session.sh new file mode 100755 index 00000000..830204a6 --- /dev/null +++ b/tools/re-capture/wave6_session.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash +set -u +export HOME=/sylph-home/re SDL_AUDIODRIVER=dummy DISPLAY=:98 +export PYTHONPATH=/sylph-home/.local/lib/python3.12/site-packages +SD="$(cd "$(dirname "$0")" && pwd)" +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" \ + /tmp/live-pilot.log 2>&1 & + PILOT=$!; echo "--- pilot (SYLPH_HUNT=$HUNT)" +else PILOT=""; echo "--- BIND FAILED, no pilot"; fi +python3 "$SD/wave6_probe.py" ; rc=$? +[ -n "$PILOT" ] && kill "$PILOT" 2>/dev/null +echo "LIVENESS DONE rc=$rc"