crisis/CrisisViz/README.md
saymrwulf 5c6d220b38 Add CrisisViz/README.md — Swift-side human guide
The Swift sub-project had no README of its own. Anyone navigating
into CrisisViz/ would see Package.swift, bundle.sh, and a stale
HANDOFF.md, with no orientation about the chapter / scene model
or why the renderer is organized the way it is.

The new README is shaped for someone reading on GitHub or iPad:

  - LaTeX-rendered formula for the pure-function timeline pattern
    (state(t) = fold of beats whose start <= t);
  - Mermaid block diagram of SceneEngine -> ChXXTimeline -> Canvas
    + GlassNarration;
  - per-chapter table with beat count, runtime at 1x, and concept,
    plus the off-by-one naming gotcha (renderer Ch01..Ch10 vs.
    timeline Ch00..Ch09);
  - build / run / test / distribute one-liners;
  - testbed output map (INVARIANTS / SOURCE_AUDIT / VIDEO_CLIPS /
    MANIFEST / SANITY);
  - cast convention (lane = lifeline, strictly serial, introduce-
    before-show, one detail slot) and the architecture-pointer
    table for the critical files.

GitHub renders the LaTeX and Mermaid natively, so this serves as
both repo doc and iPad-readable reference.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-14 15:51:50 +02:00

6.5 KiB
Raw Blame History

CrisisViz

A native macOS 26 / SwiftUI keynote-style visualizer for the Crisis consensus protocol. Ten chapters, roughly eighteen minutes of curated playback at 1\times, scrubbable forward and backward at any signed rate from -16\times to +16\times.

CrisisViz consumes a recorded simulation from crisis_data.json and replays it with narration bound to specific narrative beats — the playhead is the master of time.


The serial-timeline pattern

Every chapter is a pure function of normalized time. Given a chapter with an ordered list of beats b_0, b_1, \ldots, b_{n-1} each with duration d_i, the world state at time t \in [0, T] (where T = \sum d_i) is:

\text{state}(t) = \mathop{\textrm{fold}}_{i\,:\,\sum_{j<i} d_j \le t} (b_i, \text{state}_{\text{init}})

That is: replay every beat whose start lies before t, accumulating into a WorldState struct. Pure ⇒ scrubbable ⇒ reverse-playable. No monotonic accumulators, no animation state hidden in @State.

flowchart LR
    Engine["<b>SceneEngine</b><br/>signed speed · pause · scrubber"]
    Engine -->|sceneIndex, localTime| Timeline
    Timeline["<b>ChXXTimeline</b><br/>state(at:t) → WorldState<br/>narrationAt(scene, t)"]
    Timeline -->|WorldState| View
    View["<b>ChXX_Foo (Canvas)</b><br/>thin renderer"]
    View -->|draw| Canvas["macOS Canvas + GraphicsContext"]
    Timeline -->|narration text| Glass["<b>GlassNarration</b><br/>beat-bound overlay"]

    classDef engine fill:#cfe2f3,stroke:#2c5f8f,color:#062b4d
    classDef pure fill:#d9ead3,stroke:#38761d,color:#0b3d0b
    classDef view fill:#ead1dc,stroke:#741b47,color:#3d0a26
    classDef ui fill:#fff2cc,stroke:#bf9000,color:#3d2e00
    class Engine engine
    class Timeline pure
    class View,Canvas view
    class Glass ui

The chapters

# File pair Beats T at 1\times Concept
0 Ch01_Problem.swift + Ch00Timeline.swift 12 ~43 s Four friends, one ledger, no boss
1 Ch02_Graph.swift + Ch01Timeline.swift ~75 ~326 s Asynchronous gossip & Lamport DAG
2 Ch03_Partition.swift + Ch02Timeline.swift 27 ~115 s Network partition splits the graph
3 Ch04_Rounds.swift + Ch03Timeline.swift 17 ~72 s Witness weight → round boundary
4 Ch05_Voting.swift + Ch04Timeline.swift 15 ~63 s Virtual voting via strongly-seeing paths
5 Ch06_Leader.swift + Ch05Timeline.swift 10 ~48 s Leader election
6 Ch07_Order.swift + Ch06Timeline.swift 11 ~52 s Total order — convergence guarantee
7 Ch08_DA_Problem.swift + Ch07Timeline.swift 14 ~69 s Data availability: gossip ≠ storage
8 Ch09_DA_Design.swift + Ch08Timeline.swift 19 ~85 s Erasure shards + Merkle proofs
9 Ch10_Byzantine.swift + Ch09Timeline.swift 18 ~80 s f < n/3 fork detection

