fabby — a continuous NCF-margin OSI

research/fable_2 · target 0.55 · step 0.125 s · built 2026-07-10 · one estimator, two readings + a prediction, one rate law — no calibration phase, no trips, no latches, no NaN
10 / 10synthetic suite (gpt5: 10/10, fable at HEAD: 8/10)
90.7fabby score — updated gpt5_6: 94.8, fable: 73.2 (§4, §6)
0.28–2.0 sdamage → authority moving
~1.3 ssprint: floor → full authority once verified
1.5 swarm-up to first reading
0NaN readings, everywhere

1 · The idea: remove, don't add

fable answers "is the loop still stable?" with two spectral rings, a calibration phase, a coherence gate, an input-power gate, a sidelobe guard, two CUSUM trip detectors, a regime-change detector, an evidence gate and a 20-knob authority law with latched ceilings. Each piece exists because a simpler draft failed some record — but the pieces interact, and the failures we saw (GDBqFpKb stuck at a = 0.2 forever; NaN readings whenever a gate blanks) are failures of the interactions.

fabby starts from the observation that almost all of those guards exist to answer one question: "which frequencies am I allowed to believe?" There is a single mathematical device that answers it without any gate: prior regularization. Believe each bin exactly in proportion to how strongly the command drives it; where it isn't driven, believe the healthy prior. Every reading then degrades continuously toward "no evidence = healthy ceiling" instead of flipping through branches, so the indicator can be simple because it has nothing to protect itself from.

0.5 s Hann segmentevery 0.125 s step, one FFT of the tap u and the response dm
→
forgetting spectraSuu, Syy, Suy — exponential memory τ ≈ 1 s. The warm-up IS the memory filling: ≤ 1.5 s
→
regularized FRFT̂ = Suy/(Suu+λ), λ = 10% of mean input power
→
two margins + a predictiondriven band: chordal NCF margin of T̂. Quiet band: sensitivity growth of the output floor. Prediction: margin at higher authority from αL̂
→
b = min, always finiteevidence-weighted toward the 0.707 ceiling as the drive dies; the prediction gates the recovery sprint

2 · The math

2.1 Driven band — the project margin, regularized

The quantity is unchanged from the project definition: with the estimated closed-loop FRF T̂(ω) from the OSI tap to the measured displacement, the generalized (chordal) NCF stability margin is

b(ω) = 1 / ( √2 · √( |1−T̂(ω)|² + |T̂(ω)|² ) ), b_st = min over band of movmedian₃( b(ω) )

capped at its mathematical ceiling 1/√2 ≈ 0.707 (T̂ = S̄T̄ identity: |S|²+|T|² ≥ ½ always, with equality only at T = ½ — so 0.707 literally means "as far from instability as any loop can be"). The FRF estimate is the ridge-regularized least squares solution

T̂(ω) = Suy(ω) / ( Suu(ω) + λ ), λ = prior_frac · meanband( Suu ), prior_frac = 0.10

which is the Bayesian posterior mean under a zero-mean prior on T̂. Where the command has power (Suu » λ) the data win and T̂ is the ordinary H1 estimate; where it hasn't (Suu « λ) the estimate shrinks to 0 and the chordal margin of 0 is the ceiling — "nothing observable is wrong". This single term is what replaces fable's coherence gate, input-power gate and sidelobe guard: a noise bin cannot fake a deficit because a noise bin has no leverage on T̂. There is no bin mask, no minimum-bin count, no NaN.

2.2 Quiet band — margin from sensitivity growth

Damage at frequencies the command doesn't drive (the lowband-earthquake case) never appears in T̂. But the output noise floor there is shaped by the sensitivity: Pyy(ω) ≈ |S(ω)|²·N₀(ω), and the chordal margin obeys b ≤ 1/(√2·|S|). So the growth of the self-normalized floor against the model shape |S₀|² estimates the margin loss directly:

