Twinned from https://github.com/cfsh/ninefold/issues/514 by tools/beads/import_github.py's reconcile pass.
Hit live on the #505 merge. Filed unclaimed.
merge_pr.sh 505 ran the post-merge UI gate and reported:
``` ──── UI gate: main @ d3d90d9 ──── 31 passed, 1 failed compared against the sweep recorded at f5fd75f
████ NEW FAILURES — this merge regressed main ████ new-pattern [probe-gate] recorded baseline at d3d90d9 (1 failing) ```
#505 cannot have caused it. Its entire diff is tools/pr/rebuild_f5.py, tools/pr/poll.py, their tests, and CLAUDE.md — no game code, no scene, no probe. And new-pattern passes on that exact sha:
```
$ git log --oneline -1
d3d90d9 rebuild_f5: hold paused PRs out of f5 ... (#505)
$ for i in 1 2 3; do ./probe.sh tools/probes/new-pattern.json; done
run 1: exit=0 | 0 assertion-failures
run 2: exit=0 | 0 assertion-failures
run 3: exit=0 | 0 assertion-failures
```
3/3 green standalone, failed once inside a 32-probe sweep. It is flaky under sweep load, not regressed.
tools/probes/new-pattern.json is entirely frame-counted — {"wait": 45} for the scene to load and seed the demo, {"wait": 10} / {"wait": 14} after opening the naming modal. Nine wait steps, zero wait_ms.
VisualProbe.cs:213-215 names this exact hazard: *"the off-screen probe window runs uncapped under load, so 40 frames can be under 200ms"*. During a full sweep the box is busy, and 45 frames is no longer the wall-clock the probe was calibrated against. Same class ninefold-reviewer flagged as F1 on #508 hours ago, biting main's gate rather than one PR.
The flake is now the baseline. The gate recorded d3d90d9 (1 failing) including new-pattern, so from here on a *genuine* break in that probe is compared against "already failing" and never surfaces as NEW. The gate is off for that probe until something re-records it green.
And it cannot be corrected at that sha, by design:
``` $ python3 tools/pr/probe_gate.py [probe-gate] d3d90d9 is already recorded — skipping the sweep (1 failing at that sha). new-pattern (since d3d90d9) [probe-gate] (another session swept this exact tree; re-sweeping would re-derive [probe-gate] a known answer, and a flaky probe could turn it into a false NEW.) ```
That guard is right in general — and here it is the thing preserving the false entry. The (since <sha>) tracking shows persistence is intended, which is correct for a real failure and wrong for a flake.
Self-healing on the next merge *if* the probe passes in that sweep. Not guaranteed, since the flake is load-dependent and merges are exactly when the box is loaded.
probe_gate.py.new-pattern's waits to wait_ms. Fixes this probe; says nothing about the next one. Worth doing anyway.wait_ms" — would find the rest before a gate does.--force re-sweep a recorded sha. So a session that knows the entry is a flake can correct it instead of waiting for the next merge to maybe fix it.The first is the one that stops the class; the rest are cleanup.
Related: #508 (F1, same frames-vs-wall-clock class), #389 (a gate that rotted unnoticed), #409 (needs:direction on probe-gate reporting), #412.
None.
No comments.