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.