q(ω) = P̄yy(ω) / |S₀(ω)|² (self-normalized by its quiet-band median) g(ω*) = max over quiet bins of movmean₇( q ) ("quiet": Suu < λ, both memories) b_q = b₀(ω*) / √( g(ω*) / g_thr )

b₀(ω*) is the nominal margin curve of the initial loop model at the growing bin — the anchor the growth discounts. The 7-bin pooling matches the physical bandwidth of an emerging unstable mode (the 5k-plant bump spans ~12 Hz above 2 dB) and buys the variance reduction that lets 1–2 dB of growth be evidence. g_thr (1 dB) recenters the statistic so the typical healthy pooled maximum reads the anchor — and the map is deliberately smooth with no saturation kink, because the authority law needs b_q to be a monotone continuous function of growth to have an equilibrium instead of a relaxation oscillation (the single most important lesson of this redesign — see §5).

2.3 Evidence weight — no drive, no verdict, no NaN

On the real rig the OSI tap u = G_T(dd_fem − iff·fm) contains the force echo of dm, so an idle tap still correlates with the output and would fake a terrible margin (GDBqFpKb reads b ≈ 0.13 before the earthquake even starts). The fix is the same idea once more: weight the verdict by how much exogenous drive actually exists,

w = clip( rms(u)/quiet_mm − 1, 0, 1 ), quiet_mm = 0.05 mm b = w · min(b_st, b_q) + (1−w) · 0.707

