summaryrefslogtreecommitdiff
path: root/scripts/.claude/ct-wind-wiring.txt
diff options
context:
space:
mode:
authorClaude <noreply@anthropic.com>2026-04-23 23:10:11 +0000
committerClaude <noreply@anthropic.com>2026-04-23 23:10:11 +0000
commite1db9200e9d44c10450361cc8984a45b2eda87b7 (patch)
tree21bdfca02491e6ff4cde7a22673c821c0a03e108 /scripts/.claude/ct-wind-wiring.txt
parent55509f579b4d074f01237dd90791b6d25aaec3cd (diff)
parenteb78d317c722234a7ef2c501c68c9aa730ec2758 (diff)
Merge origin/main — unite all work onto one branch
Brings in dev log (NavLogger), UnitPrefs, MapLayerManager, HUD views, conditions throttling, track save/load pipeline, improved ParticleWindView (antimeridian-aware, dynamic particle count), Snackbar error surfacing, and all other main-branch work from the prior session. Combined with master's hardware source flags, vessel registry, crew management, thermal alarm, CPA collision alerts, and track stats. Also documents primary branch policy in CLAUDE.md and .agent/config.md: all work merges to main, never master. https://claude.ai/code/session_011h2dXbgXg3PesQMmQUNTCW
Diffstat (limited to 'scripts/.claude/ct-wind-wiring.txt')
-rw-r--r--scripts/.claude/ct-wind-wiring.txt156
1 files changed, 156 insertions, 0 deletions
diff --git a/scripts/.claude/ct-wind-wiring.txt b/scripts/.claude/ct-wind-wiring.txt
new file mode 100644
index 0000000..09768ab
--- /dev/null
+++ b/scripts/.claude/ct-wind-wiring.txt
@@ -0,0 +1,156 @@
+Context
+-------
+MainViewModel.addGpsPoint() (android-app/app/src/main/kotlin/org/terst/nav/ui/MainViewModel.kt,
+line 67) builds TrackPoint with hardcoded wind zeroes:
+
+ windSpeedKnots = 0.0, windAngleDeg = 0.0, isTrueWind = false
+
+LocationService already exposes live NMEA wind data:
+ LocationService.nmeaWindDataFlow: SharedFlow<WindData> (companion object, line 362)
+
+WindData (android-app/app/src/main/kotlin/org/terst/nav/sensors/WindData.kt):
+ windAngle: Double // degrees, relative or true
+ windSpeed: Double // knots
+ isTrueWind: Boolean
+ timestampMs: Long
+
+TrackPoint (android-app/app/src/main/kotlin/org/terst/nav/track/TrackPoint.kt) already has
+windSpeedKnots, windAngleDeg, isTrueWind fields — they just aren't populated.
+
+Goal
+----
+Cache the latest WindData in MainViewModel and use it when building TrackPoints.
+Default to zero wind if no NMEA wind sentence has arrived yet.
+
+Step 1 — Red: write failing tests
+----------------------------------
+Create android-app/app/src/test/kotlin/org/terst/nav/ui/MainViewModelWindTest.kt
+
+Setup boilerplate needed (Dispatchers.setMain / resetMain with UnconfinedTestDispatcher):
+
+ import androidx.arch.core.executor.testing.InstantTaskExecutorRule
+ import kotlinx.coroutines.Dispatchers
+ import kotlinx.coroutines.ExperimentalCoroutinesApi
+ import kotlinx.coroutines.test.UnconfinedTestDispatcher
+ import kotlinx.coroutines.test.resetMain
+ import kotlinx.coroutines.test.runTest
+ import kotlinx.coroutines.test.setMain
+ import org.junit.After
+ import org.junit.Assert.assertEquals
+ import org.junit.Before
+ import org.junit.Rule
+ import org.junit.Test
+ import org.terst.nav.sensors.WindData
+ import org.terst.nav.ui.MainViewModel
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ class MainViewModelWindTest {
+
+ @get:Rule val instantTask = InstantTaskExecutorRule()
+ private val dispatcher = UnconfinedTestDispatcher()
+
+ @Before fun setUp() { Dispatchers.setMain(dispatcher) }
+ @After fun tearDown() { Dispatchers.resetMain() }
+
+ @Test fun `addGpsPoint uses zero wind when no wind update received`() = runTest {
+ val vm = MainViewModel()
+ vm.startTrack()
+ vm.addGpsPoint(37.0, -122.0, 5.0, 90.0)
+ val pt = vm.trackPoints.value.first()
+ assertEquals(0.0, pt.windSpeedKnots, 0.001)
+ assertEquals(0.0, pt.windAngleDeg, 0.001)
+ assertEquals(false, pt.isTrueWind)
+ }
+
+ @Test fun `addGpsPoint uses latest wind data after updateWind`() = runTest {
+ val vm = MainViewModel()
+ vm.updateWind(WindData(windAngle = 45.0, windSpeed = 15.5, isTrueWind = true, timestampMs = 1000L))
+ vm.startTrack()
+ vm.addGpsPoint(37.0, -122.0, 5.0, 90.0)
+ val pt = vm.trackPoints.value.first()
+ assertEquals(15.5, pt.windSpeedKnots, 0.001)
+ assertEquals(45.0, pt.windAngleDeg, 0.001)
+ assertEquals(true, pt.isTrueWind)
+ }
+
+ @Test fun `addGpsPoint reflects wind update between fixes`() = runTest {
+ val vm = MainViewModel()
+ vm.startTrack()
+ vm.addGpsPoint(37.0, -122.0, 5.0, 90.0)
+ vm.updateWind(WindData(windAngle = 180.0, windSpeed = 8.0, isTrueWind = false, timestampMs = 2000L))
+ vm.addGpsPoint(37.1, -122.0, 5.0, 90.0)
+ val pts = vm.trackPoints.value
+ assertEquals(0.0, pts[0].windSpeedKnots, 0.001)
+ assertEquals(8.0, pts[1].windSpeedKnots, 0.001)
+ }
+ }
+
+Run and confirm RED:
+ cd android-app && ./gradlew :app:testDebugUnitTest --tests "*MainViewModelWind*"
+
+Step 2 — Green: implement
+--------------------------
+In android-app/app/src/main/kotlin/org/terst/nav/ui/MainViewModel.kt:
+
+1. Add import:
+ import org.terst.nav.sensors.WindData
+
+2. Add field after trackRepository:
+ private var latestWind: WindData? = null
+
+3. Add method:
+ fun updateWind(wind: WindData) {
+ latestWind = wind
+ }
+
+4. Update addGpsPoint() to use latestWind:
+ fun addGpsPoint(lat: Double, lon: Double, sogKnots: Double, cogDeg: Double) {
+ val wind = latestWind
+ val point = TrackPoint(
+ lat = lat, lon = lon,
+ sogKnots = sogKnots, cogDeg = cogDeg,
+ windSpeedKnots = wind?.windSpeed ?: 0.0,
+ windAngleDeg = wind?.windAngle ?: 0.0,
+ isTrueWind = wind?.isTrueWind ?: false,
+ timestampMs = System.currentTimeMillis()
+ )
+ if (trackRepository.addPoint(point)) {
+ _trackPoints.value = trackRepository.getPoints()
+ }
+ }
+
+Run and confirm GREEN:
+ cd android-app && ./gradlew :app:testDebugUnitTest --tests "*MainViewModelWind*"
+
+Step 3 — Wire: feed wind updates from LocationService
+------------------------------------------------------
+In android-app/app/src/main/kotlin/org/terst/nav/MainActivity.kt,
+inside observeDataSources(), add after the anchorWatchState collector:
+
+ lifecycleScope.launch {
+ LocationService.nmeaWindDataFlow.collect { wind ->
+ viewModel.updateWind(wind)
+ }
+ }
+
+No additional imports needed (LocationService is already imported).
+
+Step 4 — Verify all tests pass
+-------------------------------
+cd android-app && ./gradlew :app:testDebugUnitTest
+
+Confirm all existing tests still pass alongside the 3 new wind tests.
+
+Step 5 — Commit and push
+-------------------------
+git add -A && git commit -m "feat(track): wire NMEA wind data into GPS track points
+
+MainViewModel caches the latest WindData from LocationService.nmeaWindDataFlow
+via updateWind(). addGpsPoint() populates TrackPoint wind fields from the cache,
+defaulting to zero if no NMEA wind sentence has arrived yet.
+
+MainActivity.observeDataSources() feeds LocationService.nmeaWindDataFlow
+into viewModel.updateWind() alongside the existing GPS and anchor observers.
+
+3 new unit tests in MainViewModelWindTest verify zero-default, wind capture,
+and mid-track wind update behaviour." && git push origin main