crisis/CrisisViz
saymrwulf 54aae1a4dd Update all documentation for the crisis_agents layer + async refactor
Three sweeping additions and one new file, reflecting how the project
has grown:

* Parent `README.md` rewritten. The architecture mermaid now shows
  `crisis_agents` as a third sibling layer on top of the pure
  protocol algorithms, alongside the CrisisNode TCP runtime and the
  SimulatedNode in-process recorder. A fourth audience-shaped quick
  start (🤖 "run the AI-agent coordination demo") joins the
  protocol-pytest, simulation-CLI, and visualizer entries. The
  repository-layout tree expands to enumerate `src/crisis_agents/`'s
  modules. Test count corrected (~170).

* New `src/crisis_agents/README.md`. Comprehensive package
  documentation:
    - threat model + what's out of scope
    - the two principles enforced by tests: no chokepoint, no clock
    - mental-model mermaid (closed phase → boundary opens → async
      loop → quorum vote → multi-signer proof)
    - six-phase walkthrough matching the CLI output
    - module-by-module reference table
    - reuse map from `src/crisis/` (Message, LamportGraph,
      find_mutations, ProofOfWorkWeight, etc.)
    - build/run/test instructions including the `--live` Claude path
    - quorum-threshold formula in LaTeX: ⌈2N/3⌉
    - test taxonomy with the two sentinel files
      (test_no_chokepoint, test_async_quiescence) highlighted

* `INSTALL.md` extended. New Section 4 covers running the
  `crisis-agents demo`, both mocked-deterministic and `--live` with
  real Claude sub-agents. Anthropic SDK shown as optional `[live]`
  extras. Old sections renumbered (Section 5 → Section 6 for Swift,
  6 → 7 for Troubleshooting). Two new troubleshooting entries for
  live-mode failures.

* `CrisisViz/HANDOFF.md` gets a new Section 0. Brief notice that a
  sibling Python sub-project (`crisis_agents`) now exists, what it
  does, and — most importantly — that it doesn't share code with
  CrisisViz: refactoring one cannot break the other. Cross-link to
  the crisis_agents README so a future Swift-side agent has the
  pointer without having to discover it via grep.

Source-of-truth corrections in the parent README:
  - the "three audiences" framing becomes four
  - the layout tree now lists `src/crisis_agents/`
  - the architecture diagram explicitly marks the agent layer as
    "decentralized, asynchronous" (the two principles the recent
    refactors enforce)

CrisisViz code: still untouched by all this. Only its HANDOFF doc
gets a heads-up paragraph.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-14 22:13:00 +02:00
..
Sources/CrisisViz Ch08 erasure shards: migrate to serial timeline + per-cast vaults 2026-05-07 22:30:29 +02:00
Tools Add macOS .app bundle with native Dock icon and activation policy 2026-04-30 20:21:18 +02:00
.gitignore Add CrisisViz/package-dmg.sh — distributable DMG installer 2026-05-14 15:52:14 +02:00
bundle.sh Add macOS .app bundle with native Dock icon and activation policy 2026-04-30 20:21:18 +02:00
HANDOFF.md Update all documentation for the crisis_agents layer + async refactor 2026-05-14 22:13:00 +02:00
package-dmg.sh Add CrisisViz/package-dmg.sh — distributable DMG installer 2026-05-14 15:52:14 +02:00
Package.swift Add CrisisViz: native macOS 26 SwiftUI visualizer with vertex inspection 2026-04-30 20:06:58 +02:00
README.md Add CrisisViz/README.md — Swift-side human guide 2026-05-14 15:51:50 +02:00

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.