fable osi · diagnosis & fix · 2026-07-05 · research/osi_parameter_tuning/fable

The numerical substructure echoes dm straight back into dd — and the OSI hears itself

Closing the numerical loop does not change the PID + Hdc,dm loop — the intuition is right. What it changes is the command signal the OSI listens to: the 3-DOF FEM has a direct feedthrough term dd = C·x + interface_from_force·fm, so every sample of the measured force — noise included — reappears in dd within the same tick, amplified ×8 by the fem3_ncf3 phase lead. Above the FEM's top mode (18.5 Hz) that echo is almost all of dd. The estimator then measures the inverse of the outer feedback path instead of the inner-loop FRF, the chordal margin at those bins reads 0.1–0.3, and the authority law dutifully sheds to the floor. One subtraction at the OSI's command tap — estimator untouched — restores the margin to where the open-loop tests always had it.

RTHS_02 · median OSI margin
0.29→0.66
engaged window, target 0.55 · open-loop tests sat at ≈0.6–0.7
time below target
98%→6%
what was left is the EQ ramp-in transient, not steady state
time at full authority
0%→76%
a = 1.00 through the entire strong-motion phase
instability detection
kept
genuine event still reads b < 0.1 after the fix (record 6)
F1

Where the echo enters the loop

The RTHS signal path, with the part nobody drew on the whiteboard in red. The FEM's output equation has a D-term: interface_from_force = −5.22·10⁻⁸ m/N. Multiply by the interface stiffness (≈6·10⁶ N/m) and the phase lead's high-frequency gain (×8) and the loop hands the OSI a zero-lag copy of roughly −0.23·dm at every frequency, buried inside dd:

FEM (3-DOF) dd = C·x + iff·fm phase lead (8s+71)/(s+71) PID dc MTS + specimen H_dc_dm load cell fm ag (el-centro) dd dd_lead dm fm (measured force → numerical substructure) the echo: iff·fm re-enters dd in the SAME tick (D-term), then ×8 through the lead OSI u-tap today: dd_lead (echo inside) OSI y-tap: dm

The proof is one filter and one regression away. Take the >25 Hz part of dd during the earthquake and overlay the reconstructed echo G_lead(iff·fm): they are the same waveform (r = 0.94), and the cross-correlation peaks at 0.0 ms — the FEM's algebraic D-term, not the actuator's forward path, which would show up 15–25 ms later:

dd high-frequency content overlaid on the reconstructed force echo, and the cross-correlation peaking at zero lag
Fig 1 — the echo, visible raw. Left: one second of >25 Hz dd (black) vs 0.92·G_lead(iff·fm) (red), RTHS_02, t = 80–81 s. Right: cross-correlation of the two; the single-sample spike at 0 ms is the algebraic feedthrough. Side lobes at ±15–35 ms are the ordinary loop dynamics.
F2

Why the estimator cannot defend itself

The OSI's ST branch estimates the inner-loop FRF as M = S_uy/S_uu from (dd, dm) and takes the worst chordal margin over bins that pass three gates — coherence, input power, sidelobe. This is textbook-correct when the command is exogenous. The echo breaks the assumption, and it breaks it in a way every gate approves of:

Input spectrum, coherence, and per-bin chordal margin at t=95s for corrupted and cleaned taps
Fig 2 — one slow window (t = 95 s), full autopsy. Top: dd input power — the echo (red) floods 20–100 Hz above the −35 dB gate; the cleaned tap (black) drops it 20–40 dB. Middle: coherence — echo bins are fully coherent, so the γ² gate cannot help. Bottom: per-bin chordal margin of the bins that pass all gates — corrupted bins (red) scatter down to 0.20 far below both plant models; the cleaned tap's five genuine bins (black) sit on the healthy C·H₇₀₀₀ curve.
Estimated FRF on the complex plane, corrupted vs cleaned
Fig 3 — where M actually lands. Corrupted 20–100 Hz bins (red) cluster on the negative-real side, far off the model T(ω) locus (green) — the signature of closed-loop identification bias toward the inverse feedback path. The cleaned tap's bins (black) return to the model.