Below 0.05 mm rms the tap is echo and sensor noise — the reading blends to the healthy ceiling instead of going NaN, which is precisely what lets authority recover through silence (GDB's stuck-at-0.2 deadlock cannot happen). Full trust needs only 0.1 mm; the suite commands run at 0.15 mm rms and the GDB earthquake at 0.14–0.28.

2.4 The authority prediction — how recovery gets to be fast AND safe

The regularized FRF contains more than the current margin: with L̂ = T̂/(1−T̂), the margin the loop would have at a relative gain multiplier α is the chordal margin of αL̂ — computable for every α from data already in hand. fabby evaluates it on a small log grid and the law uses the minimum along the path from α=1 up to full authority (the margin is not monotone in gain, and authority passes through every intermediate value):

pred(α) = min over driven bins of |1+αL̂| / √(2(1+|αL̂|²)), α ∈ [1 … min(a_max/a, 4)] α_safe = the α where the path-min of pred crosses target + 0.025 (certified-safe multiplier) blind = [quiet band populated] × w (observability of the certification)

Two guards make the certification honest. The horizon cap (α ≤ 4): a weak-signal FRF extrapolated 10× is noise, so the law only ever certifies "the next factor of 4 is safe", climbs, and re-verifies from fresh data — a rolling certification that still reaches full authority in a couple of estimator lengths. The observability gate: when the quiet band is populated, the command does not drive the frequencies where this rig actually goes unstable — the driven-band FRF then certifies nothing about higher authority there, and the quiet statistic itself is blind at reduced gain (growth below its noise floor). A certification that does not cover the hazard band is not a certification, so blind disables the sprint and regulation rides the always-safe creep. In every situation where recovery should be fast — broadband repair or heal, pulse over, test over, silence — the quiet set is empty and the sprint has full authority to act.

2.5 The authority law — one rate equation

d ln a / dt = −(k_dn·d + k_dn2·d²) d = relu(target − b)/target shed + k_creep·s s = relu(b − target − 0.025)/target creep + min(k_sprint·ln α_safe, r_max)·conf·(1−blind) sprint conf ← conf + (conf_raw − conf)·dt/τ, τ = 1.2 s up / 0.8 s down conf_raw = sat( (b − target − 0.03)/0.05 )

A multiplicative integrator on ln a, clipped to [0.08, 1], two state variables (a, conf). The shed is quadratic in the deficit: a grazing dip (d≈0.05) nibbles at ~0.3 ln/s while a collapsing reading (d≈0.4) sheds at ~6 ln/s — large authority drops only happen when the margin evidence itself is large, which is the "minimize sudden drops unless strongly justified" requirement made structural. The creep keeps regulation alive where the model is blind. The sprint drives authority toward the certified-safe point a·α_safe at a rate proportional to the certified distance ln α_safe (capped 2 ln/s — floor to full in ~1.3 s): for a healed or idle plant α_safe pins at the horizon and it runs flat out, while for a damaged plant it tapers smoothly to zero exactly at the predicted target crossing — an over-shed is reclaimed in a couple of seconds, but authority never charges past what the model can defend. conf multiplies it so the reading must sit clearly above target for a second or two first — trust builds slowly and evaporates instantly. No confirmation counters, no commit gate, no latches, no cooldowns.

3 · What was removed, and what pays for it

fable mechanismfabby replacement
coherence gate + null calibrationridge prior λ (§2.1) — undriven bins have no leverage
input-power gate (35 dB)
sidelobe / leakage guard (25 dB)
80-window calibration phasenone: the model shape |S₀|² is the baseline; warm-up = memory fill (≤1.5 s)
residual z-score + engage/expire hysteresissmooth growth→margin map (§2.2), deadband in the law
2 CUSUM fast-trip detectors + trip undoproportional shed on a fast estimator (τ≈1 s) — a collapse reads huge deficit within ~0.3 s
regime-change detector + rebaselinenothing to rebaseline: forgetting spectra follow the excitation by themselves
evidence gate (NaN) + blind probe + latch ceilingevidence weight w (§2.3): silence reads healthy, recovery is the ordinary sprint
prediction curve + safe-interval search + pred_floor/capssame idea, minimal: certified-safe α_safe from the path-min over a capped horizon (§2.4)
latched post-event ceiling (200 s memory)observability gate: sprint off while the hazard band is command-undriven; nothing remembered
5 s window + 0.6 s fast ring (two scales)one scale: τ_s = 0.9 s driven / τ_q = 1.25 s quiet

What pays for it: a shorter memory means a noisier margin (σ ≈ 0.02–0.04 vs fable's ~0.01 slow branch), which the law absorbs in the deadband, the quadratic shed and the conf gate rather than in the estimator. Post-event caution shrank from fable's 200 s latch to a ~10 s envelope — the model-verified sprint is what makes that safe: it re-checks the driven band every step on fresh data instead of remembering that something once went wrong.

Parameters (complete list)

indicatorvaluemeaning
segment_s / step_s0.5 / 0.125 sFFT segment, update cadence
tau_s / tau_q0.9 / 1.25 sdriven / quiet spectral memory (speed ↔ noise)
prior_frac0.10ridge prior vs mean input power (which bins to believe)
g_thr_db / pool_n1.0 dB / 7healthy pooled-max recentering / mode bandwidth
quiet_mm0.05 mmtap rms below which there is no evidence
warm_s1.5 sreport ceiling until the memory has filled
pred grid / horizon13 pts, α ≤ 4authority-prediction path (§2.4)
band / quiet band / line reject2–100 / 28–100 / 58–62 Hzproject constants (shared with fable)
authorityvaluemeaning
target0.55margin to regulate onto
k_dn / k_dn26 / 25linear + quadratic shed (gentle grazes, fast collapses)
k_creep / db_up0.5 / 0.025model-free proportional recovery + rest band
k_sprint / r_max3 / 2.0sprint per ln of certified headroom, capped (floor→full ~1.3 s)
conf_off / conf_w / τ_conf0.03 / 0.05 / 1.2 s up, 0.8 s downsprint trust: builds slowly, evaporates instantly
a_min / a_max0.08 / 1authority bounds

4 · The 10 ground-truth cases & the fabby score

Sample-accurate closed inner loop (discrete PID × identified 7000/5000 plants) where the true chordal margin is computable at every step. Both methods run the identical simulation; the authority each one commands feeds back into its own loop. fabby passes 10/10; fable at HEAD passes 8/10.

The fabby score (fabby_score.m) measures the thing that actually matters for J4: how well the true margin resulting from the commanded authority tracks the best-achievable margin. From the known plant blend λ(t), the ideal is b*(t) = max(target, b_true at full authority) — never detune a healthy loop, regulate a damaged one exactly onto the target. The signed error e = b_true − b* is averaged with the unsafe side (e < 0, less margin than the target demands) weighted 3×, and mapped to a 0–100 score (100 = perfect tracking; 0 = spending the whole run 0.10 over-conservative or 0.033 unsafe). Sitting shed while the true margin is high above target — the "authority never came back" failure — is exactly what this punishes. b* is drawn as the dashed ideal line in the margin panel below.

94.8gpt5_6 mean score (updated 07-10)
90.7fabby mean score
73.2fable mean score
74.8 / 57.9 / 0.0hardest case (4, quiet-band): gpt5 / fabby / fable

Click a case, wheel to zoom, drag to pan, double-click to reset.

5 · GDBqFpKb replay — following the diagnostic gain

The record that motivated the redesign: a live run (target was 0.8, up_deadband 0.10) where a diagnostic inner-loop gain was stepped 1 → 1.5 (50.2 s) → 2 (56.5 s) → 1 (75.5 s). The live law shed to a = 0.2 at 64.4 s and never recovered — after the earthquake ends (~78.4 s) its evidence gate blanks the margin to NaN and the recovery deadlocks. Below, fabby replays the same logged tap u_osi and dm (open loop: its authority cannot affect the logged plant, so once the reading sits below target it integrates to the floor — in closed loop it would settle at the b = target equilibrium instead, as the suite cases show).

+0.28 sshed begins after the 1.5× gain step (live: +14.2 s)
76.8 srecovery starts (gain edge at 75.47 s)
80.0 sfull authority (live: never)
0 %NaN readings (live b_osi: 87 % NaN)

Reading the margin panel: before 45 s the tap is idle (echo + noise, rms < 0.013 mm) — the evidence weight holds fabby at the healthy ceiling where the live indicator showed NaN. During the 2×-gain window both agree the margin is bad. After the gain returns (75.5 s) and the record goes quiet (78.4 s), fabby blends back to the ceiling and sprints home; the live law stays at 0.2 for the remaining 35 s of the record. The updated gpt5 (violet) is now clean in the idle pre-test phase too — its first version false-shed to the floor there until it adopted the same weak-drive-blends-to-ceiling device (§2.3).

Why gpt5 drops instantly at the quake (45 s) while fabby takes ~1.5 s — and what the hybrid loop does to both. This run is a full hybrid simulation: the numerical FEM substructure loop is closed around the inner actuator loop. The Speedgoat tap already carries the feedthrough adjustment — prepare_fable_runtime computes the full Newmark feedthrough D_fm = 1000·(ifl·K_eff⁻¹·f2f + iff) = −6.05×10⁻⁵ mm/N here, and the tap is u_osi = G_T(dd_fem − D_fm·fm) — but that removes only the zero-lag echo. The FEM's dynamic response to fm stays wrapped around the loop: per the vHS stability table the coupled open loop is L_out = −F_fm·k_mm·G_T·T_inner, and for this exact configuration (5k plant, pl_5k lead, fem_sp1_7000, logged k̂ = 5856 N/mm) it has |L_out| ≈ 0.30 across the 28–44 Hz quiet band and a coupled NCF margin of 0.401 @ 8.6 Hz at gain 1, vs the inner loop's 0.344 @ 38 Hz. So for THIS record the inner loop is the binding one, and the governing margin at quake onset is ≈0.344: gpt5's instant 0.352 lands near the right answer — but less cleanly than "handed the answer key": the quiet-band output shape it fits is distorted by the wrap factor |1−T·L_out/T| (~±30%) that its inner-model-only ρ family cannot represent; it got away with it because the distortion is smooth there. fabby is deviation-based — "plant equals the reference" reads as no deviation (median 0.646, high by +0.30) until driven-band evidence accumulates. Both authorities still shed within 0.4 s of onset (fabby 45.38 s, gpt5 45.12 s). The production-relevant warning cuts the other way: on the 7000 configuration the COUPLED loop binds (model outer 0.399 @ 38 Hz < inner 0.588 @ 35 Hz, per the numerical_ncf_plan analysis) — and that loop is monitored by neither estimator, both of which report the inner-loop margin from the tap. The planned online fix (b_out = chordal of W·M̂ with W = −F_fm·k_mm·G_T precomputed) applies equally to fabby, gpt5 and fable.

6 · fabby vs gpt5_6 — an honest comparison (updated implementation, 07-10)

research/gpt5_6 is an independent redesign of the same problem (gpt_osi + gpt_auth). Its first version scored 84.9 (case-4 under-defense, an idle-tap false shed on GDB, a fragile warm-up residual baseline). The updated version fixed all three: it blends the final reading to the NCF ceiling under weak command activity (the same evidence-weight device as fabby §2.3), it scales its guard and deadband with the quiet-band evidence weight, and — the biggest idea — it replaced the nonparametric residual statistic with a one-parameter model fit: fit a positive loop-gain multiplier ρ to the normalized output-spectrum shape in the quiet band, then read the margin of ρ·a·L₀ directly. Everything below is measured on the canonical fable_synthetic_suite with identical target 0.55; gpt5's traces are the violet lines in §4 and §5.

fabbygpt5_6 (updated)
canonical suite10/1010/10
fabby score90.794.8 — wins 8 of 10 cases; case 4: 74.8 vs 57.9
margin accuracy (healthy broadband)0.0130.008
case-4 estimate accuracy (healthy lowband)0.073 (upper bound by design)0.001 — the ρ-fit nails it (see caveat below)
case-4 defense (damaged, true margin min)0.4750.532, with much smoother authority (travel 1.4 vs 3.2)
quiet-band estimatornonparametric pooled growth → b₀/√g (assumes nothing about the damage shape)parametric ρ-fit onto {ρ·L₀} (far better-conditioned, but only spans gain-multiplier damage in 28–44 Hz)
driven-band estimatemodel-free (ridge to zero; L₀ used only by the quiet anchor)model-blended everywhere (confidence-weighted T-prior at current authority)
estimator↔authority couplingnonea_cmd must be fed back (prior, ρ-fit and full_margin all scale with it)
law2 states, 3 terms (quadratic shed, creep, certified sprint)1 state, 1 signed error, evidence-scaled deadband/guard, max_step clamp — simpler
GDB replay (target 0.55)full authority 80.0 s, 0 % NaNfull authority 79.2 s, 0 % NaN, idle phase now clean

Why the ρ-fit wins here — and where it can lose. fabby's quiet-band term estimates growth nonparametrically and maps it through b₀/√g — noisy, so the law must defend with margin to spare (fabby's case-4 authority breathes; score 57.9). gpt5 instead assumes the damaged loop lies in the one-parameter family ρ·a·L₀ and fits ρ to the output shape — a far better-conditioned estimate (case-4 error 0.001, authority nearly still). The caveat is the assumption: this suite's damage (7000→5000 plant swap) acts near the critical band almost exactly like a gain multiplier, so the family contains the truth and the fit is essentially exact — the best case the parametrization will ever see. Damage that moves a resonance in frequency or adds a new mode (a cracked specimen shifting modes, an actuator oil-column change) is outside the family and outside its hard-coded 28–44 Hz window; the fit then returns whichever ρ best mimics the shape, with no residual term left to catch what it missed. fabby's nonparametric term is strictly weaker on this suite and strictly more general off it. The same trade appears in the driven band: gpt5 blends toward the identified model everywhere (accurate while the model is right, confidently wrong when the rig drifts from it), fabby stays model-free where it has data.

A warning about gpt5_6's own GDB panel (gpt_osi_report.html shows its authority flat at 1.0 through the whole record and calls it PASS). That replay is shadow-tuned: its target is set to nominal−guard = 0.325 — below the 5k plant's healthy margin — so "never shed" becomes the law's correct equilibrium by construction, and the diagnostic-gain destabilization can no longer produce a deficit the law is asked to act on. Its own results file records the consequences: the indicator latencies for both destabilizing gain edges are NaN (the estimator never detected either step — during 57–64.4 s, gain 2 at full logged authority, the effective inner margin was ≈0.05 and it read median 0.352 with a pinned at 1.000), transition_pass = 0 and indicator_pass = 0. Those failing metrics are excluded from the headline acceptance, which instead reports "finite / no-pre-gain-shed / inverse-gain MAE better than the logged run" — criteria an inert a≡1 trace passes automatically (the logged fable MAE of 0.627 is dominated by its post-event deadlock; in the damaged window gpt5's MAE 0.459 is actually WORSE than the broken live run's 0.364). The §5 replay above runs both methods at the canonical target 0.55 with no per-record tuning.

Verdict. On this benchmark the updated gpt5_6 is the better implementation: higher score (94.8 vs 90.7), smoother authority, faster GDB recovery, and a simpler law — and its update is partly convergent evolution (it adopted the evidence-weight blend fabby introduced, while its ρ-fit and max_step clamp are ideas fabby should adopt). fabby's remaining advantages are structural rather than measured: no model dependence in the driven band, no a_cmd coupling into the estimator, a quiet band that watches 28–100 Hz rather than 28–44, and a damage model that assumes nothing about how the plant fails. Which one is "better" on the real rig therefore hinges on one question: how well does damage on this column stay inside the gain-multiplier family of the identified model? The natural v3 is fabby's skeleton with gpt5's ρ-fit added as a second quiet-band reading (min of the two: parametric sharpness when the family holds, nonparametric floor when it doesn't) plus the max_step clamp.

