Commit Graph
7 Commits
Author SHA1 Message Date
JakubandClaude Sonnet 5 f8ab4c3a98 fix(sim): white-on-black display palette instead of amber
Real SSD1306/SH1106 OLEDs this sim mirrors are monochrome white-on-black,
not amber -- swap the canvas fillStyle/strokeStyle from #ffb000 to #fff
everywhere the sim display driver blits lit pixels.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018iubftDmKNWmkNnhJRz8UH
2026-09-03 23:05:04 +02:00
JakubandClaude Sonnet 5 b2cf459460 fix(sim): text-width/render bugs, splash version, wasm-fetch error, battery lag
Code-review pass over the buzzer/sim commits turned up several real bugs,
plus two issues found afterward from manual browser testing:

Rendering (SimDisplayDriverCanvas, variants/sim/SimDisplayDriver.h + target.cpp):
- getTextWidth() measured UTF-8 BYTES (strlen()*6), not codepoints. Since
  b067e95b stopped stripping accents, any accented string now measures
  double its real width -- mis-centred titles, premature ellipsis/marquee,
  badges pushed off-screen. Now uses the real MiscFixedRenderer measurement
  (miscFixedTextWidth()), same as SH1106Display/SSD1306Display.
- Added the matching getCodepointWidth() override (O(1) single-glyph
  advance), same pattern as SSD1306Display.
- isSingleFont() was left at the base class's `false`, though this backend
  only ever renders MiscFixed -- UITask.cpp's status-bar indicator height
  keys off this (`lh-2` vs `lh`), so the sim drew it 2px taller than a real
  board.
- print() blitted the full 128x64 canvas on every call (dozens per frame,
  60fps) -- now tracks a dirty bounding box and only clears/blits the
  region actually touched.

Web Audio (buzzer bridge, index.html + mesh.html):
- No AudioContext.resume() -- a context created (or later suspended) in the
  'suspended' state (Safari/Firefox, or any browser backgrounding the tab)
  stayed silent forever. Now resumed on every gesture.
- linearRampToValueAtTime with no anchoring setValueAtTime interpolates
  from the LAST scheduled event, not "now" -- so the anti-click ramps could
  effectively snap instead of fading. Fixed with cancelScheduledValues +
  setValueAtTime(current) before each ramp.
- mesh.html: a gesture only armed the clicked instance's audio. Click A,
  send A->B, and B (the one actually meant to beep on receipt) stayed
  silent. Now any gesture arms both A and B.
- RTTTL rests (freq=0, still "playing") now explicitly hold pitch and drop
  gain instead of it happening to work by coincidence.

Misc: sim_test_get_num_contacts() was missing the g_sim_ready gate every
other sim_test_* hook has, so it could return a bogus negative count before
setup() finishes seeding num_contacts.

Splash screen missing "Solo <version>" bar: variants/sim never defined
FIRMWARE_SOLO_BUILD (every real Solo board does), so SplashScreen silently
skipped that whole line -- the sim looked like a plain non-Solo companion
build. Added -D FIRMWARE_SOLO_BUILD=1 to platformio.ini and build_wasm.sh.
Verified on a real canvas screenshot: "MESHCORE 1.17.1 / 19 Aug 2026 /
Solo v1.27".

Wasm-fetch error message: "failed to start: RuntimeError: Aborted(both
async and sync fetching of the wasm failed)" is Emscripten's own opaque
message for the single most common real cause -- the page opened via
file://...index.html instead of served over http(s) (fetch() on a local
file is blocked by CORS in both Chrome and Safari, confirmed by reproducing
the exact same error/stack via file://). Both harnesses now detect
location.protocol === 'file:' and show an actionable message with the
one-line fix instead of the raw stack trace.

