Files
wadamesh/deploy/apps/apps.json
T
tmetz1987andClaude Opus 5 49f2cac65e wardrive 1.2: review a run on the device
1.1 logs a survey to CSV and notes in its own docs that there is no on-device
viewer for it, so checking coverage meant pulling the card and opening a laptop.
1.2 adds a second screen that reads the run's own log back and plots it on the
firmware's basemap via wada.map, with each stop coloured by whichever direction
of the link you ask about. Colour cycles through how well we heard them, how well
they heard us, and the worst imbalance.

Design notes a reviewer may want up front.

- A sweep writes one row per responder against a single GPS fix, so a corner
  where ten nodes answered is ten rows sharing one coordinate. The loader
  aggregates by STOP, not by row, keeping the best link in each direction and the
  largest-magnitude single-reply asymmetry at that stop. Drawing a raw row would
  have coloured the marker by whichever node answered first, which is arrival
  order and none of the three metrics the button offers.

- The log is read in 4 KB windows, one per tick, and finishes only on a read that
  yields nothing. A callback gets 100,000 VM opcodes and parsing a whole log in
  one would exceed it; and since queued rows keep draining while the screen is
  open, treating a short read as end-of-file silently dropped everything appended
  mid-walk.

- Stops are capped. Past the cap the reservoir halves in place and widens its
  stride, and it carries the newest stop across each halving and appends the
  final position when the walk ends, so the drawn route ends where the run did.
  Simulated: a 5,000-stop run keeps 315 stops spread 79/78/79/79 across the four
  quarters of the route and ends on stop 5,000. A prefix cap would have drawn the
  first 400 and nothing after.

- Drawing is strided to at most ~150 stops. Each marker and segment is an LVGL
  object created synchronously and the host puts no ceiling on an app's view
  (k_map_markers_max belongs to the Map tab), so the bound has to come from the
  app, on a loop the radio shares.

- The map view is closed before any ui.clear(), routed through one helper so the
  order cannot be broken by a later edit. See the note in drop_map: MapUd is the
  only userdata type without a generation field, so clearing with a view open
  strands the one-view counter and leaves a stale pointer. Reported separately.

- ui.canvas argchecks 1..480 on both dimensions where map.view allows 1..800, so
  the bare-plot fallback asked for 792 px on an 800 px board and would have
  raised inside the very branch that exists to keep the app standing. Clamped,
  and the call is guarded.

- Probing pauses while the review screen is open, per the store's guidance that a
  survey should probe on a cadence its user chose. Entering banks the in-flight
  probe only if it is still inside its harvest window, and otherwise drops it, on
  the same terms the tick path uses; both call one shared predicate so they
  cannot drift apart. Either way the discovery set is cleared so nothing crosses
  the pause.

- Zoom and centre are fitted in Web Mercator units, matching latLonToWorldPx:
  latitude costs 1/cos(lat) times as many pixels as longitude, and the visual
  centre is the inverse of the mean projected y rather than the mean latitude.
  Both matter on a long north-south run. A run straddling the antimeridian is
  fitted on latitude alone, with the centre longitude unwrapped onto 0..360
  before averaging so it lands near the line rather than on the far side of the
  world, and the card says it happened.

- Layout reserves every fixed element and gives the plot the remainder, because
  on_open is handed the body height rather than the panel height, and a
  half-height plot pushed the control row off the bottom. The two-line label
  reservations were checked against the shipped glyph tables at the narrowest
  label width, which is what shortened two of the strings.

- The manifest gains "icon": "map". 1.1 shipped without one, so the drawer tile
  falls back to the generic app symbol; an app cannot ship artwork, and of the
  sixteen glyph names map is the one that describes what 1.2 now does. Drop it if
  you would rather the tile stayed as it is.

TWO FIXES HERE BELONG TO 1.1, NOT TO THE REVIEW SCREEN. Both corrupt the file the
new screen reads, which is why they are here, but say the word and I will pull
them out and send 1.2 as the viewer alone.

