A one-shot recordable walkthrough of the 32/64-lane probabilistic bit field. Silent by design: the left pane carries the whole narration, so the recording needs no voice-over and no editing - open it, go fullscreen, press through.
pnpm install
pnpm dev # http://localhost:5180
The deck is a list of steps, not slides. One press, one visual state.
| Key | Does |
|---|---|
Space / → / Enter / click |
next step |
← / Backspace |
previous step |
Shift + → / ← |
jump a whole beat |
Home / End |
first / last step |
a |
start / stop autoplay |
` |
toggle the step sidebar |
h |
toggle the state HUD |
f |
toggle dev / show mode |
p |
freeze the bit flow where it is |
r |
reset the field on the original seed |
Clicking a row in the sidebar jumps straight to that step. The step index also
lives in the URL hash (1-based, so #13 is step 13), which means a reload lands
you back where you were - edit copy in stages.ts, hot reload, see it in place.
Modes. ?mode=show hides the sidebar and HUD; the bare URL is the working
mode with both on. f toggles between them at runtime.
a runs the deck on its own timings from wherever you are, and any manual input
- arrow, space, click - drops out of it. It is a transport, not a mode: nothing to get trapped in, and a bad take stops without a reload. It never starts on load, because the capture has to be rolling first.
Pacing lives in the hold column of stages.ts, in ms, one per step. Read the
column top to bottom and you have the pace of the whole video. In dev mode a
line fills across the bottom of the HUD over each hold, so you can see the pace
rather than count it.
Holds are set from repaint time, not reading time. At 6 Hz with 32 rows of history a full screen takes 5.3s to turn over, so any step that changes the bias holds at least that long; shorter and the caption moves on while half the field still shows the previous pattern. The continuous sweep needs 7s, one full period.
Step changes are snaps, not tweens. That is deliberate for now - see Not in scope yet.
Open http://localhost:5180/?mode=show, fullscreen at 1920x1080, start the
capture, press a, step away. Total run is about 1:38.
Capture at 60fps. Step 18 runs 60 ticks per second, exactly one row per display frame. At 30fps every other row is dropped and the waterfall judders in the file even though it looked smooth on screen.
The bit source is seeded (DEFAULT_SEED in engine.ts), so retakes look the
same. ← re-takes a beat without restarting the run.
| File | Holds |
|---|---|
src/stages.ts |
the storyboard - every step's copy and target state |
src/director.ts |
step index, folding, navigation, URL hash |
src/field.ts |
canvas renderer, ring buffer, spotlight veil |
src/engine.ts |
bit source and the bias-vector shapes |
src/components/ |
the 30/70 chrome, text pane, bias strip, dev tools |
src/styles/tokens.css |
every colour and type size in the app |
A step declares only what it changes. Copy is per-step; state, focus and
the show* flags are sticky and fold forward from BASE_FIELD. That is why
jumping to step 14 gives exactly the same picture as pressing there.
{
beat: 'Per-lane bias',
headline: 'Every lane, its own bias.',
lines: ['A ramp across 32 lanes, painted in the output.'],
state: { bias: { kind: 'ramp' } },
}Bias shapes are flat, ramp, step and sine, defined in engine.ts. Add a
shape there and it is available to any step.
focus veils everything outside the region and rings what is left:
{ kind: 'column', lane }- one generator's whole history{ kind: 'row', row }- one clock tick across every generator{ kind: 'cell', lane, row }- a single bit{ kind: 'axis-x' }/{ kind: 'axis-y' }- brightens that axis's separators instead of veiling. Unused by the current storyboard, kept as an alternative treatment.{ kind: 'none' }- release
Steps 3, 4 and 5 use column, row and cell on the same coordinates, so the sequence narrows from a generator to a tick to the bit where they intersect.
When a step changes the bias vector, the next row pushed carries an orange line on its top edge, and it scrolls down with history. Everything above the line was produced under the new program, everything below under the old one, which makes a transition legible instead of something you had to be watching for.
It fires on the bias spec rather than the per-lane vector, so entering the continuous sweep marks once instead of marking every frame it changes. A lane resize regenerates history, so it clears the markers rather than leaving lines pointing at rows that no longer exist.
--pane-text and --pane-field in tokens.css. Currently 30/70.
Published to GitHub Pages at
https://sqortex-labs.github.io/bitflow-demo/. Every push to main runs
.github/workflows/deploy.yml, which type-checks, builds, and republishes; a
type error fails the job rather than shipping a broken deck.
base: './' in vite.config.ts is what lets the build run from the /bitflow-demo/
subpath. All app state lives in the URL hash, so there is no router path to break.
The layout targets 1920x1080 and has no responsive handling, so the hosted link is a desktop artifact.
Tweens. Autoplay runs on snaps: bias jumps between steps rather than easing, the
rate changes in one go, and 32 to 64 lanes appears rather than widens. Easing
those lands in field.ts and is the larger job; worth doing only if the snaps
read badly on playback.
The bit source is the seeded generator. A live board feed implements the same
BitSource interface (sample, reset) and swaps in without touching the
renderer.