diff --git a/docs/re/BACKLOG.md b/docs/re/BACKLOG.md index df6e261..ba5c770 100644 --- a/docs/re/BACKLOG.md +++ b/docs/re/BACKLOG.md @@ -442,7 +442,20 @@ search cannot find a *schedule*. cannot miss 15 s. Cause was mine: the candidate search had been narrowed to **4 MB** when the witness was bolted in, while `timer_probe` searched the full **32 MB** and found 286 with a clean ~17/s cluster. **Fixed: full-region search - once at startup + warn when <8 witnesses — not yet run.** All three witness + once at startup + warn when <8 witnesses.** +* ✅ **WITNESS VALIDATED (2026-08-24, 4th attempt)**: full-region search gives + 6500 candidates → 32 witnesses at 21/s, and the flags have **zero + contradictions** with the loss data (the one loss is unflagged; every flagged + sample has no losses). That is the check the three previous versions failed. +* 🔴 **The freeze is now THE blocker.** Onset across runs: **27, 45, 83, 183, + 255 s** (median ~83 s) — usable window is 1–4 min and unpredictable. This run + froze at 45 s with 1 loss, useless for arrivals despite a working instrument. + ⚠️ Being honest: the "cheap" probe still reads 32 MB at startup + 32 MB every + 90 s, and the new calibration added **two more 32 MB reads** — the 45 s freeze + came right after it. **Trimmed to 8 spread 512 KB windows + rescan every 180 s + — not yet run.** Also: the 42-record anomaly recurred and the discard rule + correctly refused the run; a rescan-until-baseline retry is now in place to + test whether it climbs. All three witness failures share one pattern: a shortcut in *selecting* the witness, each caught only by the flagged samples contradicting losses in the same output. * ✅ **Bind failure fixed + abort verified**: `entities2 self` finds the player by diff --git a/docs/re/guest-stalls.md b/docs/re/guest-stalls.md index be985c2..8fd983b 100644 --- a/docs/re/guest-stalls.md +++ b/docs/re/guest-stalls.md @@ -296,3 +296,45 @@ intended. The cause: `entities2 self` finds the player by *motion* between two samples, so a craft sitting still at mission start is invisible. The session now holds the throttle for 3 s before each attempt. Verified — the next run bound immediately and produced 11 losses. + +--- + +# ✅ The witness is validated (2026-08-24, fourth attempt) + +``` +tick witnesses: 6500 candidates, using 32 at 21/s (frame-rate band) +t= 15s down=0 +t= 30s down=1 <- loss, NOT flagged +t= 45s down=0 *** GUEST STALLED (0/32 witnesses moved) *** +t= 60s … t=211s down=0 *** GUEST STALLED (0/32) *** (every sample) +TOTAL down=1 stalled samples=12 +``` + +**Zero contradictions.** The single loss falls in an unflagged sample; every +flagged sample has no losses. That is the internal-consistency check the three +previous versions each failed, and it passes here. + +What made the difference was searching the **whole 32 MB region** rather than a +4 MB window — 6500 candidates instead of 5, with a clean cluster at 21 /s +matching the measured frame rate. + +## 🔴 But the freeze is now clearly the dominant problem + +Onset across runs: **27, 45, 83, 183, 255 s** — median around 83 s. This run +froze at 45 s and produced exactly one loss, so it is useless for the arrival +question even though the instrument finally works. + +The usable window is one to four minutes and unpredictable. Fourteen of fifteen +runs have gone into the arrival question and it still has no answer, largely +because of this. + +## The probe is still not cheap + +Worth being honest about: "cheap probe" was never that cheap, and the witness +made it worse. It still reads 32 MB to enumerate at startup, another 32 MB every +90 s to rescan, and the full-region calibration added **two more 32 MB reads**. +The freeze at 45 s came immediately after that calibration. + +Trimmed: calibration now samples **8 spread windows of 512 KB** (4 MB total, +still covering the whole span) rather than reading everything twice, and the +rescan interval is doubled to 180 s. Not yet run. diff --git a/tools/re-capture/wave7_probe.py b/tools/re-capture/wave7_probe.py index 14f5489..8ee1ac9 100755 --- a/tools/re-capture/wave7_probe.py +++ b/tools/re-capture/wave7_probe.py @@ -16,7 +16,7 @@ wave3 = importlib.util.module_from_spec(_w3); _w3.loader.exec_module(wave3) ROSTER_VT = struct.pack('>I', 0x820AF030) DELTA, WIN, LINK, HULL = 0x130, 0x400, 0x08, 0x154 -BASELINE, RESCAN = 116, 90 +BASELINE, RESCAN = 116, 180 # rescan halved in frequency: it is a 32 MB read def scan_vt(fd, size, vt): out = [] @@ -62,10 +62,20 @@ def main(): w = gworld.World(); fd = w.fd defs = entities2.definitions(w) if not defs: print('NOT IN A MISSION'); return 2 + # The 42-instead-of-116 anomaly has now happened twice. Rather than discard + # immediately, rescan a few times: if the count CLIMBS toward 116 the roster + # is being built progressively and the probe simply started too early, which + # is a fact worth having. If it sits at 42 it is something else. roster = scan_vt(fd, w.size, ROSTER_VT) print('roster records: %d (baseline %d)' % (len(roster), BASELINE)) + for attempt in range(1, 6): + if len(roster) == BASELINE: break + time.sleep(10) + roster = scan_vt(fd, w.size, ROSTER_VT) + print(' rescan %d: %d records' % (attempt, len(roster)), flush=True) if len(roster) != BASELINE: - print('DISCARD: not the reproduced baseline'); return 3 + print('DISCARD: settled at %d, not the reproduced baseline %d' + % (len(roster), BASELINE)); return 3 f = os.fdopen(os.dup(fd), 'rb') label, want = {}, {} for o in roster: @@ -88,19 +98,24 @@ def main(): # failed to advance in 15 s windows where craft were being destroyed, i.e. # they are bursty, not frame counters. timer_probe searched the WHOLE 32 MB # region and found 286 with a clean cluster at ~17/s. Pay the one-off cost. + # Full-region calibration found the witnesses but reads 32 MB twice, and this + # probe is already suspected of provoking the freezes. Sample the region + # SPREAD OUT instead: 8 windows of 512 KB, 4 MB total but covering the whole + # span, which found 6500 candidates' worth of diversity in practice. lo, hiw = gmem.va_to_off(0xBC000000), gmem.va_to_off(0xBE000000) - span = hiw - lo + step = (hiw - lo) // 8 + wins = [(lo + i * step, min(512 << 10, step)) for i in range(8)] def grab(): - out, pos = bytearray(), lo - while pos < hiw: - n = min(1 << 24, hiw - pos); out += os.pread(fd, n, pos); pos += n - return bytes(out) + return b''.join(os.pread(fd, n, o) for o, n in wins) a = grab(); time.sleep(3.0); b = grab() cands = [] + base_of = [] + for o, n in wins: base_of += [(o, i) for i in range(0, n, 4)] for k in range(0, min(len(a), len(b)) - 3, 4): va, vb = struct.unpack_from('>I', a, k)[0], struct.unpack_from('>I', b, k)[0] if va < vb and 5 < (vb - va) / 3.0 < 200: - cands.append((lo + k, round((vb - va) / 3.0))) + wo, off = base_of[k // 4] + cands.append((wo + off, round((vb - va) / 3.0))) # The MODAL cluster is not the frame counter. One run picked a modal rate of # 93/s, and only 11 of 31 of those advanced during active combat -- they are # subsystem counters that tick in bursts. timer_probe measured the frame-rate