What the policy’s machinery actually does on real rides — answered by
re-running the recorded corpus through kernel variants, with zero new riding.
Reproduce with python3 tools/cue-ablation/ablate.py — paths resolve
against the repo root, so any CWD works (2026-08-12 corpus: 23 traces,
88.1 km, 57,783 steps, 42 recorded cues; the corpus itself is local field
data, gitignored per NFR-005). Without it, make demo-corpus builds a
synthetic corpus and ablate.py demo-rides runs the same sweep. The
regenerated synthetic tables reproduce the
timing mechanisms, but not the field cue-count increase (23 → 23 on the
demo, versus the re-verified 42 → 83 field result).
Verification update (2026-09-12): the sweep now invokes the normal replay verifier on every original trace before printing any tables. This checks every recorded decision’s fields at its timestamp and rejects unrecorded HEAD_UPs; optional omitted NONE records remain allowed by the schema. Previously the sweep checked only aggregate HEAD_UP count, which could accept changed event IDs or timing. The uploaded private corpus now passes 23/23 trace verifications, and the field tables below have been regenerated (aggregate evidence). New output also compares full observation-decision streams and cue timestamps against baseline, so equal counts are no longer presented as equal decisions.
This is the replay harness used as a research instrument rather than a
regression suite: the same --print evaluation that authors fixtures
re-scores the whole field corpus under a modified policy in seconds.
| Variant | What changes | How |
|---|---|---|
baseline |
Nothing — sanity anchor | Traces exactly as recorded; must reproduce all 42 recorded cues (and does) |
no_memory |
Personal route memory (RFC 0002) disabled | personal_memory stripped from each trace |
no_thresholds |
Severity/confidence thresholds and both cooldowns removed | policy_config rewritten to 0 (notice window, speed gate, and the FR-004 one-cue-per-event budget stay) |
every_zone |
no_thresholds plus the notice window blown open (0–32,767 s) |
The true “cue once per approached zone” upper bound: only the speed gate, inside-event check, and FR-004 budget remain |
distance_gate |
Distance window instead of time-to-event | replay_cli rebuilt with -DCUE_ABLATION_DISTANCE_GATE (kernel #ifdef, replay ablation builds only — #error-guarded out of PICO_BUILD firmware): cue when 30 m ≤ distance ≤ 115 m — the 5–20 s window at the corpus median cue-moment speed of 5.8 m/s |
| Variant | Cues | Lead s min/med/max | In 5–20 s window | Suppressions (full histogram) |
|---|---|---|---|---|
baseline |
42 | 12 / 15 / 20 | 42/42 | TOO_SLOW 21,196 · TOO_EARLY 6,733 · INSIDE_EVENT 1,909 · ALREADY_CUED 564 · TOO_LATE 347 |
no_memory |
42 | 12 / 15 / 20 | 42/42 | TOO_SLOW 21,196 · TOO_EARLY 6,733 · INSIDE_EVENT 1,909 · ALREADY_CUED 580 · TOO_LATE 331 |
no_thresholds |
42 | 12 / 15 / 20 | 42/42 | identical to baseline |
every_zone |
83 | 34 / 226 / 1,748 | 0/83 | TOO_SLOW 21,196 · ALREADY_CUED 7,589 · INSIDE_EVENT 1,899 · TOO_LATE 24 |
distance_gate |
41 | 6 / 19 / 73 | 24/41 | TOO_SLOW 21,196 · TOO_EARLY 6,488 · INSIDE_EVENT 1,906 · ALREADY_CUED 763 · TOO_LATE 397 |
| Variant | Changed observation decisions | Changed cue timestamps |
|---|---|---|
| baseline | 0 | 0 |
| no_memory | 16 | 0 |
| no_thresholds | 0 | 0 |
| every_zone | 7114 | 125 |
| distance_gate | 820 | 83 |
1a. The kernel’s threshold and cooldown gates do not bind in this
corpus. no_thresholds is decision-identical to baseline: across 57,783
field steps, not one suppression came from severity, confidence, or either
cooldown. The reason is upstream: every one of the corpus’s 37,349
observations arrives with severity ≥ 128 and confidence ≥ 128 — the phone’s
squeeze scoring is the first filter, and the corpus never presents the
kernel a sub-threshold zone. The kernel gates are the MCU-side defense in
depth for inputs the phone no longer prefilters — a role this corpus cannot
exercise. (Scope: this claim is about the threshold and cooldown gates
only; the notice window is measured separately below.)
1b. The notice window is the load-bearing noise gate. every_zone —
thresholds and window removed — nearly doubles the cue count (42 → 83)
and puts zero of them in the 5–20 s spec window: cueing a zone at first
sight fires a median 226 s (up to 29 minutes) before the squeeze, and
because each cue spends the event’s FR-004 budget slot at that first
sighting (ALREADY_CUED 564 → 7,589), the useful in-window moment can never
cue again. On this corpus, essentially all of NFR-001’s noise control that
the kernel itself provides lives in the notice window plus the speed gate
(TOO_SLOW 21,196).
2. Personal route memory changes reasons, but no cue timestamps.
no_memory produces the same 42 cues. The corpus carries 239 memory
records — 83 UNSAFE, 29 with a +2 s notice bonus — but UNSAFE only bypasses
a severity gate that never binds (finding 1). The 5 → 7 s minimum-notice
change does not alter emitted cues, whose shortest lead is 12 s. The histogram
shows 16 changed decisions, all confined to reason codes:
16 steps shift between TOO_LATE and ALREADY_CUED, and no cue moves. The
lever is armed but has not yet been reached by field conditions.
3. Speed normalization is what the time window buys — a distance window demonstrably fails this corpus. Cue-moment speeds span 0.9–17.1 m/s (3.3–61 km/h). A 30–115 m window — calibrated to approximate 5–20 s at the median cue speed — keeps only 24 of 41 cues inside the notice window the spec asks for, fires one cue at a 73 s lead (a slow approach at long distance), and drops one recorded cue outright. Same corpus, same calibration point, 41% of cues out of spec: time-to-event is not a stylistic choice, it is what makes one window serve the whole field speed range.
distance_gate kernel variant lives behind
-DCUE_ABLATION_DISTANCE_GATE and is compiled only by the ablation tool
(via replay/Makefile’s own recipe, so the binary cannot drift from the
one CI tests); a #error rejects the flag in PICO_BUILD firmware, and
the default build is byte-identical with the flag unset.--print evaluates without verifying, so ablated variants (which diverge
from recorded decisions by design) run to completion; the baseline
is preceded by normal verification of all original traces. The aggregate
cue-count check remains a secondary tripwire, not evidence of exact
equivalence. Run python3 -m unittest discover -s tools/cue-ablation for
the count-preserving corruption regressions (also part of make test).