Fable OSI Speedgoat overloading fix — validation report

2026-07-03 · model_ColumnSetup_00 · target IISL · base rate 2048 Hz (488.28 µs budget per tick)

1. The problem, reproduced on the target

The fable OSI + authority-factor runtime stopped the Speedgoat with a CPU overload when the dd pause switch was flipped to resume and the indicator started evaluating. Reproduction (actuator disconnected): the target ran 30 s paused with TET ≈ 50 µs, then died 5 s after resume with

Overload limit (0) exceeded in 0.000488281s (tid=0) rate with 1 overloads
TETMax = 780.2 us   (budget 488.3 us)

The 5 s delay is diagnostic: it is exactly when the 5 s slow-spectral ring first fills and the whole slow pipeline — two 2048-pt FFTs, medians over the 99×17 spectra ring, classification, residual branch and the 25×99 prediction grid — landed in a single base-rate tick, on top of the fast-path work and the TSE stiffness update that always shared the same tick (64 divides 256). The same burst design also explains the “sometimes it works until the fast drop” observation: whichever event first stacked the heavy branches into one tick (ring fill, calibration finalize, trips + prediction) pushed that tick over 488 µs.

2. The fix: time-sliced scheduling, identical arithmetic

No algorithm was weakened. fable_osi_runtime.m now executes one bounded pipeline phase per base-rate tick after each 0.125 s step boundary and publishes at a fixed 56-tick (27 ms) latency — negligible against the 0.125 s step cadence and the 0.25–0.5 s fast-trip spec:

ticks after step boundarywork
0ring-tail snapshot + step bookkeeping
1–4fast segment: detrend/window, 512-pt FFT (dd, dm)
5–8slow segment (hop steps): detrend/window, 2048-pt FFT (dd, dm)
9–12ring medians, FRF/coherence/residual, classification + regime detector, calibration accept
13–51calibration finalize, split small: baseline medians (4), ln-ratio table (8), µ0 medians (4), pooled-MAD sort as 16 chunk-sorts + 4 merge passes, burst/FRF stats
52–55ST branch, residual branch, prediction grid + fast-grid FRF, fast path
56combine + authority law + publish

Supporting changes:

3. Arithmetic equivalence (bitwise)

test_fable_runtime_equivalence compares the time-sliced runtime against the desktop fable_osi/fable_auth pair, sample-aligned across margins, branches, trips, authority and decisions:

streamlengthmax |diff|verdict
synthetic path-coverage (calibration, trips, burst, residual, regime change)90 s0PASS
record 20260617_1731 (Pvar instability event)824 s0PASS
record 20260617_1749 (Pvar instability event)308 s0PASS
record 20260630_1930 (OSI test)422 s0PASS

4. On-target verification (physical Speedgoat, no actuator)

145 s protocol covering the calculation cases: always-on indicator from t = 0 (paused), first calibration + finalize at ~30 s, resume at 40 s (regime change → second calibration + finalize), authority law active at 60 s, pause again at 80 s (indicator keeps running), resume at 96 s, run to 145 s.

quantitypre-fix (burst)fixed (time-sliced)
outcome after resumeCPU overload, target stoppedfull run, no overload
TETMax (whole run, incl. both calibration finalizes)780 µs (> budget)278 µs = 57% of budget (275–278 across three full-protocol runs)
worst tick sourceeverything in one tickthe single 2048-pt slow FFT (phase 8) — irreducible without changing arithmetic
TETAvg~57 µs~50 µs
headroom for new calculationsnone (over budget)≈ 211 µs on the worst tick, ≈ 200 µs on any of the 200 idle ticks per step
Target verification: TET, margins, authority
Fixed implementation on target: running TETMax/TETAvg against the 488 µs budget (top), the slow/fast/selected indicators (middle) and pause flag vs authority (bottom). The indicator keeps evaluating through both pause windows.

5. Programmatic overload test (no target needed)

