This repository has been archived on 2026-09-16. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
Syplheed-Reborn/docs/re/guest-stalls.md
Sylpheed RE agent 2b98105ac0 re: multi-witness vote works, its threshold did not, and freezes persist
The graded output is the improvement. Instead of a single bit it reports how
many witnesses agree, and the sequence tells a coherent story: 11, 9, 7, 4, 1,
then 0 of 31, with the drop to zero at t=183 s coinciding exactly with the last
loss and 106 s of nothing after it. That is a real freeze, identified.

The threshold was wrong though. Flagging a stall at "fewer than half" marked the
entire run stalled, including samples in which craft were destroyed, so 11 of 31
advancing is a healthy guest rather than a stalled one. The cause is the cluster
choice: the modal rate was 93/s, far above the ~16.5/s frame rate timer_probe
measured, and those are subsystem counters that tick in bursts and sit idle in
most 15 s windows even while the game runs. Picking the modal cluster was
convenient rather than principled.

Fixed to prefer the cluster whose rate falls in the frame-rate band of 8-40/s,
falling back to modal only if none exists, and to flag a stall only when zero
witnesses advance, which is the signal the data actually supports. Not yet run.

The uncomfortable part: this run used the cheap probe and still froze, at about
183 s. The previous iteration's "0 stalled samples" came from the unreliable
single-word witness and cannot stand as validation. What the evidence supports
now is that the no-probe control ran 300 s clean, the heavy probe froze at 27 to
255 s, and the cheap probe froze at 183 s -- one run on each arm. Cheap sampling
plausibly helps but does not remove the freeze, and it is equally possible the
freeze is stochastic and the control was lucky. Recorded as unresolved rather
than resolved in the probe's favour.

Practical consequence: the usable window is roughly three minutes per run,
sometimes less, whether or not the probe is cheap. Experiments needing longer
have to survive a freeze or be redesigned around one.
2026-08-24 17:09:43 +00:00

11 KiB
Raw Blame History

The guest stalls, often — and the probe now detects it

Status: ✅ the stall witness works and is validated against independent telemetry; ✅ it correctly discarded a run that would otherwise have been written up as evidence; 🔴 stalls are frequent and early enough to threaten the whole wave line of work; 🟡 the probe's own memory scanning is the leading suspect and is untested.

✅ The witness works

wave6_probe.py locates a word that advances at frame rate and prints *** GUEST STALLED *** on any sample where it fails to advance. Its first run:

tick witness at 0x11c225810, ~26.7/s
t=  14s craft=300 deployed=41  ARRIVALS=0 losses=0
t=  27s craft=300 deployed=41  ARRIVALS=0 losses=0  *** GUEST STALLED ***
   ... stalled for every remaining sample to t=313s

Validated independently. The pilot's own telemetry, which the probe never sees, agrees: 35 distinct speed values across the entire log, and exactly 1 in the last 400 lines, against 236 in the first 400 lines of a healthy run. All the variation is in the first ~50 s. The witness flagged the stall at t = 27 s and the telemetry puts it in the same place.

✅ It earned its keep immediately

Without it, this run reads as "no arrivals across 313 seconds with 300 craft resident" — a clean, quotable, and completely worthless result, since the game was frozen for 90 % of it. That is exactly the failure the previous iteration warned about, caught automatically on the first run after the fix.

Operational rule: a run whose witness reports a stall is discarded, and every future write-up must state the witness result. Flat samples are not evidence unless the witness says the guest was advancing.

🔴 Stalls are frequent and early

run stall onset usable window
keep-out 1400 (previous) ~255 s ~255 s
keep-out 600 player died 83 s, frozen after 83 s
this run ~27 s ~27 s

Two of the last three long runs froze, one of them almost immediately. This is consistent with the pre-existing freeze recorded elsewhere in the corpus, but its frequency makes long observation windows unreliable — and long windows are exactly what the arrival question needs.

🟡 Leading suspect: the probe itself

