Build System Groundwork and Modem Plumbing
Tightening the build pipeline for modem-core WASM, dynamic backend loading, and making the modem behave like a real modem.
I wasted an hour debugging a modem timing issue that did not exist. The bug was in yesterday’s code—I was running a stale WASM build. That is the kind of lie a build system tells when it is not honest about dependencies, and it is the kind of lie that makes debugging feel like archaeology instead of engineering.
So I stopped and fixed the pipeline. The modem-core WASM build is now a first-class step in the Makefile, the Vite entry no longer assumes imports are fresh, and production builds go through the same path as development. The constraint is simple: if the frontend runs, it runs today’s code.
With the build predictable, I moved the modem toward a single source of truth for pacing. A modem is not a socket with a baud rate; it is a state machine with explicit delays and buffer limits. The byte interval now derives from the profile, not from scattered constants in the transmit path:
export const Bell103: ModemProfile = {
baudRate: 300,
audio: {
originateCarrierHz: 1170,
answerCarrierHz: 2125,
markHz: 1270,
spaceHz: 1070,
},
dialSequence: {
dialToneMs: 250,
dtmfToneMs: 80,
dtmfGapMs: 70,
postDialSilenceMs: 700,
handshakeMs: 3000,
},
flowControl: 'RTS/CTS',
};
Those numbers give the dial tone and handshake a predictable shape. Backends can focus on content; the modem owns the transport clock.
I also moved backend loading to the dial path. If you never call a service, it never ships to the browser—initial payload dropped ~30KB. The experience shifts from “choose a mode” to “dial a system,” which is the mental model I actually want.
Tomorrow: the big repo restructure. The build is honest now, which means I can move files without wondering if I am testing the right version.
This set the stage for the big repo restructure. Previous: 2026-01-21