web/scripts/apps/inputlen.lua + web/scripts/lua-input.wada run against the
real LuaAppHost.cpp in the web host build: caps().input_len is true,
random(6) stays in 1..6 with every value seen, random(1, 1) is 1,
random(5, 3) is in 3..5, random(1) is 1, random() is a raw word; 250
characters typed into wada.ui.input(..., 300) come back whole on Enter,
100 typed with no max_len come back as 63; the full 32-bit range returns
integers.
The Lua harness mock now mirrors the current host: sys.random with the
new ranges (deterministic xorshift32), caps().input / input_len, and
wada.ui.input with its argument checks, the 63 default, the 1..2000
clamp and one prompt at a time, answered by answer_input(). Scenarios
inputlen_random and inputlen_input run the same app; every store app's
scenarios still pass.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The button names what the next press will do, and relabelling in place is only
safe on firmware that carries the button:set fix. Add wardrive_start_stop
(Start/Stop toggles under caps().panel) and wardrive_start_stop_old_firmware
(the label stays put without it), both scoped to the 1.3 path so 1.1 and 1.2
keep running the utf8 scenario alone.
Red until the fix on top: wardrive_start_stop expects "Stop" while the survey
runs, which the app does not do yet.
Assisted-by: OpenCode:deepseek-v4.1-flash
Signed-off-by: Sebastian Rutofski <kontakt@sebastian-rutofski.de>
Harness: #648 and #607 each declared the map mock's map_obj/map_centers
locals; the second declaration shadowed the first, so reset_world() and the
scenarios after it would never see the map the mock created. One declaration
is kept. tripodometer, protreck and gpscompass harness runs: ALL OK.
apps.json: one protreck row, ver=1.2, requires=sdk_ext (no seed: false)
protreck.lua:
- get_heading(): compute from raw compass x/y via atan(c.y,-c.x); remove
c.hdg (no such field) and GPS course fallback (M9-only)
- tap detection: record press={x,y} on "down", act on "up" only when
distance <= 12 px — swipes no longer trigger waypoint/chrono/timer/alarm
- alarm clock: use sys.datetime() (local time) instead of now_unix()+decompose()
- wada.timer.after: schedule_alarm() fires alarms while screen sleeps, rescheduled daily
- key events: left/right = tab nav, up/down mirror swipe actions
- canvas capped at 480×480 (Tanmatsu compatibility)
harness.lua:
- timer.after mock added
- protreck_tdeck fixture: h=240→196 (T-Deck actual resolution)
- protreck_chrono / protreck_timer / protreck_compass: send down+up tap pairs
- protreck_compass: compass mock returns raw {x,y,z} (not {hdg})
- protreck_deniv: alt_seq reordered so step 9 (first on_tick sample) is the
max value, matching the GPS call order through draw_status_bar
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Builds on 1.0 (already in main). New in 1.1:
- Return to home: records the position of the first GPS fix after Start
and shows straight-line distance + cardinal bearing back to that point
each poll. Home is persisted in wada.store (trip_home_lat_e6/lon_e6)
so reopening mid-trip keeps the original departure point. Clears on Reset.
- Average speed: total_km / elapsed_s × 3600, live tile beside max speed.
- Min altitude: tracked alongside current altitude, persisted via
wada.store ("trip_minalt"). Clears on Reset.
- Speed derivation: when GPS speed_kmh is absent (non-M9 boards) speed is
derived from successive fixes; avg speed stays ≤ max speed by construction.
- Layout proportional to screen height: rows fill the space between the speed
block and the button bar — no overflow on T-Deck (320×240) or V4 (240×276).
- requires: sdk_ext (sys.gps is an ext-SDK function; errors on 2 MB V4 otherwise).
Harness: realistic fixtures (step × 0.0002° ≈ 22 m/2 s ≈ 40 km/h), home
persistence asserted, avg ≤ max by construction in all non-M9 scenarios.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
New version in deploy/apps/protreck/1.2/ (1.0 directory untouched).
- 5 tabs: ASTRO · CMPAS · CHRONO · TIMER · ALTI (was 4)
- Permanent status bar: local time (UTC offset from GPS longitude),
GPS sats, battery % — shown on all tabs
- ASTRO: 3 alarms A1/A2/A3; short tap = cycle slot, long press ≥500ms
= toggle on/off; swipe up/down ±15 min on selected alarm;
civil twilight via generalised sun_utc_dep() formula
- CMPAS (new tab): compass rose with cv:line rotating marks;
sys.compass() or GPS course fallback; tap = save/clear waypoint;
bearing + distance shown on rose
- ALTI: cumulative déniv+/− (0.5m noise threshold), climb rate m/h;
GPS sampling active on all tabs
- Harness: 10 scenarios ALL OK, worst tick ~13k / budget 100k
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Two things made a full harness run untrustworthy, and together they produced a
false accusation against a community app.
wada.fs was stubs. append threw the data away, remove always said true, and
read, write and list were not there at all — although the firmware registers
all five (LuaAppHost.cpp). Any app that writes a file and reads it back
therefore failed here while working perfectly on device. wardrive 1.2 is
exactly that app: it errors on a nil fs.read at its first log flush, long
before it renders anything, and the emoji scenario then reported the repeater
name as unrendered. Read as "1.2 regressed the beta_70 emoji fix", which it
did not. It passes untouched once fs is real.
wada.fs is now an in-memory filesystem following LuaAppHost's semantics: read
returns the data plus the file's TOTAL size so a caller can window a growing
log, an offset at or past the end gives an empty string rather than nil, a
missing file gives nil, and list is sorted so a hash order cannot make a test
flaky.
The second: the gpscompass scenario list was acting as the dispatch default,
so an app with no branch of its own was run against tests written for a
different app. Six store apps have no branch (2048, airtime, monitor, nearby,
sdktest, snake), so a full sweep came back with six red apps as its normal
state — and a real regression would not have stood out against that. Those
apps now say they have no scenarios and run none. They are untested, which is
worth saying out loud, but that is not the same as broken.
A sweep of every store app is now 8 apps passing their own scenarios, 6
reported untested, 0 failures.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Stacked on #585 and merged after it, as the PR asks. Two conflicts, both from
the stack base moving:
apps.json: unioned by id, so all fourteen apps survive.
harness.lua: the additive blocks came from #586. Where both sides edited the
same code, #585's is newer and wins: its ping scenarios pass cfg.gps as a
FUNCTION, which is what the harness calls (cfg.gps()); #586 still passed the
table and would have failed. Taking #586 there would have broken ping.
Then restored the two dispatch branches my first resolution dropped. The
breakout and tripodometer scenarios existed but nothing ran them, so both
apps silently fell through to the gpscompass scenarios and "failed".
All five new apps pass their own scenarios, and so do gpscompass and sdscan.
Sun/Moon rise+set from GPS lat/lon + date (Spencer 1971), moon phase 0-29.
Chronometer with lap times, countdown timer with alarm beep and preset
cycling, altimeter with GPS alt_m, min/max persistence and trend graph.
Swipe left/right to switch tabs. 4 tabs fit any screen size.
test(harness): add 7 ProTreck scenarios (tdeck, v4, no_gps, chrono,
timer, alti, cost). ALL OK 7/7.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Send a DM to any contact, measure round-trip time. Both devices
exchange their GPS coordinates; the result shows RTT, distance and
cardinal bearing when both have a fix. The app also auto-replies to
incoming pings so two devices running Ping can time each other.
Harness additions: send_dm mock, wada.geo (haversine), deliver_dm
helper, and 6 scenarios (tdeck, v4, no_contacts, round_trip,
auto_reply, cost) — all pass.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Tracks total distance (Haversine accumulation), elapsed time, current
speed, max speed, and altitude from live GPS fixes. Start/Stop button
pauses accumulation; Reset clears the trip. The current trip persists
across app restarts via wada.store.
Also adds 6 harness scenarios (no-fix, T-Deck, moving, persist, reset,
cost) and registers the app in apps.json.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Ball + paddle + 8×5 brick grid. Swipe left/right (or arrow keys) to
move the paddle; swipe up / Enter / Space to launch. Angle on bounce
varies by paddle-hit position. Speed increases each level; high score
persists via wada.store.
Adds 6 harness scenarios (tdeck, v4, keyboard, logic, hiscore, cost).
Worst tick ~8k instructions, 8% of the 100k budget.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Detects /tetris/ in the app path and routes to 6 dedicated scenarios:
- tetris_tdeck/v4: open + swipe input on touch screens
- tetris_keyboard: arrow + enter input on keyboard boards
- tetris_logic: hard-drop to game-over, verify restart resets timer to 700 ms
- tetris_hiscore: verify high score is stored as a number after game-over
- tetris_cost: 100 ticks, worst ~6000 instructions (budget 100000)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
An infected M9 card turned up with 373 findings. Every list row is a focus
stop on a keyboard board, and the firmware's key navigation collects at most
160 of them per screen, so in 1.0 the Remove all and Scan again buttons below
the list could not be reached on an M9 at all.
1.1 puts the buttons above the list, first in focus order, on the results
and failed-removal screens, and shows at most 60 rows plus an "and N more"
row (the title carries the full count). App-only: no firmware change.
Harness: a 373-finding scenario, and a check on every list screen that the
buttons come first and the focus stops stay under 160. It fails on 1.0.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Some ThinkNode M9 cards shipped with a dormant Windows worm (Elecrow
security advisory, September 2026). An infected card seen since carries
autorun.inf in the root, launching xlfqf.pif on open, explore and autoplay
with random-junk comment lines in between: the Sality autorun pattern.
- SD Scan store app (deploy/apps/sdscan/1.0, requires "sd", not seeded):
walks the card a small page per tick, lists what it finds and why, and
removes it after a confirmation screen with Cancel first. It says on
every screen that it only removes files it recognises and that
formatting the card is the safe fix. On older firmware it still finds
threats by name but cannot remove them.
- Firmware: wada.sd.check(path) and wada.sd.remove(path), plus paging for
wada.sd.list(path, start, max) and caps().sd_clean. What counts as a
threat lives in SdThreat.h: autorun.inf, Windows program, script and
shortcut extensions, or a real MZ+PE header under any name. remove()
classifies again in firmware and refuses anything else, so no app can
use it to delete tiles, backups or chat history. It clears read-only,
hidden and system first, because FAT refuses to delete a read-only file.
- A warning when a card with Windows malware in its top folder is mounted,
at boot or on insert, offering SD Scan (or the Store).
- Tests: test/test_sd_threat.cpp, and SD Scan harness scenarios including
the real infected card's root. Removal checked on a T-Deck.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
oumike. Closes#315 (pisti87's request).
Two conflicts, both resolved by keeping BOTH sides rather than choosing:
- sdRuntimeLifecycleBusy() gained an audio-playback source here and a web
reader source in #317. They are independent consumers of the same card and
both have to gate the mount lifecycle. The reader's self-exclusion is kept:
it calls this from its own task while holding the card and would otherwise
deadlock against itself.
- The Lua harness caps table needed sd_list from #312 as well as the audio
flags, and the test order needed the wardrive suite from #324 as well as
audio_api.
Built on all eight S3 envs and both ESP32-P4 targets.
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.