Battery-set latency: SimMainBoard's battery value is an exact, instantaneous
JS-set integer (see sim_battery_set_mv()), but UITask's battery-check code
polls it every 8s and runs it through an EMA (alpha=0.2) meant to smooth a
REAL board's noisy ADC -- so a value typed into the demo UI could take tens
of seconds to visibly settle. SIM_PLATFORM now checks every 250ms and skips
the EMA (nothing to smooth), since the reading is already clean. Measured
on real canvas pixels: indicator update now lands within one screen-refresh
cycle instead of up to 8s+.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-03 19:32:23 +02:00
JakubandClaude Sonnet 5 b067e95b24 fix(sim): accent-picker confirm was silently dropping the diacritic
Selecting a variant from the Hold-Enter accent popup (e.g. 'o' -> 'o with
acute') correctly stored the real UTF-8 character in the text buffer, but
the live-typing preview line rendered it as the plain base letter -- the
accent was invisible even though the data was right.

Root cause: DisplayDriver::translateUTF8ToBlocks() transliterates extended
Latin characters down to ASCII for display drivers whose font can't render
them; real boards with the pixel-perfect MiscFixedFont override it to a
plain passthrough once _single_font is set (SH1106Display.cpp etc).
SimDisplayDriverCanvas reuses that same MiscFixedFont/MiscFixedRenderer
(confirmed the popup's own preview renders diacritics correctly, since it
prints its own variants directly rather than through this path) but never
added the matching override, so every string routed through
translateUTF8ToBlocks() -- not just the keyboard preview -- silently lost
its diacritics.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-03 12:10:57 +02:00
JakubandClaude Sonnet 5 d7242ddc21 fix(sim): real text-size scaling + correct joystick-board menu hint
Two more real-vs-sim mismatches, found after seeing the rendered UI:

1. The Clock screen's big time display (setTextSize(2)) rendered at size 1
   -- SimDisplayDriverCanvas ignored setTextSize() entirely (a leftover
   no-op from the old system-font renderer) and never overrode
   getCharWidth()/getLineHeight(), so the big-digit layout math in
   UITask.cpp's drawBig() came out wrong even once print() itself gained
   real font support. Track _text_sz, scale both metrics by it (matching
   SSD1306Display's own getCharWidth()==6*_text_sz pattern), and pass it
   through to miscFixedPrint() in target.cpp instead of a hardcoded 1.

2. The Home carousel's "<PRESS_LABEL> to open" hint said "long press to
   open" -- true only for touchscreen-only boards with no dedicated Enter
   button (PRESS_LABEL's #if UI_HAS_JOYSTICK / #else split in
   examples/companion_radio/ui-new/UITask.cpp). The sim's D-pad + OK key
   behaves like a joystick board (a SHORT Enter press opens each page;
   holding it separately reaches the real context menu via
   handleLongPress()), so showing the touchscreen wording was both
   inaccurate and different from what a real joystick board like Heltec V3
   displays. Added SIM_PLATFORM to that #if alongside UI_HAS_JOYSTICK --
   UI_HAS_JOYSTICK itself stays unset, since its other two gates
   (begin()-ing/polling real joystick MomentaryButton objects) need
   hardware the sim's target.cpp doesn't declare.

Verified in real Chromium: Clock screen shows "08:11:10" at real double
size above the normal-size date line; Home carousel now says "press Enter
to open". Full regression clean: 3 native envs, wasm companion_radio, the
2-instance+repeater mesh demo, and the long-press context-menu test.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-03 10:12:31 +02:00
JakubandClaude Sonnet 5 725e3b715e feat(sim): long-press context menu + pixel-perfect real font rendering
Two prototype polish fixes ahead of the website embed work:

1. Holding Enter (the sim's main way into "the rest of the options") did
   nothing -- the JS bridge's sim_enqueue_key() went straight to
   enqueueKey(), bypassing UITask::handleLongPress() entirely, so
   KEY_CONTEXT_MENU could never be reached. Add injectSimKeyLongPress()/
   sim_enqueue_key_longpress(), which does route through the real
   handleLongPress() (same code a real MomentaryButton(pin, 1000, ...)
   reaches), and wire up press-and-hold (buttons + Enter/Space key) in both
   web harnesses with the same 1000ms threshold real hardware uses.

2. SimDisplayDriverCanvas::print() drew text with the browser's own system
   font (ctx.fillText, '8px monospace') instead of the real bitmap font a
   MeshCore-Solo board renders with OLED_MISC_FIXED_FONT=1 (see
   solo/heltec_v3/platformio.ini). Vendor the real Adafruit_GFX (unmodified,
   from the same PlatformIO registry package a real board build pulls) into
   variants/sim/thirdparty/gfx/, and render print() through the real,
   shared src/helpers/ui/MiscFixedRenderer.h + MiscFixedFont.h -- byte-
   identical glyphs to real hardware, not a look-alike.

Verified in a real Chromium (Playwright): short Enter -> "CLOCK TOOLS",
held Enter -> "CLOCK FIELDS" (gotoDashboardConfig(), proving
KEY_CONTEXT_MENU is really reached); font renders as hard square pixels,
inverse/selected-row text still punches correctly through a filled bar.
Full regression re-run clean: all 3 native envs, wasm companion_radio, and
the two-instance + repeater mesh demo (relay routing, DM delivery).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-03 10:06:44 +02:00
JakubandClaude Sonnet 5 9cfb58a60b feat(sim): two-device messaging + repeater relay over a JS ether
Ports examples/simple_repeater to variants/sim/ (new sim_simple_repeater
native env + build_wasm_repeater.sh) and adds a JS "ether"
(variants/sim/web/mesh.html) that bridges two real companion_radio WASM
instances through a real simple_repeater instance in a strict A<->R<->B
topology (no direct A-B link), proving genuine relay routing rather than
a shortcut.

Also fixes multi-instance issues Phase 2's single-instance design never
surfaced: SimDisplayDriver's canvas context/id caching was keyed on a
single global instead of per-instance, and both wasm builds were missing
_malloc/_free/HEAPU8 runtime exports needed for the ether to poke bytes
into an instance's memory.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-03 09:14:15 +02:00
Jakub 8f4c92a217 feat(sim): add variants/sim/ — real companion_radio firmware on native + Emscripten
New board variant compiling the unmodified MyMesh/UITask/DataStore app
logic against real mesh::Radio/MainBoard/RTCClock/RNG interfaces, for
running the actual firmware outside embedded hardware:

- Native (plain g++, platform = native): ASCII-art display over stdout,
  stdin-driven input, local-disk-backed DataStore/IdentityStore.
- Emscripten/WASM (variants/sim/build_wasm.sh, since PlatformIO's native
  platform force-overrides any CC/CXX toolchain override back to system
  clang++): canvas-backed display, IDBFS-backed persistence across page
  reloads, JS-callable input via sim_enqueue_key(), emscripten_set_main_loop.

Real rweather/Crypto (AES128/SHA256/Ed25519) vendored unmodified and
proven working on both targets. variants/sim/web/index.html is a bare
verification harness, not the polished website embed.
2026-09-03 00:46:47 +02:00