7 · Honest limits

Quiet-band information limit. Below ~1.5–2 dB of sensitivity growth the quiet statistic is inside its own noise; the defendable true margin in a lowband-earthquake scenario is ≈ 0.45–0.48 (suite tolerance target−0.15), not the target itself. This is physics, not tuning — fable hits the same wall (its case 4 currently regulates worse because of the latch regression). Regulation wobble. In sustained quiet-band damage, authority breathes between ~0.16 and ~0.64 around its equilibrium (visible in case 4) — the price of a 1.25 s estimator driving 2 s reactions; the deadband and the envelope keep it off the healthy cases entirely. Verified recovery is 3–7 s end-to-end, not 2. The sprint itself moves floor→full in ~1.3 s, but it fires only after the estimator has flushed (~1 s) and the reading has sat clearly above target for ~1–2 s (conf). That verification lag is deliberate: recovering into a damaged plant on one noisy healthy-looking sample is how hunting starts. When evidence is unambiguous (idle tap, silence, GDB post-test) total recovery is ~1.5–2 s. Quiet-band recovery rides the creep. With the hazard band command-undriven the sprint's certification is void (observability gate), so reclaiming authority after a quiet-band shed is proportional-only — visibly slower breathing in case 4. That is the correct asymmetry: fast recovery is reserved for situations the model can actually verify. Fading drive over damage. When the drive dies while the plant is damaged, evidence fades and authority recovers — indistinguishable, by information, from a healed plant. fable "solves" this with a 200 s latched ceiling; fabby keeps nothing (the GDB deadlock is that latch's failure mode). The protection is speed: if excitation returns degraded, the quadratic shed reacts within ~0.3 s.

Files: fabby_osi.m · fabby_auth.m · fabby_method.m · run_fabby_tests.m · make_report_data.m · results/ — reproduce with run_fabby_tests() (runs both methods on the suite, replays GDBqFpKb, regenerates report_data.js; leaves OSI_tests/fable untouched).