From 4f87ae6cda6131012447a8b2ddde088bb8844b04 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 27 May 2026 05:38:24 +0000 Subject: Add RACE mode to TackDetector for rapid mark-rounding detection MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Introduces TackMode enum (CRUISING/RACE) and per-mode Config, replacing the previous single set of hardcoded constants. RACE config: T_SETTLE=12s, T_MANEUVER=20s, STAB_MAX=30°, MIN_GAP=20s, START_SKIP=20s — tighter windows for quick successive tacks at race starts and mark roundings. CRUISING remains the default. Two new tests demonstrate the behavioral difference: RACE fires earlier (past 20s cold-start vs 60s) and keeps separate events for a 50s gap that CRUISING deduplicates (50s < 60s MIN_GAP). 18/18 passing. https://claude.ai/code/session_01KXYBuAHZkvv63DeUG6XaZD --- .../kotlin/org/terst/nav/track/TackDetectorTest.kt | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) (limited to 'test-runner/src/test/kotlin') diff --git a/test-runner/src/test/kotlin/org/terst/nav/track/TackDetectorTest.kt b/test-runner/src/test/kotlin/org/terst/nav/track/TackDetectorTest.kt index 265ba76..e3bca9f 100644 --- a/test-runner/src/test/kotlin/org/terst/nav/track/TackDetectorTest.kt +++ b/test-runner/src/test/kotlin/org/terst/nav/track/TackDetectorTest.kt @@ -124,4 +124,26 @@ class TackDetectorTest { val tacks = TackDetector.detectTacks(leg1 + leg2 + leg3) assertEquals(1, tacks.size) } + + // ── Race mode ───────────────────────────────────────────────────────────── + + // RACE config: T_SETTLE=12s, T_MANEUVER=20s, START_SKIP=20s, MIN_GAP=20s, STAB_MAX=30° + // Min apex time in RACE at 1 Hz: START_SKIP(20) + T_SETTLE(12) + T_MANEUVER/2(10) = 42s from t0 + // So leg1=30s (cog A), leg2=30s (cog B): apex candidates fire around t=30s in leg2. + // For t=42 in leg2 (t0=0): before=[42-22..42-10]=[20,32]→in leg1 ✓; after=[52..64]→in leg2 ✓ + + @Test fun `race mode detects tack that falls inside cruising cold-start window`() { + // Tack at t=30s is inside CRUISING START_SKIP=60s but past RACE START_SKIP=20s + val pts = leg(330.0, 30, 0L) + leg(70.0, 30, 30_000L) + assertEquals(1, TackDetector.detectTacks(pts, TackMode.RACE).size) + assertEquals(0, TackDetector.detectTacks(pts, TackMode.CRUISING).size) + } + + @Test fun `race mode yields two events for gap that cruising deduplicates`() { + // Intermediate leg of 50s: gap > RACE MIN_GAP=20s → 2 events + // gap < CRUISING MIN_GAP=60s → 1 event (deduped) + val pts = leg(330.0, 60, 0L) + leg(70.0, 50, 60_000L) + leg(330.0, 60, 110_000L) + assertEquals(2, TackDetector.detectTacks(pts, TackMode.RACE).size) + assertEquals(1, TackDetector.detectTacks(pts, TackMode.CRUISING).size) + } } -- cgit v1.2.3