| .github | |
| matlab | |
| src | |
| .gitignore | |
| CLAUDE.md | |
| index.html | |
| package-lock.json | |
| package.json | |
| README.md | |
| tsconfig.json | |
| vite.config.ts |
hitandrun-commonview#
One interactive figure, one shared view. This app shows the hitandrun-interactive hit-and-run sampling figure — just the figure, no source code — and keeps it identical for everyone who has the page open, in the style of commonview: peers discover each other over nostr relays and form a WebRTC full mesh; the oldest peer is the central peer and owns the authoritative state.
The twist over commonview's counter: the shared state is a live MATLAB
computation. The central peer — and only the central peer — runs
numbl (a MATLAB-compatible runtime) in a web worker,
executing the unmodified hitandrun_demo.m from hitandrun-interactive. The
figure's uihtml bridge is intercepted host-side (no iframe): control changes
from any viewer are forwarded to the central peer, which feeds them to the
script and broadcasts the results — parameter selections, the region, and the
samples — to every viewer.
What is shared#
- Parameters: sample count, convex/non-convex region, local-segment mode.
- The region and the samples: samples travel as a Float32 blob (up to 100,000 points ≈ 800 KB), streamed in 64 KB chunks over the data channel and authenticated by a SHA-256 in a signed header. The JSON "view" message that follows it references the blob by id.
- The sampling movie: the central peer drives the animation clock, so every viewer watches the same step at the same time.
Graceful failover#
- Every message is a signed envelope (schnorr over the peer's key, which is its ID); state is only trusted from the current central peer.
- Every peer keeps the latest sample blob, so whichever peer becomes central can serve it to late joiners.
- If the central peer leaves, the next-oldest peer becomes central, boots its own engine (viewers see "starting engine…"), and continues from the last-known state — the script is stateless (the region rides along with each resample request), so any peer's engine can pick up where the last one left off.
- If the central peer's engine fails to boot or to compute (a timeout counts), it announces the failure and steps down; the election skips engine-failed peers, and the next-oldest healthy peer takes over.
- State is never persisted: once all peers leave, the room resets.
Run#
npm install # requires numbl >= 0.4.8 on npm (browser-embedding exports)
npm run dev
Open the printed URL in two tabs (identity is per page load, so every tab is its own peer). Drag the samples slider or press "New region" in either window and watch both update; close the central window and watch the other take over.
How the engine embedding works#
src/engine/numbl.worker.ts runs executeCode from the numbl npm package
against an in-memory filesystem holding the .m files (verbatim copies from
hitandrun-interactive, under matlab/). The script's
uihtml(...) call surfaces as a plot instruction carrying the component id and
initial Data; sendEventToHTMLSource calls surface via the onHtmlSourceEvent
hook; and events from the app re-enter the still-live interpreter through the
UihtmlSession returned by executeCode — firing the script's
HTMLEventReceivedFcn exactly as if the figure page had sent them. The HTML
the script loads for the figure is replaced by a one-line placeholder; nothing
is ever rendered from the worker.