feat(adr-120): windowed temporal classifier (W-MLP) — 53.53% → 90.40%
Adds WindowedMlpModel: 440 → 64 ReLU → n_classes, stacks last 20
frames × 22 features as input. Captures temporal patterns that
frame-level classifiers physically cannot see (walking cadence,
sit-stand cycles, gesture rhythm).
AppStateInner gets feature_window: VecDeque<[f64; 22]> (cap 20)
auto-pushed at the 3 tick sites before adaptive_override. The
classify_window API flattens the buffer (oldest first) + current
frame's features → 440-d input → softmax over classes. Cold-start
(<20 frames) falls back to frame-level MLP.
AdaptiveModel now carries all three classifiers side-by-side:
LogReg (ADR-118), MLP (ADR-119), W-MLP (this). classify_window
picks W-MLP first; legacy classify() picks MLP > LogReg.
Result on the same 6-node, 7-class, 151,329-frame dataset:
LogReg: 49.58%
MLP: 53.53%
W-MLP: 90.40% (+36.87 pts over MLP, +50.0 pts over original
2-node 15-feature LogReg baseline)
Per-class W-MLP accuracy:
absent 100% (was 41%)
present_still 100% (was 99%, saturated)
transition 86% (was 36%) — sit/stand cadence captured
waving 90% (was 38%) — gesture cadence captured
present_moving 82% (was 33%) — walking step cadence captured
active 74% (was 30%) — jumping bursts captured
Loss broke through frame-level plateau (1.15 → 0.25). Caveat:
90.4% is training-set accuracy; ~28k weights on ~30k windowed
samples means some overfitting likely. Held-out test set
recommended as follow-up.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>