Skip to content

Commit a03c431

Browse files
rocketmarkclaude
andcommitted
lc-gate: force-accept after consecutive drops to break EWMA deadlock
light_residuals_all only updates from accepted batches, so a batch the gate rejects can never loosen the gate that rejected it. When a 2nd/3rd lighthouse comes online after tracking converged on one LH alone, the resulting state perturbation can push every LH's RMS above the cutoff at once, deadlocking tracking permanently (observed on stghnd-4D41). Force-accept a batch after 5 consecutive per-LH drops so the EWMA can update and the gate re-calibrates instead of starving forever. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 77aaff1 commit a03c431

2 files changed

Lines changed: 28 additions & 5 deletions

File tree

src/survive_kalman_tracker.c

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@
2525

2626
#define SURVIVE_MODEL_MAX_STATE_CNT (sizeof(SurviveKalmanModel) / sizeof(FLT))
2727

28+
// @spec TE-PROC-039
29+
// Consecutive per-LH gate rejections before the lc-gate force-accepts a batch
30+
// to break the light_residuals_all EWMA deadlock (see lc-gate comment below).
31+
#define LIGHT_GATE_MAX_CONSECUTIVE_DROPS 5
32+
2833
// clang-format off
2934
STRUCT_CONFIG_SECTION(SurviveKalmanTracker)
3035
STRUCT_CONFIG_ITEM("light-error-threshold", "Error limit to invalidate position",
@@ -490,6 +495,16 @@ void survive_kalman_tracker_integrate_saved_light(SurviveKalmanTracker *tracker,
490495
// cutoff 5*~0 ≈ 0 drops everything, preventing recovery. With the floor
491496
// the minimum cutoff is 5*1e-3 = 0.005, safely above clean tracking
492497
// batch RMS (~0.001 typical) and well below reflection bursts (~0.04+).
498+
//
499+
// light_residuals_all only updates from accepted batches, so a batch
500+
// rejected by the gate can never loosen the gate that rejected it. If
501+
// a newly-joining lighthouse (or any state perturbation) pushes every
502+
// LH's RMS above the cutoff at once, the gate and the EWMA it depends
503+
// on deadlock permanently — observed in production when a 2nd/3rd
504+
// lighthouse came online after tracking had converged on one LH alone.
505+
// LIGHT_GATE_MAX_CONSECUTIVE_DROPS force-accepts a batch after this
506+
// many consecutive rejections per LH, so light_residuals_all updates
507+
// and the gate can re-calibrate instead of starving forever.
493508
if (tracker->light_outlier_threshold > 0) {
494509
CN_CREATE_STACK_VEC(y_dry, cnt);
495510
bool dry_ok = map_light_data(&cbctx, &Z, &tracker->model.state, &y_dry, NULL);
@@ -500,15 +515,22 @@ void survive_kalman_tracker_integrate_saved_light(SurviveKalmanTracker *tracker,
500515
FLT rms = FLT_SQRT(sq / cnt);
501516
FLT effective_mean = linmath_max(tracker->light_residuals_all, 1e-3);
502517
if (rms > tracker->light_outlier_threshold * effective_mean) {
503-
SV_INFO("lc-gate: dropping LH%d batch rms=%.4f > %.1f*mean=%.4f for %s",
504-
lh, rms, tracker->light_outlier_threshold,
505-
effective_mean,
518+
if (tracker->light_gate_consecutive_drops[lh] < LIGHT_GATE_MAX_CONSECUTIVE_DROPS) {
519+
tracker->light_gate_consecutive_drops[lh]++;
520+
SV_INFO("lc-gate: dropping LH%d batch rms=%.4f > %.1f*mean=%.4f for %s",
521+
lh, rms, tracker->light_outlier_threshold,
522+
effective_mean,
523+
survive_colorize_codename(tracker->so));
524+
tracker->stats.lightcap_model_dropped++;
525+
continue;
526+
}
527+
SV_INFO("lc-gate: force-accepting LH%d batch rms=%.4f after %d consecutive drops for %s",
528+
lh, rms, tracker->light_gate_consecutive_drops[lh],
506529
survive_colorize_codename(tracker->so));
507-
tracker->stats.lightcap_model_dropped++;
508-
continue;
509530
}
510531
}
511532
}
533+
tracker->light_gate_consecutive_drops[lh] = 0;
512534

513535
SurviveObject *so = tracker->so;
514536

src/survive_kalman_tracker.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ typedef struct SurviveKalmanTracker {
138138
FLT imu_residuals;
139139
FLT light_residuals_all;
140140
FLT light_residuals[NUM_GEN2_LIGHTHOUSES];
141+
uint32_t light_gate_consecutive_drops[NUM_GEN2_LIGHTHOUSES];
141142

142143
FLT Obs_R[7 * 7];
143144
FLT IMU_R[6 * 6];

0 commit comments

Comments
 (0)