docs/research/sota-2026-05-22/R12_1-pose-pabs-closed-loop.md
Status: synthetic validation of R12 PABS's needed closure · 2026-05-22
R12 PABS (tick 19) gave a clean 1,161× intruder-vs-drift lift in static scenes. But it had a known false-alarm problem: subject moving 10 cm gave PABS = 22,000× drift. R12 PABS noted:
Real production PABS needs a pose-aware forward model updating from
pose_tracker.rsin real-time. The actual structure-detection signal is PABS-after-pose-update.
This tick implements the closed loop in synthetic form and validates that pose updates resolve the false-alarm problem while preserving intruder detection.
5 m link, 2.4 GHz, 50 frames. Subject walks continuously from (2.0, 2.0) to (3.0, 3.5). Intruder enters at frame T=25 at fixed position (1.5, 1.5). Two PABS pipelines compared:
Compute PABS = ‖observed − predicted‖² / ‖observed‖² at each frame for both pipelines.
| Phase | Fixed-expected | Pose-updated |
|---|---|---|
| Pre-intruder (T<25), subject moving | 6.02 | 0.30 |
| Post-intruder (T≥25), intruder enters | 7.76 | 2.84 |
| Intruder detection lift | 1.29× | 9.36× |
The closed loop resolves the false-alarm problem:
R12 PABS gave a clean detection signal only in static scenes. Real-world rooms have moving subjects almost always. Without pose updates, every subject step triggers a false-alarm spike. R12.1 validates that updating the forward model from pose estimates absorbs subject motion into the prediction, leaving only unexplained residuals for the structure-detection signal.
The 20× suppression of subject-motion contribution is much larger than the pose tracker's 5 cm noise. This is because the multi-scatterer body model (R6.1) is smooth — 5 cm pose noise produces small per-subcarrier prediction errors, well below the static-drift floor.
R12 PABS catalogued this as ~50-100 LOC. Concretely:
// pseudocode for the closed loop in vital_signs / structure module
let pose = pose_tracker.estimate(csi_window)?; // ADR-079 / ADR-101
let expected_scene = body_model.from_pose(pose) + room_walls;
let y_predicted = fresnel_forward.simulate(expected_scene);
let pabs = (csi_window - y_predicted).norm_sq() / csi_window.norm_sq();
if pabs > threshold {
emit_structure_event();
}
Three additions:
body_model.from_pose(pose) — translate pose-tracker output to scatterer positionsfresnel_forward.simulate(scene) — the R6.1 multi-scatterer modelpabs(observed, predicted) — straightforward L2 normTotal ~80 LOC + ~30 LOC of plumbing. Slot into the existing vital_signs cog at the per-frame inference path.
| Tick | Thread state | Headline |
|---|---|---|
| R12 (tick 5) | NEGATIVE | SVD eigenshift fails: 0.69× signal/drift |
| R12 PABS (tick 19) | POSITIVE | 1,161× intruder detection (static) |
| R12.1 (this) | CLOSED LOOP | 9.36× intruder detection (dynamic) |
Three ticks, three states: failure → success with caveat → success without caveat. The kind of multi-tick arc that justifies a long research loop.