Auto commit Sun Mar 22 12:01:01 AM CDT 2026

This commit is contained in:
Tretzi
2026-03-22 00:01:01 -05:00
parent 869c98a47c
commit 88723bf214
10 changed files with 226 additions and 56 deletions

View File

@@ -171,7 +171,7 @@ class TimelineCurveGestureTest {
}
@Test
fun `ensureMinutePoints snaps points to minute boundaries`() {
fun `ensureTimelinePoints snaps points to selected boundaries`() {
val points = listOf(
TimelinePoint(0, 0.0f),
TimelinePoint(30, 0.5f), // 30 seconds - not a minute boundary
@@ -179,26 +179,26 @@ class TimelineCurveGestureTest {
)
val curve = TimelineCurve(points)
val result = curve.ensureMinutePoints(180)
val result = curve.ensureTimelinePoints(180, granularitySec = 30)
// Check that all points are on minute boundaries (multiples of 60)
// Check that all points are on selected boundaries (multiples of 30)
result.points.forEach { point ->
assertTrue("Point at ${point.tSec} should be on minute boundary", point.tSec % 60 == 0)
assertTrue("Point at ${point.tSec} should be on 30s boundary", point.tSec % 30 == 0)
}
// Should have points at 0, 60, 120, 180 seconds
assertEquals(4, result.points.size)
// Should have points at 0, 30, 60, 90, 120, 150, 180 seconds
assertEquals(7, result.points.size)
}
@Test
fun `ensureMinutePoints preserves interpolated values`() {
fun `ensureTimelinePoints preserves interpolated values`() {
val points = listOf(
TimelinePoint(0, 0.0f),
TimelinePoint(120, 1.0f),
)
val curve = TimelineCurve(points)
val result = curve.ensureMinutePoints(180)
val result = curve.ensureTimelinePoints(180, granularitySec = 60)
// Value at 60 seconds should interpolate to 0.5
val valueAt60 = result.valueAt(60f)
@@ -209,7 +209,7 @@ class TimelineCurveGestureTest {
fun `extendTo adds points for extended duration`() {
val curve = TimelineCurve.constant(0.5f, 60)
val extended = curve.extendTo(120)
val extended = curve.extendTo(120, granularitySec = 60)
// Should have points covering up to 120 seconds
assertTrue(extended.points.any { it.tSec == 120 })

View File

@@ -1,6 +1,7 @@
package com.mindmachine.mvp
import com.mindmachine.mvp.session.FlashColor
import com.mindmachine.mvp.session.SplitFlashSequencer
import com.mindmachine.mvp.session.computeSplitFlashFrame
import org.junit.Assert.assertEquals
import org.junit.Test
@@ -39,4 +40,37 @@ class VisualSignalTest {
val validAt2000ms = computeSplitFlashFrame(frameTimeNanos = 2_100_000_000L, flashOnMs = 2000, flashOffMs = 2000)
assertEquals(validAt2000ms, tooSlow)
}
@Test
fun sequencer_keeps_blank_duration_constant_when_flash_duration_changes_mid_blank() {
val sequencer = SplitFlashSequencer(startTimeNanos = 0L, flashOnMs = 100, flashOffMs = 1000)
assertEquals(FlashColor.BLACK, sequencer.frameAt(100_000_000L).left)
sequencer.updateIntervals(flashOnMs = 200, flashOffMs = 1000)
// Still blank before the configured 1000ms blank phase is complete.
assertEquals(FlashColor.BLACK, sequencer.frameAt(600_000_000L).left)
assertEquals(FlashColor.BLACK, sequencer.frameAt(1_099_000_000L).left)
// At 1100ms total, blank ends and next on-phase starts.
assertEquals(FlashColor.GREEN, sequencer.frameAt(1_100_000_000L).left)
}
@Test
fun sequencer_applies_new_blank_interval_on_next_blank_boundary() {
val sequencer = SplitFlashSequencer(startTimeNanos = 0L, flashOnMs = 100, flashOffMs = 100)
// Enter second on phase.
assertEquals(FlashColor.GREEN, sequencer.frameAt(200_000_000L).left)
// Change blank interval during on phase; current on should finish uninterrupted.
sequencer.updateIntervals(flashOnMs = 100, flashOffMs = 1000)
assertEquals(FlashColor.GREEN, sequencer.frameAt(250_000_000L).left)
// Next phase is blank and should use new 1000ms off interval.
assertEquals(FlashColor.BLACK, sequencer.frameAt(300_000_000L).left)
assertEquals(FlashColor.BLACK, sequencer.frameAt(1_299_000_000L).left)
assertEquals(FlashColor.RED, sequencer.frameAt(1_300_000_000L).left)
}
}