fix: web text corruption, blocking-load notice, no-cache dev server

**Most UI text drew with raylib's built-in font, not the loaded one.**
Thirteen call sites used DrawText/MeasureText directly instead of the
project's DrawTextScaled/MeasureTextScaled wrappers, so the cursor and
marker readouts, spectrum panel labels and export toast rendered from
raylib's default bitmap atlas. On desktop that merely looked slightly off;
on web it renders as garbage. The only raw calls left are the two
fallbacks inside the wrappers themselves.

**The rail tooltip held a dangling pointer.** RailButton stored a
TextFormat() result for the deferred draw pass, and raylib documents that
string as expiring once TextFormat has been called a few more times — which
it has by then. It copies into owned storage now.

**Loading a large file in the browser looked like a crash.** The web build
computes its whole STFT in one synchronous pass, with no frame presented in
between, so nothing drawn on the canvas during that window ever reaches the
screen. A notice now goes into the host DOM instead, which the browser
paints independently, and yields once so that paint actually happens before
the work starts. Because that yield unwinds the stack under ASYNCIFY, the
load block is guarded by stftBusy — without it the re-entered main loop
calls ComputeSTFTInit again and frees the STFT the suspended call is still
building.

**Removed EXPORTED_FUNCTIONS from the web link flags.** It replaces
emscripten's default export list rather than extending it, so everything
unnamed is dead-code-eliminated. The upload callback stays reachable via
the EMSCRIPTEN_KEEPALIVE already on its definition.

**Adds serve_web.py**, a dev server that sends no-store. Browsers cache
.wasm hard enough that a plain reload runs a stale module while the page
looks freshly loaded — which makes a rebuild appear to change nothing.
That cost most of a debugging session today: three separate fixes were
tested against a binary that never changed, each returning an identical
fault at an identical address. README now points at it and says why.

Documents the remaining web issue in known_bugs.md: large captures still
corrupt text after loading. Leading theory is ALLOW_MEMORY_GROWTH
reallocating the heap mid-load and invalidating pointers cached across
that moment, which fits the symptom being text specifically.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V8ZWfr5XZyyDttvkhJUgHN
This commit is contained in:
2026-08-12 18:38:59 -07:00
parent 40623feadc
commit bc8bcf78b7
11 changed files with 233 additions and 20 deletions
+40
View File
@@ -6,6 +6,46 @@ need to decide.
---
## Large WAVs in the browser: text corruption and a long hang
**Status:** open. Reproduces on the web build only; desktop is unaffected.
Loading a large capture through the web UI (drag-drop or the Open file button)
produces garbled glyphs in menu titles, tooltips and axis labels, and the page
stops responding for a long stretch. Small files load and render correctly, and
the corruption appears *after* the load rather than at startup — so it is
triggered by the size of the work, not by the build being broken.
The hang is understood and partly by design: the web build computes its entire
STFT in one synchronous pass (see the `__EMSCRIPTEN__` branch in the main loop),
because the desktop's incremental fill depends on idle main-loop frames the
browser doesn't hand back the same way. A DOM overlay now warns before it
starts, but the page genuinely is frozen until it finishes. A real fix means
chunking that work across frames or moving it to a Web Worker.
The **corruption** is the unexplained part. The leading theory is heap growth:
the build links with `ALLOW_MEMORY_GROWTH=1`, and a large file forces the wasm
heap to grow mid-load. Growth reallocates the backing `ArrayBuffer`, which
invalidates every cached view and raw pointer held across that moment — so
anything retaining a `char*` or a texture-side pointer from before the growth
would read garbage afterwards. Font glyph data reached through raylib's atlas is
a plausible casualty, which fits the symptom being *text* specifically.
Worth checking first:
- Whether `INITIAL_MEMORY` large enough to avoid growth entirely makes it go
away. That would confirm the theory cheaply, at the cost of a bigger initial
allocation.
- Whether the font atlas survives a deliberate `sbrk`-forced growth.
- SAFE_HEAP + ASSERTIONS=2 (`build_web.sh` debug path) to catch the first bad
access rather than the downstream symptom.
**Testing note:** always serve the web build with `serve_web.py`, never
`python3 -m http.server`. Browsers cache `.wasm` hard enough that a plain reload
runs a stale module, which makes rebuilds look like no-ops and has already
burned significant time chasing bugs that were fixed.
---
## Playhead vs. a selection edited mid-playback
**Status:** partially addressed; underlying semantics still undefined.