13 Commits
Author SHA1 Message Date
Kaj SchittecatandClaude Opus 5 66e1f37d2c harness: model the filesystem, and stop apps running other apps' tests
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>
2026-10-04 12:42:12 +02:00
Kaj Schittecat 3171f5adea Merge PR #586 from samuelcoustet: Breakout and Trip Odometer Lua apps
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.
2026-10-03 17:24:15 +02:00
SamandClaude Sonnet 4.6 06f9c6e549 feat(apps): ProTreck 1.0 — outdoor watch Lua app (Astro/Chrono/Timer/Alti)
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>
2026-10-02 22:52:51 +02:00
rootandClaude Sonnet 4.6 7254b9cca6 feat(apps): Ping 1.0 — mesh DM ping with GNSS position and RTT
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>
2026-10-02 22:50:22 +02:00
SamandClaude Sonnet 4.6 9b7ca27747 feat(apps): Trip Odometer 1.0 — GPS trip computer as a Lua app
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>
2026-10-02 22:41:14 +02:00
SamandClaude Sonnet 4.6 ec57effffb feat(apps): Breakout 1.0 — classic brick-breaker as a Lua app
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>
2026-10-02 22:37:57 +02:00
SamandClaude Sonnet 4.6 44f5dae4eb test(harness): add Tetris scenarios — tdeck, v4, keyboard, logic, hiscore, cost
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>
2026-10-02 22:21:44 +02:00
Kaj SchittecatandClaude Opus 5 80f6aa97ea apps: SD Scan 1.1, buttons above the findings list
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>
2026-09-19 09:13:43 +02:00
Kaj SchittecatandClaude Opus 5 6868911f59 touch: SD Scan, find and remove Windows malware on the SD card
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>
2026-09-18 13:33:30 +02:00
Kaj Schittecat 65ea09ea89 Merge PR #316: WAV/MP3 playback for Lua apps
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.
2026-08-27 10:49:59 +02:00
Michael A. Cojocari 417a52caa1 Fix Wardrive UTF-8 text handling
Signed-off-by: Michael A. Cojocari <michael.cojocari@gmail.com>
2026-08-25 16:12:24 -04:00
Michael A. Cojocari acdcc6e7fc Work 2026-08-23 20:32:26 -04:00
Christopher Van Hoose 6cc0c1e56e Make the WMM block generated, and the tests reviewable
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.
2026-08-22 13:30:36 -04:00