CC = gcc CFLAGS = -Wall -Wextra -O2 -Iinclude $(shell pkg-config --cflags libpulse) LDFLAGS = $(shell pkg-config --libs libpulse) -lrt -lpthread PULSE = src/pulse.c .PHONY: all clean serve blog test test-all test-web test-zebra-spaces stamp zebrad # stamp each web page with today's date + its own md5/sha256 (run before deploy) stamp: @node web/stamp.js web/chat.html web/zebra-audio.html web/zebra-spaces.html web/how-it-works.html web/host-your-own.html all: tx rx chat bt carrier zebrad test: test/unit @./test/unit # web modem/protocol tests — pure Node, no browser or second device needed test-web: @node test/web-protocol.test.js # zebra-spaces state-machine tests — pure unit tests for the FSM framework # + each machine spec (publish / subscribe / call / remote-tile). Extracts # the live code from web/zebra-spaces.html so the tests track the page. test-fsm: @node test/zebra-fsm.test.js # Receive-side stream lifecycle (watchVideoTrackForRemoval) — fake-clock # state-machine tests covering 'when is it safe to remove a tile'. Catches # the regressions where transient mutes (NACK gaps, network blips, mobile # handoffs, hard-refresh renegotiation churn) would otherwise kill live # tiles. Extracts the function from the page and the shipped mute window # so the assertions track what's live. test-video-removal: @node test/video-track-removal.test.js # Mesh state-sync invariant tests — pins the contract fox stated as # "whatever one device shares all should see, and when unshared none # should see." Runs the shipped handleRemoteSfuTrack + renderVideoTile + # removeVideoTile + watchVideoTrackForRemoval against multi-peer # scenarios with synthetic ontrack/mute/unmute/ended events. Catches # regressions where one peer's publish/unpublish leaves another peer # out of sync. test-mesh: @node test/multi-peer-mesh.test.js # Mod-action serializer — pins the kick race fix fox hit 2026-06-04 # ("kicked two phones, only one was kicked"). Extracts runModSerial + # awaitStateUpdate + resolvePendingStateUpdate from the live page so # the assertions track the shipped code, then drives synthetic mod # actions through them with a stubbed roomEpoch counter to prove the # second action signs against the FRESH epoch. test-mod-actions: @node test/mod-action-serializer.test.js # SelfListenerFSM — pins the state-machine contract for the speaker/ # cohost/host "switch myself to the buffered HTTP listener stream" # toggle. Extracts createFSM + selfListenerSpec from the live page so # the spec can't drift from shipped transitions (off↔on with # TOGGLE/ENABLE/DISABLE/UNMUTE/DEMOTED/CLEAR edges). test-self-listener: @node test/self-listener-fsm.test.js # Listener audio attach pipeline — pins src→(jbuf)→gain→destination # graph reachability through attachAudioStreamViaWorklet, # setWorkletStream, installJitterBuffer, attachSfuTrack, # flushSfuStreams. Extracts each function from the live page and # replays the cascade scenarios (fresh attach, same-stream no-op, # different-stream in-place swap, dedup by pubkey, publisher rejoin, # zero-live-track reject). Catches silent-listener regressions where # a chain builds but never reaches audioCtx.destination. test-listener-audio: @node test/listener-audio-attach.test.js # zebra-spaces JS↔Go protocol parity + vault + ed25519 + (optionally) a live # server flow. The live-server tier auto-runs when proxy.unturf.com sits # alongside this checkout AND has a Go toolchain — we build the relay binary # transparently and point the test at it. Otherwise that tier is skipped and # only the pure-protocol/crypto tiers run. test-zebra-spaces: @bin=""; \ if [ -d ../proxy.unturf.com/cmd/zebra-spaces-signal ]; then \ go=$$(command -v go || echo /home/fox/.local/go/bin/go); \ if [ -x "$$go" ]; then \ echo "Building zebra-spaces-signal for live-server test..."; \ (cd ../proxy.unturf.com && "$$go" build -o /tmp/zspc-signal-test ./cmd/zebra-spaces-signal/) && bin=/tmp/zspc-signal-test; \ fi; \ fi; \ ZEBRA_SPACES_BINARY=$$bin node test/zebra-spaces.test.js; \ rc=$$?; rm -f /tmp/zspc-signal-test; exit $$rc test-all: test/unit test/integration test/functional test-web test-fsm test-video-removal test-mesh test-mod-actions test-self-listener test-listener-audio test-zebra-spaces @echo "--- unit ---" @./test/unit @echo "--- integration ---" @./test/integration @echo "--- functional ---" @./test/functional @echo "--- web protocol ---" @node test/web-protocol.test.js @echo "--- zebra-fsm ---" @node test/zebra-fsm.test.js @echo "--- video-track-removal ---" @node test/video-track-removal.test.js @echo "--- multi-peer-mesh ---" @node test/multi-peer-mesh.test.js @echo "--- mod-action serializer ---" @node test/mod-action-serializer.test.js @echo "--- self-listener FSM ---" @node test/self-listener-fsm.test.js @echo "--- listener audio attach ---" @node test/listener-audio-attach.test.js @echo "--- zebra-spaces ---" @$(MAKE) -s test-zebra-spaces test/unit: test/unit.c include/zebra.h include/modem.h test/test.h $(CC) $(CFLAGS) -o $@ test/unit.c test/integration: test/integration.c src/pulse.c include/zebra.h include/modem.h test/test.h $(CC) $(CFLAGS) -o $@ test/integration.c src/pulse.c $(LDFLAGS) test/functional: test/functional.c src/pulse.c include/zebra.h include/modem.h test/test.h $(CC) $(CFLAGS) -o $@ test/functional.c src/pulse.c $(LDFLAGS) tx: src/tx.c $(PULSE) $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) rx: src/rx.c $(PULSE) $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) chat: src/chat.c $(PULSE) $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) bt: src/bt.c $(PULSE) $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) carrier: src/carrier.c $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) zebrad: src/zebrad.c include/zebra.h $(CC) $(CFLAGS) -o $@ src/zebrad.c $(LDFLAGS) -lm blog: python3 blog/build.py serve: blog cd web && python3 -m http.server 8765 clean: rm -f tx rx chat bt carrier zebrad test/unit test/integration test/functional rm -rf web/blog/001-volume-modem web/blog/002-sse-chatroom web/blog/index.html