Project

General

Profile

Task #19 » 0001-test-ekf-bracket-the-ZARU-rest-gate-with-an-above-th.patch

knight8241, 08/07/2026 18:31

View differences:

common/src/commonTest/kotlin/com/aether/mofe/engine/ImuFusionEKFTest.kt
"ZARU should drive the gyro-bias estimate to the resting reading (~0.05). Got: $bg")
}
@Test
fun gyroBiasAboveRestThresholdReadsMovingSoZaruCannotAnchorYaw() {
// Rest-gate BOUNDARY — the on-device diagnostic tree's "motionMode ≠ STATIONARY"
// branch. ZARU only fires while the RAW-gyro rest gate reads the device as still. A
// standing gyro bias that EXCEEDS zuptGyroThreshold makes the raw rate itself look
// like motion, so the device reads MOVING, ZARU never engages, and the bias integrates
// into an ever-growing heading — the regime where only an absolute AoA reference can
// recover yaw. Complements zaruStopsYawDriftWhenStationaryFlat (a bias BELOW the gate,
// which ZARU arrests); together they bracket the gate. Bias is derived from the config
// so the test tracks the threshold rather than hard-coding it.
val cfg = EkfConfig() // zaruEnabled = true (default)
val biasZ = cfg.zuptGyroThreshold + 0.08 // rad/s, safely ABOVE the rest gate
val e = ImuFusionEKF(TARGET_1, cfg)
e.update(PositionSolution(TARGET_1, Timestamp(0), Vector3D(0.0, 0.0, 0.0),
Matrix.diagonal(0.001, 0.001, 0.001), 2.0, true))
var t = 0L
var facingAt150: Vector3D? = null
var facingLast = Vector3D(0.0, 1.0, 0.0)
repeat(250) { i ->
t += 10_000 // 100 Hz
e.predict(ImuSample(Timestamp(t), Vector3D(0.0, 0.0, 9.81), Vector3D(0.0, 0.0, biasZ)))
if (i == 149) facingAt150 = facingOf(e)
if (i >= 149) facingLast = facingOf(e)
}
// Raw rate exceeds the gate → never classified at rest → ZARU disarmed.
assertEquals(MotionMode.MOVING, e.motionMode,
"a gyro bias above zuptGyroThreshold must read MOVING, holding ZARU off")
// With ZARU disarmed the standing bias integrates → the heading keeps walking.
val drift = angle(facingAt150!!, facingLast)
assertTrue(drift > 0.03,
"with the rest gate holding ZARU off, a standing gyro bias must walk the heading (got $drift rad)")
}
// ── Motion mode + trust-gated ZUPT (mesh gear-shift) ────────────────────────
@Test
    (1-1/1)