Naming gotcha: chapter renderers are 1-indexed (Ch01..Ch10), timelines are 0-indexed (Ch00Timeline..Ch09Timeline). They pair off-by-one. The renderer file's number matches the user-facing chapter label.


Build · run · test · distribute

swift build                        # dev binary
swift run CrisisViz                # launch dev binary (no Dock icon)
./bundle.sh                        # build + assemble CrisisViz.app + open
./bundle.sh --no-launch            # build only
swift run CrisisViz --testbed      # PNG + MP4 + invariants harness
./package-dmg.sh                   # build CrisisViz.dmg for distribution

For first-time setup and prerequisites see ../INSTALL.md.


Testbed outputs

swift run CrisisViz --testbed writes everything to ~/Desktop/CrisisViz_Testbed/:

File What it verifies
INVARIANTS.md 38 logical curriculum assertions (cast colors, lane Y positions, scene staging)
SOURCE_AUDIT.md Regex audit of all .swift files — forbidden patterns like hashJitterY, palette[i], hardcoded colors
MANIFEST.md PNG sweep across all scenes × time offsets \{0, 2, 4, 6, 8\}\,\text{s}
VIDEO_CLIPS.md 36 MP4 clips at 8\,\text{s} / 30\,\text{fps}, one per scene
SANITY.md File-size + freeze-frame heuristic checks (catches dead canvases)

All five must be green before shipping curriculum changes. The testbed verifies layout and logical claims; it cannot evaluate animation smoothness — that requires running the live app and watching.


Controls

Input Action
← / → previous / next scene (across all chapters)
Space play / pause
Bottom slider signed speed, -16\times to +16\times
Chapter scrubber jump to any chapter
Click a vertex open the vertex inspector overlay

Cast convention

The visualizer adopts a fixed cast of named players over a subset of the simulation's process IDs:

Lane Name Role
0 Aaron leader-eligible honest
1 Ben honest
2 Carl honest
3 Dave the Byzantine in Chapter 9
4+ anonymous peers the rest of the simulated nodes

Hard rules:

  • Lane = lifeline. Vertices sit exactly on their player's lane Y. No jitter, ever. Source audit forbids reintroducing hashJitterY.
  • Strictly serial. Never two simultaneous events on screen at the same time.
  • Introduce before show. A cast member's lane is invisible until their introduce(...) beat fires.
  • One detail slot. The top-center "composing / open-envelope" panel is a single fixed rectangle; only one envelope occupies it at a time.

Architecture pointers

Concern File
@main, activation policy, window clamping Sources/CrisisViz/App/CrisisApp.swift
Playback controller (signed speed, scrubber) Sources/CrisisViz/Engine/SceneEngine.swift
Pure per-chapter timelines Sources/CrisisViz/Engine/ChNNTimeline.swift
Cast → color → lane mapping Sources/CrisisViz/Engine/DataManager.swift
Chapter metadata, scene routing Sources/CrisisViz/Model/ChapterDefinitions.swift
Cast definitions Sources/CrisisViz/Model/Cast.swift
Main canvas + inter-chapter morph Sources/CrisisViz/Views/ImmersiveView.swift
DAG layout (lane = lifeline) Sources/CrisisViz/Canvas/DAGLayoutEngine.swift
Bottom controls bar Sources/CrisisViz/Glass/GlassControls.swift
Beat-bound narration overlay Sources/CrisisViz/Glass/GlassNarration.swift
Testbed entry points Sources/CrisisViz/Testbed/

For the engineering log oriented at the next coding agent, see HANDOFF.md.