- Ticks pause while the display sleeps; millis() does not. On waking, the harvest
  deadline is long past, the radio still holds replies heard at the old position,
  and sys.gps() reports the new one, so those replies were logged in the wrong
  place. Pocket the device, walk 400 m, wake it. Sweeps dropped this way are now
  counted on the status line, alongside a sweep discarded because the survey was
  stopped mid-listen - two causes, one counter, and the label says dropped rather
  than naming either.

- Renaming a run wrote the previous run's queued rows into the new file, and
  wrote_header being process-local appended a second header mid-file whenever an
  existing log was reopened. The on-device reader skips those lines; a
  spreadsheet does not.

TESTED ON: nothing. I have a T-Deck Plus but have not run this, so treat every
runtime claim above as read from the firmware source rather than observed. Some
specifics are worth naming rather than hiding. The body-height figure and the
capability, argcheck and projection details above come from reading
src/ui-touch/, not from the SDK page, which is out of date in at least two places
(it documents an input event type the firmware never emits, which is why this
version has no on_input, and it omits both argcheck ranges). The riskiest
unverified behaviour is whether a map view can be created again after being
closed on a screen switch; that is guarded by pcall with a canvas fallback. Label
wrapping at the narrowest width is calculated, not seen.

Written with AI assistance, then reviewed by nine independent passes and two
cross-vendor panels. Every finding that survived verification is fixed. The one
that mattered most was the arrival-order colour above, which the first panel
missed and the second caught.

ONE QUESTION, not decided here. 1.2 is 34,159 bytes against 1.1's 8,477, and
wardrive already requires sdk_ext, so boards that cannot run it still pay for it
in the baked catalog. "seed": false looks right to me, but the README calls that
your knob, so the row is left exactly as it was.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: tmetz1987 <tylerzmetzger@gmail.com>
2026-09-12 22:44:11 -07:00

56 lines
1.9 KiB
JSON

{
"apps": [
{
"id": "monitor",
"name": "RF Monitor",
"ver": "1.3",
"desc": "Scrollable: RSSI/noise chart with dBm axis, link margin, RX rate, radio params, heard feed."
},
{
"id": "airtime",
"name": "Airtime",
"ver": "1.4",
"desc": "Utilization bars with a % axis, rx/tx split, duty ceiling, budget. Reset button top-right."
},
{
"id": "snake",
"name": "Snake",
"ver": "1.0",
"desc": "The classic, in Lua. Swipe to steer."
},
{
"id": "sdktest",
"name": "SDK Test",
"ver": "1.7",
"desc": "Developer tool. Checks the extended Lua SDK on this board: capabilities, clock, battery, GPS, private file access, audio API, read-only SD listing, crypto against published RFC vectors, channel discovery, and buttons that TRANSMIT test messages."
},
{
"id": "2048",
"name": "2048",
"ver": "1.2",
"desc": "The classic sliding-tile puzzle. Swipe to move, tap to restart. Keeps your best score. Contributed by pisti87."
},
{
"id": "wardrive",
"name": "Wardrive",
"ver": "1.2",
"desc": "LoRa coverage survey. Probes every 20s and logs each reply with GPS position, altitude and BOTH link directions (how well you heard them, how well they heard you) to a CSV, then plots the run back on the map so you can check coverage before driving it again. Reference app for wada.mesh.discover.",
"requires": "sdk_ext"
},
{
"id": "nearby",
"name": "Nearby",
"ver": "1.0",
"desc": "Your contacts on a real map, sorted by distance, with bearing and a live frame counter. Reference app for wada.map, wada.ui.list, wada.geo and app.on_packet.",
"requires": "sdk_ext"
},
{
"id": "gpscompass",
"name": "GPS Compass",
"ver": "1.0",
"desc": "Compass rose: magnetometer or GPS course, live fix, bearing to contacts",
"seed": false
}
]
}