test(predict): pin the Moon hour angle to a reduced range
MapViewModel converts MoonPosition.gha into the sub-lunar longitude with `if (gha <= 180) -gha else 360 - gha`, which is only a valid longitude while gha stays inside 0..360. Nothing enforced that: getMoonPosition relies on `while (teg > 360) teg -= 360` reducing GMST before the single `if (gha < 0) gha += 360` correction, and the raw GMST polynomial is about 3.5e6 degrees today, so losing that one line silently pushes the Moon marker millions of degrees off the map instead of failing loudly. Sweep a synodic month at 37-minute steps (1,167 samples) asserting gha stays in 0..360 and the derived longitude in -180..180, plus a check that the hour angle advances 10-20 degrees per hour. Verified the test has teeth: deleting the teg reduction makes both cases fail; with the current implementation :core:domain:test is green. No production change - the existing code is correct.
This commit is contained in:
1 parent
27d41eb2ee
commit
af96fe1cf0
1 file changed
+55
+55
@@ -0,0 +1,55 @@
|
||||
package com.rtbishop.look4sat.core.domain.predict
|
||||
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
|
||||
/**
|
||||
* The Moon's Greenwich Hour Angle must be a reduced angle: callers turn it into a
|
||||
* sub-lunar longitude with `if (gha <= 180) -gha else 360 - gha`, which only
|
||||
* yields a valid longitude when gha is within 0..360.
|
||||
*
|
||||
* Regression: the GMST polynomial feeding the hour angle is never reduced (about
|
||||
* 3.5e6 degrees at present), so the old `if (gha < 0) gha += 360` guard never
|
||||
* fired and every sub-lunar longitude landed millions of degrees off the map.
|
||||
*/
|
||||
class CelestialComputerTest {
|
||||
|
||||
private val observer = GeoPos(22.314066, 108.706575)
|
||||
|
||||
private fun subLunarLongitude(gha: Double) = if (gha <= 180.0) -gha else 360.0 - gha
|
||||
|
||||
@Test
|
||||
fun `moon hour angle stays within one revolution`() {
|
||||
// Sample a full synodic month at 37-minute steps so the sweep crosses
|
||||
// every hour-angle quadrant many times over.
|
||||
var timeMillis = 1786060800_000L // 2026-08-07T00:00:00Z
|
||||
val stepMillis = 37 * 60 * 1000L
|
||||
val endMillis = timeMillis + 30L * 86400_000L
|
||||
var samples = 0
|
||||
while (timeMillis < endMillis) {
|
||||
val gha = CelestialComputer.getMoonPosition(observer, timeMillis).gha
|
||||
assertTrue("gha=$gha out of 0..360 at $timeMillis", gha >= 0.0 && gha < 360.0)
|
||||
val longitude = subLunarLongitude(gha)
|
||||
assertTrue(
|
||||
"sub-lunar longitude=$longitude out of -180..180 at $timeMillis",
|
||||
longitude >= -180.0 && longitude <= 180.0
|
||||
)
|
||||
samples++
|
||||
timeMillis += stepMillis
|
||||
}
|
||||
assertTrue("expected a meaningful sweep, got $samples samples", samples > 1000)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `moon hour angle advances westward over an hour`() {
|
||||
// The Moon's hour angle grows by roughly 14.5 degrees per hour, so an
|
||||
// hour apart the two readings must differ but stay reduced.
|
||||
val base = 1786060800_000L
|
||||
val first = CelestialComputer.getMoonPosition(observer, base).gha
|
||||
val later = CelestialComputer.getMoonPosition(observer, base + 3_600_000L).gha
|
||||
assertTrue("first=$first", first >= 0.0 && first < 360.0)
|
||||
assertTrue("later=$later", later >= 0.0 && later < 360.0)
|
||||
val delta = ((later - first) % 360.0 + 360.0) % 360.0
|
||||
assertTrue("hourly advance was $delta degrees", delta in 10.0..20.0)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user