Build System Groundwork and Modem Plumbing
Making the WASM build honest about its dependencies, loading backends on dial, and giving the modem one source of truth for pacing.
I wasted an hour debugging a modem timing issue that did not exist. The bug was in yesterday’s code, because I was running a stale WASM build. A build system that isn’t honest about its dependencies turns debugging into archaeology.
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 a state machine with explicit delays and buffer limits, not a socket wearing a baud rate. The byte interval now derives from the profile rather than from constants scattered through the transmit path:
At this point in the work, the profile still used a three-second handshake window:
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, and backends no longer have to care: 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, and the initial payload dropped by about 30 KB. The interaction changes from “choose a mode” to “dial a system,” which is the mental model I wanted in the first place.
Tomorrow: the big repo restructure. The build is honest now, so I can move files without wondering whether I am testing the version I just edited.
This set the stage for the big repo restructure. Previous: 2026-01-21