From 1280df7ce08cadf6aa7f6e5d8e8e8173c7ea1b74 Mon Sep 17 00:00:00 2001 From: Sylpheed RE agent Date: Mon, 24 Aug 2026 14:27:31 +0000 Subject: [PATCH] re: the 42-vs-116 outlier does not reproduce; losses are observable Two further runs sampled the counts from the instant flight is detected. Both give 116 roster records in the very first sample, flat thereafter, with 14 unit definitions throughout. That is not a load race: a race would climb toward 116 from below, and it never does. The leading explanation was arithmetic rather than behavioural -- census_probe counts raw aligned vtable hits while wave5_probe counted distinct primary_va values, which are different quantities if an offset can alias to a VA another offset also claims. Measuring both in the same run refutes it: 116 raw hits and 116 distinct VAs in every sample. So the outlier stands unexplained. Four runs now read 116/300, 116/300, 116/296 and one 42/170, with save drift, freezing, load race and VA aliasing each individually refuted. Recorded rather than dropped, because a one-in-four anomaly in the measurement apparatus is what later turns into a wrong conclusion. Practical rule adopted: discard a run that disagrees with 116 rather than interpreting it, and reproduce any finding in at least two runs. Useful new fact: craft declines steadily and monotonically in both runs, 300 to 288 and 296 to 280, roughly 16-20 losses over 168 s under the hunting pilot. Whether the player or NPC crossfire is responsible is not determined here, but destruction is visible in this signal, which is half of what the kill-versus- no-kill experiment needs. Still not observed in any run: an arrival. --- docs/re/BACKLOG.md | 9 ++- docs/re/mission-per-record-strength.md | 59 ++++++++++++++++++ tools/re-capture/census_probe.py | 83 ++++++++++++++++++++++++++ tools/re-capture/census_session.sh | 16 +++++ 4 files changed, 166 insertions(+), 1 deletion(-) create mode 100755 tools/re-capture/census_probe.py create mode 100755 tools/re-capture/census_session.sh diff --git a/docs/re/BACKLOG.md b/docs/re/BACKLOG.md index 39bf7fc..e1bd15a 100644 --- a/docs/re/BACKLOG.md +++ b/docs/re/BACKLOG.md @@ -347,7 +347,14 @@ search cannot find a *schedule*. Fan-in: only **41 of 116** records have craft (2×24, 4×1, 8×4, 18×12 = 300). ⇒ **an arrival is craft appearing for a record that had none; a kill is that count decreasing — both attributable to a named squadron**, hence to a route. - 🔴 **BLOCKER (2026-08-24): the per-record measurement does not reproduce.** + ✅ **Blocker downgraded (2026-08-24): the baseline DOES reproduce** — two more + runs give 116 roster records flat from t=0 (not a load race) with craft + declining 300→288 and 296→280, so **losses are observable**. Raw hits == + 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. + 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 drift is REFUTED (savedata untouched since 2026-08-23) and the guest was not diff --git a/docs/re/mission-per-record-strength.md b/docs/re/mission-per-record-strength.md index 2a351f2..50f5d76 100644 --- a/docs/re/mission-per-record-strength.md +++ b/docs/re/mission-per-record-strength.md @@ -72,3 +72,62 @@ Every sample was identical: 170 craft, 24 deployed, no `0 → n`, no `n → n− 2. **Get a confirmed kill.** Without one, the kill-versus-no-kill experiment cannot run. The pilot reaches firing range and does not destroy anything, so the gap is accuracy, not engagement. + +--- + +# The 42-vs-116 outlier does not reproduce (2026-08-24) + +Status: ✅ the baseline is stable and it is **not** a load race; 🔴 the VA-aliasing +explanation is refuted; 🟡 the outlier itself stays unexplained; ✅ losses are +observable. + +`tools/re-capture/census_probe.py` samples the raw counts every 12–20 s from the +instant flight is detected, and also reports what the scan is looking at. + +## ✅ Two further runs, both stable at 116 + +``` + t roster distVA craft defs extents MB + 0 116 116 300 14 272 342.2 + 60 116 116 294 14 262 342.3 + 120 116 116 292 14 258 342.4 + 168 116 116 288 14 258 342.4 +``` + +and a second run, 116 throughout, craft 296 → 280. + +**Not a load race.** The roster count is 116 in the very first sample and never +moves — it does not climb toward 116 from below, which is what a race would look +like. Definitions are 14 throughout, matching Stage 02's 14 unit types. + +## 🔴 Refuted: VA aliasing was not the cause + +The leading hypothesis was arithmetic rather than behavioural: `census_probe` +counts **raw aligned vtable hits**, while `wave5_probe` counted **distinct +`primary_va` values**, and if one offset can alias to a VA another offset also +claims, those are simply different numbers. Measuring both in the same run +settles it: **116 raw hits, 116 distinct VAs, in every sample**. They are the +same number here, so the difference in what was counted cannot explain 42. + +## 🟡 The outlier stands, unexplained + +Four runs now: 116/300, 116/300, 116/296 — and one 42/170. Same disc, save, +script and stage; save drift, freezing, load race and VA aliasing are all +individually refuted. I do not know what produced it. + +What that changes in practice: the baseline is trustworthy enough to build on, +but **any single run that disagrees with 116 should be discarded rather than +interpreted**, and a result must be reproduced in at least two runs before it is +written down as a finding. Recorded rather than quietly dropped, because a +one-in-four anomaly in the measurement apparatus is exactly the kind of thing +that later turns into a wrong conclusion. + +## ✅ Losses are observable; arrivals still are not + +Craft declines steadily and monotonically in both runs — 300 → 288 and +296 → 280, roughly 16–20 losses over 168 s under the hunting pilot. Whether the +player or the NPC crossfire is responsible is not determined here; what matters +is that **destruction is visible in this signal**. + +No arrival has yet been seen in any run. The per-record `0 → n` watch is still +the right instrument, and it can now be run against a known-good baseline. diff --git a/tools/re-capture/census_probe.py b/tools/re-capture/census_probe.py new file mode 100755 index 0000000..29e48fe --- /dev/null +++ b/tools/re-capture/census_probe.py @@ -0,0 +1,83 @@ +#!/usr/bin/env python3 +"""Why does the roster-record count differ between runs (116 vs 42)? + +Samples the counts repeatedly WITHIN one run, from the instant flight is +detected, and also reports what the scan is actually looking at: + + * roster records -- vtable 0x820AF030 hits + * craft -- unit-definition-pointer sites in the entity heap + * definitions -- vtable 0x820AF844 hits + * extents/bytes -- what gmem.extents() enumerates, to test whether the scan + region itself differs between runs + +A count that climbs = a load race. A count flat at a run-specific value = not a +race, and the cause is in the guest, not the probe. +""" +import os, sys, time, struct, collections +sys.path.insert(0, __file__.rsplit('/', 1)[0]) +import gmem, gworld, entities2 + +ROSTER_VT = struct.pack('>I', 0x820AF030) +DEF_VT = struct.pack('>I', 0x820AF844) +DELTA = 0x130 + +def count_vt(fd, size, vt, want_vas=False): + """Raw aligned hits, and optionally how many DISTINCT primary VAs they map + to. wave5 printed the latter and census the former; if an offset can alias to + a VA another offset also claims, the two are different numbers and that alone + would explain 42 vs 116.""" + n = 0; vas = set() + 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: + n += 1 + if want_vas: + v = gmem.primary_va(pos + i) + if v is not None: vas.add(v) + i = blob.find(vt, i + 1) + pos += m + return (n, len(vas)) if want_vas else n + +def extent_stats(fd, size): + n = tot = 0 + for a, b in gmem.extents(fd, size): + n += 1; tot += b - a + return n, tot + +def craft_count(fd, defs): + lo, hi = gmem.va_to_off(entities2.ENT_VA_LO), gmem.va_to_off(entities2.ENT_VA_HI) + n, pos = 0, 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] in defs: n += 1 + pos += m + return n + +def main(): + secs = int(sys.argv[1]) if len(sys.argv) > 1 else 180 + every = int(sys.argv[2]) if len(sys.argv) > 2 else 12 + w = gworld.World(); fd = w.fd; size = w.size + t0 = time.time() + print('%5s %8s %8s %7s %6s %8s %10s' % + ('t', 'roster', 'distVA', 'craft', 'defs', 'extents', 'MB')) + while time.time() - t0 < secs: + el = round(time.time() - t0) + defs = entities2.definitions(w) + r, rvas = count_vt(fd, size, ROSTER_VT, want_vas=True) + d = count_vt(fd, size, DEF_VT) + c = craft_count(fd, defs) if defs else 0 + en, eb = extent_stats(fd, size) + print('%5d %8d %8d %7d %6d %8d %10.1f' + % (el, r, rvas, c, d, en, eb / 2**20), flush=True) + time.sleep(max(0, every - (time.time() - t0 - el))) + return 0 + +if __name__ == '__main__': + sys.exit(main()) diff --git a/tools/re-capture/census_session.sh b/tools/re-capture/census_session.sh new file mode 100755 index 0000000..e46d01b --- /dev/null +++ b/tools/re-capture/census_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/census_probe.py" ; rc=$? +[ -n "$PILOT" ] && kill "$PILOT" 2>/dev/null +echo "LIVENESS DONE rc=$rc"