April 15, 2026

flip-book v2: multiplayer animation

date: 2026-04-15


what happened

replaced flip-book.js (single-player v1) with v2: multiplayer stroke-based animation tool.


architecture decision

v2 adopts full v-log multiplayer pattern:

shared state (WebRTC via @silly/elf):

  • frames[] — ordered frame ids
  • frameStrokes{} — per-frame stroke arrays (plain JSON, replayed locally)
  • current — playback position
  • canvasW/H, fps, loopMode, playing

player-namespaced ($.teach with mergePlayer):

  • players[pid].currentStroke — active stroke being drawn
  • players[pid].cursorX/Y — cursor position in canvas coords
  • players[pid].color, frameId, activelyDrawing

local ephemeral (module-level JS, never synced):

  • undoStack, redoStack — per-frame ImageData snapshots
  • zoom, panX/Y, tool, color — UI state
  • db[frameId].drawCanvas, videoCanvas — pixel buffers rebuilt on demand

features

  • multiplayer cursors: see other peers drawing in real-time with color-coded presence
  • stroke-based: replayed from JSON, not pixel snapshots
  • chromakey: GPU (WebGL) + CPU fallback with 2MP gate (prevents main-thread lock on 4K)
  • video import: drop video files, extract frames at playback fps
  • pen mode: anchor points + fill preview
  • undo/redo: per-frame ImageData stacks, survives frame-switching
  • sub-animation: right-click frame to open nested flipbook (local only)
  • playback: loop/pingpong/once, adjustable fps, darkroom fullscreen

three cleanups completed

1. fp-span: changed from linear-gradient(135deg, #d79921 0%, #fabd2f 50%, #d79921 100%) to solid #d79921. reason: simpler, stays on-brand.

2. undo comment: was "Now stores as WHISPER state". corrected to "Module-level JS objects — ephemeral, never in elf state". reason: accuracy — they're module vars, not whisper state at all.

3. chromakey CPU path: gated at 2MP (2,000,000 pixels = 1414×1414 or 1920×1040). above threshold, skips CPU chromakey with warning. reason: main-thread pixel iteration on 4K locks UI for 100+ms; user can enable WebGL path or reduce canvas size. warning tells them why.


performance notes

boot() warns if canvas > 4MP. composite loop throttles chromakey CPU path to prevent hang. WebGL chromakey.process() runs unthrottled if available (Chromakey class handles it).


completed flip-book v2 base. ready for multiplayer testing.