This also explains why the BLWN / band-noise tests were always fine. An injected broadband command puts real exogenous power in every bin; the echo (a fraction of dm's noise floor) is negligible next to it, the same estimator reads 0.6–0.7, and everything looks healthy. The echo only owns the band when the exogenous content is narrowband and low — exactly an earthquake at intensity 0.06. And it explains the characteristic time signature you saw: at EQ onset the estimator's first windows are nearly pure echo (fast drop to ≈0.1), then the strong motion adds genuine low-frequency bins that pull the min-scan up to the least-bad echo bin (recovery to only ≈0.3).

No parameter can fix this. We checked every gate knob: the echo is coherent (γ²→1), often stronger than the genuine excitation (power gate inverted), broadband (frequency-median can't vote it out), and stationary (regime detector never fires). The information the estimator needs — "which part of dd did the plant not cause?" — simply is not in (dd, dm). It has to come from the wiring.
FIX

Subtract the echo at the OSI tap — one gain, one sum

The FEM computes dd = C·x + iff·fm. Both iff (a FEM constant) and fm (the measured force the FEM already consumes) are sitting in the model, so the echo term is known exactly, sample by sample. Give the OSI's command input the strictly-proper part only; the actuator keeps receiving the full dd — RTHS physics is untouched, and the estimator, runtime, and authority law stay byte-for-byte as they are.

Simulink wiring (exact)
u_osi = G_T( dd_fem − iff·fm ) = G_T(C·x)

Tap the FEM output before the phase lead, subtract iff·fm (one Gain + one Sum), pass that through a copy of the phase-lead block, feed the OSI's dd port. Zero approximation: it reconstructs the FEM's strictly-proper output.

Equivalent post-lead form: u_osi = dd_lead − G_T(iff·fm).

Offline replay (this study)
u_osi = dd − s·G_T(iff·fm), s ≈ 0.92 (fitted)

Records only store the post-lead dd, so the echo is reconstructed from fm and the saved fem3_ncf3 lead, with one scalar s fitted on the >25 Hz band (the record does not resolve an extra rig-side scaling on fm; on-target the structural wiring needs no fit). 10.8 dB echo suppression — enough to push every echo bin under the estimator's own gates.

RTHS_02 margin and authority, baseline vs fixed tap
Fig 4 — RTHS_02 (the stable reference run), before/after. Red: OSI + authority as wired today — margin pinned at 0.2–0.35, authority slammed to 0.08 and never recovered. Black/green: identical estimator on the cleaned tap — margin 0.62–0.71 through the whole earthquake, authority back at 1.00 by t = 70 and held there for the entire strong-motion phase. Remaining artifacts: the ~5 s dip right at loop engagement (mixed analysis window + EQ ramp-in; see LIMITS) and the post-disengagement tail after t = 110 where the run is over.
VAL

Validation across all ten records

The same pipeline ran on every new test record plus two historical open-loop controls. The echo detector (zero-lag regression of >25 Hz dd against G_T(iff·fm)) doubles as a classifier — it flags exactly the runs where the numerical loop actually carried the feedthrough, and its lag separates echo (0 ms) from ordinary forward-path correlation (−25 ms at the actuator delay). Metrics below are over the engaged span (skipping the estimator warm-up), target 0.55.

recordecho verdictcorr @ lag b median< targetfull authoritynote
20260625_1740_RTHS_02 closed · echo dominant 0.96 @ 0 0.29 → 0.66 98% → 6% 0% → 76% the user's stable run; fully recovered by the fix
RTHS_instability_during_eq closed · echo dominant 0.91 @ 0 0.20 → 0.33 100% → 93% 0% → 0% genuine instability: still reads b < 0.1 at the event — detection preserved
RHTS_no_inst_butindicators_jump closed · echo dominant 0.97 @ 0 0.71 → 0.71 47% → 43% 0% → 0% loop closed with ~zero excitation and no tracking (LP corr −0.27): a no-information regime — see LIMITS
RTHS_osi_rising_emergency closed · echo dominant 0.95 @ 0 0.11 → 0.19 88% → 83% 0% → 0% ran with authority collapsed live (tracking corr 0.33) — the run itself was broken by the bug; not repairable offline
RTHS_instability_at_the_end closed · echo minor 0.51 @ 0 0.71 42% 33% strong broadband excitation swamps the echo; baseline healthy through the test, collapses correctly at the real instability (~93 s). No subtraction applied
RTHS_5k no zero-lag echo 0.35 @ −52 0.51 72% 0% forward-path lag only. Its low margin is a different topic: the 5k plant's healthy margin (≈0.35 by model) sits below the 0.55 target
20260704_1129_testing_OSI no zero-lag echo 0.31 @ 0 0.71 24% 79% strong injected signal — healthy
osi_highband no zero-lag echo 0.39 @ −52 0.52 70% 0% open-loop control group
osi_lowband no zero-lag echo 0.00 @ +2 0.67 8% 62% open-loop control group — healthy
20260630_1930_testing_OSI no zero-lag echo 0.27 @ 0 0.71 3% 86% historical open-loop reference — matches its known-good replay

Three cross-checks worth calling out:

Instability record, baseline vs fixed
Fig 5 — the safety check. RTHS_instability_during_eq with the fix: the healthy stretch reads higher (0.3–0.45 vs 0.15–0.25), but at the genuine divergence (t ≈ 38–43 s) both taps agree: b < 0.1, authority sheds. The fix removes the false alarms, not the real ones. (This run was itself partly degraded by the live authority collapse, which is why its "healthy" level stays below RTHS_02's.)
ARM

The start-of-test drop: hold when there is no evidence

Even with the cleaned tap, the first seconds after the numerical loop engages read low (b ≈ 0.2 for ~5 s on RTHS_02): the 5 s analysis window still straddles the switch-on, and during the EQ ramp-in the FEM's modal response to force noise outweighs the tiny exogenous content. That reading is not evidence — and the authority law already has the right reflex for "no evidence": when the margin is NaN, it holds (fable_auth.m, "no evidence: hold"). So the remaining piece is a small evidence-arming gate (fable_evidence_gate.m) that decides, from the OSI's own input signal, when the slow margin deserves to be believed:

RTHS_02 with evidence-gated authority
Fig 6 — RTHS_02, gated vs ungated law (both on the cleaned tap). Green shading = armed. The engagement transient is blanked, so gated authority (green) never leaves 1.00 at the start (ungated red still crashed to 0.08 and needed 15 s to climb back). The one small step to 0.86 at ~105 s is real evidence (margin 0.50–0.55) and is kept. Onset a_min 0.08 → 1.00; full authority 78% → 90%.
No-excitation record with evidence-gated authority
Fig 7 — the "indicators jump" record, solved. Closed loop with almost no excitation: the gate arms only in the one span with real input (margin there reads a healthy 0.707), blanks the stale dip when that phase ends abruptly at t ≈ 72, and the authority never moves for the whole run (full authority 38% → 100%).

The price, measured. Genuine events during armed operation shed on time (instability_at_the_end, event at 87 s: −0.5 s vs ungated). A genuine event inside the very first arming window (instability_during_eq: event 3 s after engagement) sheds ~11 s later — during the first seconds of excitation, "healthy loop under new input" and "diverging loop" are statistically the same from (u, y), so this delay is an information limit, not a tuning miss; the rig's displacement/force interlocks remain the backstop for that window. The theta/arm sweep (0.03–0.08 mm × 5–8 s) confirms the onset protection is robust: every combination except the loosest (0.03 mm / 5 s) keeps start-up authority at 1.00.

Simulink realization: RMS block (2 s) → compare (0.05 mm) → 6 s on-delay with the 1 s/6 s fade comparator → a switch that publishes NaN in place of the slow margin while unarmed (or gates the authority-update enable). Plain blocks, no algorithm change; validated in run_evidence_gate_validation.m.
LIM

Limits, and what to do at the rig

What the fix does not cover

Recommended order of work

Reproduce everything

research/osi_parameter_tuning/fable/rths_echo_diagnosis.m  % 10-record detector + replays + figures + metrics
research/osi_parameter_tuning/fable/rths_echo_mechanism_figs.m  % Figs 1–3 (RTHS_02 autopsy)
research/osi_parameter_tuning/fable/fable_evidence_gate.m  % the arming gate (params/state/step)
research/osi_parameter_tuning/fable/run_evidence_gate_validation.m  % Figs 6–7 + theta/arm sweep
results: results/rths_echo_diagnosis.mat · results/echo_metrics.json · results/evidence_gate_validation.mat · figures/*.png