The declination model landed as 4.7 KB of constants pasted into a Lua app,
generated by a script that lived in out/ -- which is gitignored, holds firmware
bins, and is where the app's own "Regenerate:" comment pointed. So the pointer
dangled for anyone who cloned the repo, and nobody but me could answer the
first fair question a reviewer would ask about that block of magic numbers:
where did it come from, and how do I know it is right.
scripts/wmm/ WMM.COF + NOAA's 100 official test values (both upstream
and unmodified), the float64 reference, the generator,
verify.py, and a README covering provenance, regeneration
and how to move to WMM2030.
scripts/lua-harness/ the host harness, with run.sh so it is one command.
Neither goes in test/: that is PlatformIO's directory and a harness with a
main.c would be swept into `pio test`. scripts/ already holds this repo's dev
tooling, test_companion_serial.py included.
The block in the app is now genuinely generated rather than hand-pasted:
scripts/wmm/gen_lua.py --update <app> rewrite it
scripts/wmm/gen_lua.py --check <app> fail, with a diff, if it drifted
--check catches coefficients updated without regenerating, or a block edited by
hand. The generator owns the `local declination / do ... end` wrapper too, and
that is the point: the tables are named G/H/GD/HD, gpscompass uses a global H
for the screen height, and an unscoped `local H` silently ate it. Hand-wrapping
is how that happened, so hand-wrapping is now not a step.
Verification, all reproducible from a clean clone:
scripts/wmm/verify.py 100 NOAA values, worst D error 0.005 deg
scripts/lua-harness/run.sh 10 scenarios, incl. the generated Lua in
the device's own LUA_32BITS interpreter --
0.0002 deg vs NOAA, worst tick 12k of 100k
Also refreshes the LUA_APPS.md paragraph, which still advertised the O and F
keys that were removed and quoted harness numbers from before tilt
compensation.
3.3 KiB
Host harness for Wadamesh Lua apps
Runs a Store app on your desktop, against a mock of the wada.* host, before
it ever touches a device. Not part of the firmware build.
scripts/lua-harness/run.sh # gpscompass, all scenarios
scripts/lua-harness/run.sh deploy/apps/wardrive/1.0/wardrive.lua
SCENARIO=declination scripts/lua-harness/run.sh # one scenario
run.sh compiles luah on first use (and whenever main.c changes) from the
Lua sources already vendored in lib/lua/src — no dependency beyond a C
compiler.
What makes it worth running
It is built from the same vendored Lua 5.4.7 as the firmware, with the same
LUA_32BITS numeric model — 32-bit integers, single-precision floats. That
is the point. Maths that behaves in a desktop float64 interpreter can lose
catastrophic precision on the device, and this is where that shows up.
The mock mirrors LuaAppHost.cpp rather than being convenient: the same
argument type-checks, the same 100k-instruction budget per callback, the same
event shapes, and the same store restrictions. That has caught real defects —
the device store accepts strings and numbers only, and a boolean flag thrown
inside a guarded callback looks exactly like a keypress doing nothing at all.
Device attitude is simulated physically, not faked: a magnetic field of the right magnitude and dip, rotated into the body frame by heading/roll/pitch, plus the hard-iron and zero-g biases measured on a real M9. So calibration, tilt compensation and heading all get exercised for real.
Scenarios
harness.lua holds them all. Board shapes — M9 320x196 landscape, V4/R8
portrait, base-SDK V4 (no extended SDK), Pager, Pager portrait at Jumbo font
sizes, Tanmatsu — plus an instruction-cost probe that fails the run if any tick
exceeds a quarter of the budget.
Three are about the compass being correct rather than merely working:
declination— pulls the generated WMM block straight out of the app file and checks it against NOAA reference values at six sites, including a weak-field one near the pole. Testing the shipped bytes, not a copy, so a bad regeneration fails here rather than on hardware. Seescripts/wmm/.bearings_absolute— contacts placed due N/E/S/W and NE of the fix, to check bearings against absolute compass directions. The older marker test compares each drawn dot against the app's own bearing, which stays self-consistent even if east and west are swapped; this one would not. The north-east case is what separates a mirror from a lat/lon transpose (38.3° vs 51.7°).align_nofix— "set north" pressed with no position at all. The offset it stores silently swallows the whole declination, so the app has to give it back when the first fix lands, or the heading is wrong by twice it.
Reading a failure
Scenarios print what they saw before asserting, so a failure usually names the cause. Two conventions worth knowing:
- The dial centre is read out of the canvas draw order: digits, degree sign, T/M reference, cardinal. Add anything to that sequence and the index offsets in the assertions move with it.
APP ERROR:means the app raised inside a guarded callback — the harness keeps going, and the scenario fails later on a symptom. The firstAPP ERRORline is the real one.