Measure-only counters for the deficit hunt (~23ms short per 700ms window
at 50% on the bottleneck site; superlinear vs 25%). Nothing here changes
injection or absorption; the sweep decides the fix.
Buckets, windowed per cell into new ExperimentResult fields (audit
snapshot taken at end() — spin/attribution freeze there, forgiveness
does not):
- spin_absorbed / park_forgiven: where owed delay actually went. Spin
during a 0% cell is the baseline-contamination signature — leftover
debt from a prior window being paid in a later one (checks gate on
the experiment word, so cooldowns pay nothing and debt carries over).
- drop_park / drop_yield (+counts): the deschedule path flushes no
sample tail — an in-target-site park or yield silently loses
[last sample -> now]; on_resume re-arms before the actor runs again.
New on_deschedule hook in all three intent arms (real park; explicit/
slice-expiry yield; requeued park counts as yield — it never blocked).
Slice-expiry yields sample at the descheduling checkpoint, so a fat
yield bucket points at explicit yield_now or requeued parks.
- discard_overmax (+count, in would-be delta terms so columns compare
against injected_cycles) / discard_unarmed: the attribute() clamps,
previously silent.
LedgerCounters + ledger_counters() expose cumulative totals (tests,
run-level prints); render_ledger_audit() is the per-cell companion to
render_summary, which stays byte-identical (pinned). ExperimentResult
now derives Default so literals survive future audit-field growth.
Tests: +7 (spin counted, forgiveness counted, in-site park drop, in-site
yield drop, overmax discard, 0%-window leftover absorption — synthesized
deterministically via inject_delay_cycles_for_test with no experiment
active — and the audit render). 22/22 causal.