summaryrefslogtreecommitdiff
path: root/test-runner/src/test
diff options
context:
space:
mode:
authorClaude <noreply@anthropic.com>2026-05-20 22:18:38 +0000
committerClaude <noreply@anthropic.com>2026-05-20 22:18:38 +0000
commit62cc93086498d57ef7298eddb40cc382dfc10c6d (patch)
tree5203af49d29d97a7109909ad905fae1fa9a668e8 /test-runner/src/test
parent494e2f0f9ff3e89d1ff7409da096efee57be08b7 (diff)
Fix Long overflow in TackDetector: var lastTackMs = Long.MIN_VALUE caused subtraction overflow, filtering every tack
Diffstat (limited to 'test-runner/src/test')
-rw-r--r--test-runner/src/test/kotlin/org/terst/nav/track/TackDetectorTest.kt98
1 files changed, 58 insertions, 40 deletions
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 44bd667..540683d 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
@@ -5,24 +5,29 @@ import org.junit.Test
class TackDetectorTest {
- private fun pt(lat: Double, lon: Double, cog: Double, ts: Long) =
- TrackPoint(lat, lon, 5.0, cog, 12.0, 0.0, true, ts)
-
- private fun straightTrack(cog: Double, count: Int = 10): List<TrackPoint> =
- (0 until count).map { i -> pt(37.0 + i * 0.01, -122.0, cog, i * 10_000L) }
-
- private fun tackTrack(cogBefore: Double, cogAfter: Double, each: Int = 6): List<TrackPoint> {
- val before = (0 until each).map { i -> pt(37.0 + i * 0.005, -122.0, cogBefore, i * 10_000L) }
- val after = (0 until each).map { i -> pt(37.0 + each * 0.005 + i * 0.005, -122.0, cogAfter, (each + i) * 10_000L) }
- return before + after
+ // Build a track that's well past the 2-minute cold-start skip.
+ // Use 30s spacing: 6 warmup points (3 min) + tack points.
+ private val STEP_MS = 30_000L // 30 s between points
+
+ private fun warmupPoints(cog: Double, count: Int = 5): List<TrackPoint> =
+ (0 until count).map { i ->
+ TrackPoint(37.0 + i * 0.005, -122.0, 5.0, cog, 12.0, 0.0, true, i * STEP_MS)
+ }
+
+ private fun tackTrack(cogBefore: Double, cogAfter: Double, eachLeg: Int = 5): List<TrackPoint> {
+ val warmup = warmupPoints(cogBefore) // 5 pts × 30s = 2.5 min, tack at ~2.5+ min
+ val after = (0 until eachLeg).map { i ->
+ TrackPoint(37.0 + (warmup.size + i) * 0.005, -122.0, 5.0, cogAfter, 12.0, 0.0, true,
+ (warmup.size + i) * STEP_MS)
+ }
+ return warmup + after
}
@Test fun `straight course yields no tacks`() {
- assertTrue(TackDetector.detectTacks(straightTrack(90.0)).isEmpty())
- }
-
- @Test fun `straight course on port tack yields no tacks`() {
- assertTrue(TackDetector.detectTacks(straightTrack(330.0)).isEmpty())
+ val pts = warmupPoints(90.0) + (5 until 15).map { i ->
+ TrackPoint(37.0 + i * 0.01, -122.0, 5.0, 90.0, 12.0, 0.0, true, i * STEP_MS)
+ }
+ assertTrue(TackDetector.detectTacks(pts).isEmpty())
}
@Test fun `detects port to starboard tack`() {
@@ -33,63 +38,76 @@ class TackDetectorTest {
}
@Test fun `detects starboard to port tack`() {
- val tacks = TackDetector.detectTacks(tackTrack(70.0, 330.0))
- assertEquals(1, tacks.size)
+ assertEquals(1, TackDetector.detectTacks(tackTrack(70.0, 330.0)).size)
}
@Test fun `detects jibe on broad reach`() {
- // broad reach port → starboard: 150° → 210°, delta = 60°
- val tacks = TackDetector.detectTacks(tackTrack(150.0, 250.0))
- assertEquals(1, tacks.size)
+ assertEquals(1, TackDetector.detectTacks(tackTrack(150.0, 250.0)).size)
}
@Test fun `handles 0-360 wrap`() {
- // 350° → 80°: delta = 90° through north
- val tacks = TackDetector.detectTacks(tackTrack(350.0, 80.0))
- assertEquals(1, tacks.size)
+ assertEquals(1, TackDetector.detectTacks(tackTrack(350.0, 80.0)).size)
}
@Test fun `gradual course change is not a tack`() {
- // COG increases by 5° per point: window delta ≈ 15°, below threshold
- val points = (0 until 20).map { i -> pt(37.0 + i * 0.01, -122.0, i * 5.0, i * 10_000L) }
- assertTrue(TackDetector.detectTacks(points).isEmpty())
+ val pts = (0 until 30).map { i ->
+ TrackPoint(37.0 + i * 0.01, -122.0, 5.0, i * 5.0, 12.0, 0.0, true, i * STEP_MS)
+ }
+ assertTrue(TackDetector.detectTacks(pts).isEmpty())
}
@Test fun `small heading change below threshold is ignored`() {
- // 45° change — not a tack
assertTrue(TackDetector.detectTacks(tackTrack(90.0, 135.0)).isEmpty())
}
@Test fun `very large heading change above threshold is ignored`() {
- // 160° delta — U-turn, not a normal tack or jibe
assertTrue(TackDetector.detectTacks(tackTrack(90.0, 250.0)).isEmpty())
}
@Test fun `detects two tacks in sequence`() {
- val leg1 = (0 until 6).map { i -> pt(37.0 + i * 0.01, -122.0, 330.0, i * 10_000L) }
- val leg2 = (0 until 6).map { i -> pt(37.06 + i * 0.01, -122.0, 70.0, (6 + i) * 10_000L) }
- val leg3 = (0 until 6).map { i -> pt(37.12 + i * 0.01, -122.0, 330.0, (12 + i) * 10_000L) }
- val tacks = TackDetector.detectTacks(leg1 + leg2 + leg3)
- assertEquals(2, tacks.size)
+ // 5 pts warmup + 5 pts leg2 + 5 pts leg3; gaps of 5*30s=150s > MIN_GAP_MS=45s
+ val warmup = warmupPoints(330.0)
+ val leg2 = (5 until 10).map { i ->
+ TrackPoint(37.0 + i * 0.005, -122.0, 5.0, 70.0, 12.0, 0.0, true, i * STEP_MS)
+ }
+ val leg3 = (10 until 15).map { i ->
+ TrackPoint(37.0 + i * 0.005, -122.0, 5.0, 330.0, 12.0, 0.0, true, i * STEP_MS)
+ }
+ assertEquals(2, TackDetector.detectTacks(warmup + leg2 + leg3).size)
}
- @Test fun `deduplicates tacks at same transition`() {
- val before = (0 until 8).map { i -> pt(37.0, -122.0 + i * 0.0001, 330.0, i * 5_000L) }
- val after = (0 until 8).map { i -> pt(37.0, -122.0 + 0.0008 + i * 0.0001, 70.0, (8 + i) * 5_000L) }
- val tacks = TackDetector.detectTacks(before + after)
+ @Test fun `deduplicates tacks within MIN_GAP_MS`() {
+ // Both tack candidates at i=5 and i=6 are within 30s of each other — only 1 should register
+ val warmup = warmupPoints(330.0)
+ val after = (5 until 15).map { i ->
+ TrackPoint(37.0 + i * 0.005, -122.0, 5.0, 70.0, 12.0, 0.0, true, i * STEP_MS)
+ }
+ val tacks = TackDetector.detectTacks(warmup + after)
assertEquals(1, tacks.size)
}
@Test fun `tack event contains correct position`() {
val tacks = TackDetector.detectTacks(tackTrack(330.0, 70.0))
assertEquals(1, tacks.size)
- // Position should be somewhere in the track bounds
assertTrue(tacks[0].lat in 37.0..37.1)
assertTrue(tacks[0].lon in -122.1..-122.0)
}
@Test fun `too few points returns empty`() {
- val points = (0 until 3).map { i -> pt(37.0, -122.0, 90.0, i * 1000L) }
- assertTrue(TackDetector.detectTacks(points).isEmpty())
+ val pts = (0 until 3).map { i ->
+ TrackPoint(37.0, -122.0, 5.0, 90.0, 12.0, 0.0, true, i * STEP_MS)
+ }
+ assertTrue(TackDetector.detectTacks(pts).isEmpty())
+ }
+
+ @Test fun `cold start filter suppresses tacks in first 2 minutes`() {
+ // Tack happens at t=60s from track start → filtered by START_SKIP_MS=120_000
+ val start = 0L
+ val pts = (0 until 6).map { i ->
+ TrackPoint(37.0, -122.0, 5.0, 330.0, 12.0, 0.0, true, start + i * 10_000L)
+ } + (0 until 6).map { i ->
+ TrackPoint(37.0, -122.0, 5.0, 70.0, 12.0, 0.0, true, start + (6 + i) * 10_000L)
+ }
+ assertEquals(0, TackDetector.detectTacks(pts).size)
}
}