OSI_tests/overloadin_test/run_overloading_test.m compiles the current runtime and the frozen pre-fix reference to host MEX, streams a 90 s worst-case signal through both, times every tick, and maps the worst host phase onto target microseconds using measured anchors (pre-fix: 793 µs host ↔ 780 µs target; worst per-phase scale 2.1 from the slow-FFT phase). PASS requires the predicted worst tick under 75% of the budget. Current runtime: PASS (predicted ≈ 330 µs conservative vs 278 µs measured; pre-fix predicts ~1700 µs and fails loudly). Run it before any target run after touching fable_osi_runtime.m.

6. Ten synthetic closed-loop cases: pre-fix vs time-sliced

The fable_synthetic_suite ground-truth bench (known true chordal margin at every update). Copperwood = pre-fix burst, blue = time-sliced, black dashed = true margin.

#scenariopre-fixtime-slicednotes
1healthy_broadbandPASSPASSidentical accuracy (median error 0.014)
2intensity_stepPASSPASSno false adaptation on ×5 input step
3damage_step_broadbandPASSPASSauthority moved 0.625 vs 0.750 s (limit 0.75)
4damage_step_lowbandFAILFAILpre-existing quiet-band information limit; fails BOTH equally (viol 29.6% vs 33.8%)
5silentPASSPASSno alarms, no shed
6gradual_softeningPASSPASStracked + defended, same settle error 0.009
7damage_then_repairPASSFAIL**harness artifact: authority moved 0.625 s vs the 0.500 s limit — the block-stepped bench quantizes the 27 ms publish latency up to one full 0.125 s block (see note)
8starts_damaged_improvesPASSPASSregulates onto target, fast un-latched recovery
9regime_change_healthyPASSPASSrebaseline, no lasting shed
10transient_pulsePASSPASSbounded reaction, full recovery
On the one differing verdict (#7): the suite harness feeds 256-sample blocks and can only apply a returned authority at the next block boundary, so the sliced runtime’s mid-block publish is quantized to a full 0.125 s of extra latency (0.500 → 0.625 s against a limit that scenario #3 keeps passing). On the real target the added latency is 27 ms, and the record replays below show the two implementations produce identical step-aligned outputs. Scenario #4 fails both implementations for the same pre-existing reason and is not related to this change.
scenario 1 scenario 2 scenario 3 scenario 4 scenario 5 scenario 6 scenario 7 scenario 8 scenario 9 scenario 10

7. Actual data cases (hardware records)

The three saved records replayed through both implementations, step-aligned:

recordlengthmax |old − new| over margin AND authority
20260617_1731 Pvar (instability event, 769 s excursion)824 s0 (identical)
20260617_1749 Pvar (instability event)308 s0 (identical)
20260630_1930 testing_OSI422 s0 (identical)
record 1731 record 1749 record 1930

8. Authority factor: scale the PID gains or the command signal?

Keep multiplying the command signal (the existing Apply OSI gain Product), for three reasons:

  1. Safety semantics. When authority sheds during an event, multiplying the output instantly attenuates everything the controller has already built up — including the integrator state. Scaling the gains only changes how future error is processed: the accumulated integrator output keeps driving the actuator at full strength until it discharges, so an oscillation is arrested more slowly. For a stability-protection device the immediate effect is the point, especially on the fast-drop path.
  2. The prediction math assumes it. The fable authority-planning curve evaluates margins for α·L(jω) — a scalar on the whole open loop. Multiplying the command is exactly that; scaling the gains matches it only in steady state.
  3. Steady-state equivalence. With a constant factor the two are the same linear loop, so nothing is gained by moving it.

One caveat to watch: during a long deep shed the integrator can wind up (its output is attenuated while the error grows), which can overshoot when authority recovers. The fable law’s slow, latched recovery largely absorbs this; if it ever shows in tests, the right fix is back-calculation anti-windup on the PID using the post-product command — not moving the multiplication to the gains.

9. Files touched