docker/agent/AGENT.md warns that a full memory scan "competes with the emulator for every core under lavapipe", and the probes have grown heavier every iteration. wave6_probe now reads the whole 32 MB entity heap plus a 1 KB pread per craft — roughly 300 extra syscalls — every 12 seconds, while the emulator is trying to render.

That is a plausible cause of the freezes and it has never been tested. It would also be an uncomfortable one: it would mean the instrument has been degrading the thing it measures, and that the "no arrival" results were collected under conditions the game was struggling with.

Test: run the hunting pilot for 300 s with no probe at all, and read the outcome only from the pilot's own log — distinct speed values over time. If it does not stall, the probe is the cause and the sampling has to get much cheaper (narrow the scan to the roster records' own region, sample less often, or read only the craft bases already located rather than rescanning).

Not settled this iteration

The multi-squadron kill-threshold test did not run: the guest froze before the pilot destroyed anything, so there were zero losses and nothing to threshold.


✅ Confirmed: the probe was causing the stalls (2026-08-24)

The control needed no new decoding — hunting pilot, SYLPH_KEEPOUT=1400, 300 s, and no memory probe at all, judged only from the pilot's own log.

last pilot timestamp: 299.4s
  t=  0- 60s  distinct speeds: 211
  t= 60-120s  distinct speeds: 257
  t=120-180s  distinct speeds: 250
  t=180-240s  distinct speeds: 241
  t=240-300s  distinct speeds: 200

Healthy through the entire run, with no decline in the final window. Against the probed runs, which stalled at ~255 s, ~83 s and ~27 s and ended with 1 distinct speed value across 400 log lines.

The instrument was degrading the thing it measured. AGENT.md warned about exactly this — "the full scan competes with the emulator for every core under lavapipe" — and the probes grew heavier each iteration until wave6_probe was reading the entire 32 MB entity heap plus ~300 extra preads every 12 seconds while the game rendered.

Strength of the claim: the contrast is stark and the mechanism was predicted in advance, but the control is n = 1. It is recorded as confirmed-enough to act on, not as proven; a second clean control would settle it.

What this costs

Every "no arrival" result in mission-arrival-watch.md was collected while the probe was running, i.e. under conditions the game was struggling with, and in some runs after it had frozen outright. That does not make them wrong — the elimination test at t = 163 s happened in a window the witness later showed was live — but it does mean none of them is as strong as written, and the whole arrival question deserves re-running with cheap sampling before any conclusion is drawn from silence.

The fix: sample ~4 orders of magnitude less

The full rescan is nearly all waste. After the first scan the craft bases and their roster links are already known, so a sample only needs the hull word at each known base — 300 × 4 bytes ≈ 1.2 KB, against 32 MB.

wave7_probe.py does that: one full enumeration at the start, then cheap hull polling, with a full rescan only every ~90 s to catch anything genuinely new. That is 3–4 heavy scans per run instead of 25.

Implemented, not yet run — so the claim that it stops the stalling is untested, and the next run must report the witness before anything else.


✅ The cheap probe does not stall the guest (2026-08-24)

First run of wave7_probe.py — one enumeration, then hull polling, full rescan every 90 s:

TOTAL candidate-up=1 down=19 stalled samples=0

Zero stalled samples across the whole run, against three consecutive heavy-probe runs that stalled at ~27, ~83 and ~255 s. The fix works, and the previous iteration's confirmation is now supported from the other direction as well.

The guest is also visibly healthier: 19 losses against 8 in the heavy-probe run of comparable length. Starving the emulator was suppressing the very activity the probe existed to watch.


The single-word witness gives false positives (2026-08-24)

Status: 🔴 the witness as first written is unreliable; ✅ the contradiction that exposes it is in its own output; ✅ replaced with a majority vote, not yet run.

A run with the cheap probe and a bound pilot produced this:

t= 30s deployed=41 up=0 down=1 ... *** GUEST STALLED ***
t= 75s deployed=41 up=0 down=1 ... *** GUEST STALLED ***
t=151s deployed=40 up=0 down=2 ... *** GUEST STALLED ***
TOTAL down=16  stalled samples=13

Thirteen samples flagged as stalled while recording losses in the same samples. A frozen guest cannot destroy craft, so those flags are false positives, and the run was in fact healthy — 16 losses over 290 s.

The cause is the selection rule: it took the first word in a 4 MB window whose rate fell in a plausible band. Plenty of counters advance intermittently — a buffer index, a per-event tally — and one of those satisfies the test while saying nothing about whether frames are being rendered.

timer_probe.py had already done this correctly and the lesson was not carried over: it collected 286 linearly-advancing words and looked at the rate histogram, where a dominant cluster sat at ~17 /s.

Fixed: wave7_probe.py now collects every candidate, keeps the modal-rate cluster, samples up to 32 of them, and reports a stall only when fewer than half advance. It also says RUN UNVALIDATED when no witness is found at all, because an earlier run printed stalled samples=0 with tick witness: NONE — a witness that does not exist cannot report zero stalls.

Not yet run, so the majority rule is unverified.

What this costs

The stall counts quoted for earlier runs came from the pilot-log speed analysis, not from this witness, so they stand. But any run judged only by the single-word witness is unreliable in both directions, and the "0 stalled samples" that validated the cheap probe came from a run where the witness may equally have been a lucky pick. That validation should be repeated with the majority rule.


The majority vote is better, the threshold was wrong — and freezes persist (2026-08-24)

Status: ✅ the multi-witness vote produces a usable graded signal; 🔴 the "fewer than half" threshold is wrong; 🔴 freezes still happen with the cheap probe, so the earlier "0 stalled samples" validation is superseded.

The run

tick witnesses: 185 candidates, modal rate 93/s, using 31

t= 15s  down=1  *** GUEST STALLED (11/31 witnesses moved) ***
t= 45s  down=1  *** GUEST STALLED (11/31) ***
t= 90s  down=1  *** GUEST STALLED ( 4/31) ***
t=138s  down=1  *** GUEST STALLED ( 8/31) ***
t=168s  down=0  *** GUEST STALLED ( 1/31) ***
t=183s  down=1  *** GUEST STALLED ( 0/31) ***
t=198s … t=289s  down=0   *** GUEST STALLED (0/31) ***  (every sample)
TOTAL down=7  CONFIRMED arrivals=0

✅ Graded output is the improvement

Unlike the single-word witness, this says how many agree, and the number tells a coherent story: 11 → 9 → 7 → 4 → 1 → 0, with the drop to zero at t = 183 s coinciding exactly with the last loss. From t = 183 onward the guest really is frozen — 0 of 31 counters advance and nothing is destroyed for 106 s.

🔴 The threshold was wrong

Flagging a stall at "fewer than half" marked the entire run stalled, including samples where craft were being destroyed. 11 of 31 advancing is a healthy guest, not a stalled one.

The cause is the cluster choice. The modal rate here was 93 /s, far above the ~16.5 /s frame rate timer_probe measured — those are subsystem counters that tick in bursts, so most are idle in any given 15 s window even while the game runs. Selecting the modal cluster was convenient rather than principled.

Fixed: prefer the cluster whose rate falls in the frame-rate band (8–40 /s), falling back to modal only if none exists, and flag a stall only when zero witnesses advance — the signal the data actually supports. Not yet run.

🔴 Freezes are not eliminated by the cheap probe

This run used the cheap probe and still froze, at t ≈ 183 s. So the previous iteration's "0 stalled samples" — which came from the unreliable single-word witness — cannot stand as validation.

What the evidence now supports: the no-probe control ran 300 s clean (n = 1), the heavy probe froze at 27–255 s, and the cheap probe froze at 183 s (n = 1). Cheap sampling plausibly helps but does not remove the freeze, and with n = 1 on each arm it is equally possible the freeze is stochastic and the control was lucky. Recorded as unresolved rather than resolved in the probe's favour.

Consequence: the usable window per run is roughly 3 minutes, sometimes less, whether or not the probe is cheap. Any experiment needing longer than that has to survive a freeze or be redesigned around one.