mirror of
https://github.com/saymrwulf/crisis.git
synced 2026-05-14 20:37:54 +00:00
Refresh CrisisViz/HANDOFF.md — current state for the next coding agent
The old HANDOFF documented animation bugs that were fixed weeks
ago and predates the serial-timeline migration. It referenced
"Ch07 sort", "Ch09 erasure coding", and "Ch10 shield appear" as
broken in ways that no longer apply, and it pointed at code paths
(`.id(engine.currentGlobal)`, the 4s `sceneDuration`) that have
since been replaced. A new agent reading the old file would chase
ghosts.
The rewrite is structured as an engineering log:
1. Current state (10 chapters migrated, testbed green, last
commit on master, what the bundle pipeline produces);
2. The pure-function timeline pattern (Beat list, WorldState,
state(at: t), ChXXScenes wrappers, the renderer's shape);
3. Pedagogy invariants the renderer MUST hold (strictly serial,
introduce-before-show, lane = lifeline, fixed detail slot,
courier track, cast colors via DataManager, beat tag,
scene indicator);
4. Hard-won rules from past sessions (restart the .app, testbed
doesn't verify smoothness, narration ≡ canvas, arrows must
be visible, layout from the full dataset);
5. Test-harness reference (five layers + when to update each);
6. Known open items (DA polish, per-scene vertex counts in
invariants, LaneRenderKit extraction, animation diff
analyzer, CrisisNode/gossip TCP tests);
7. How to resume (exact commands, debug ladder).
README and HANDOFF intentionally overlap — README is the lobby
for humans, HANDOFF is the engineering log for the next agent.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
5c6d220b38
commit
8ada0af0bc
1 changed files with 109 additions and 63 deletions
|
|
@ -1,92 +1,138 @@
|
|||
# CrisisViz Handoff Document
|
||||
# CrisisViz — Agent-to-Agent Handoff
|
||||
|
||||
## What This Is
|
||||
A native macOS 26 SwiftUI visualizer for Mirco Richter's Crisis consensus protocol. Full-screen keynote-style presentation with Liquid Glass chrome, Canvas-based 60fps rendering, 36 scenes across 10 chapters.
|
||||
> **Audience.** This file is the engineering log for the **next coding agent** to pick up this project. It assumes Swift, SwiftUI, macOS 26, and the Crisis protocol context from `../README.md`. For human-facing orientation see `README.md` next to this file.
|
||||
|
||||
## Current State: App Runs, Core Animation Bugs Fixed
|
||||
Last updated: **2026-05-14**.
|
||||
|
||||
The app builds and runs: `swift build && swift run CrisisViz` from `/Users/oho/GitClone/ClaudeCodeProjects/crisis/CrisisViz/`.
|
||||
---
|
||||
|
||||
### What Was Just Fixed (This Session)
|
||||
## 1. Current state — what's shipped
|
||||
|
||||
Three root-cause bugs that made the app feel like "slide after slide with hard jumps":
|
||||
- **All 10 chapters migrated** to the serial-beat timeline pattern (pure `state(at: t) -> WorldState`, scrubbable −16× to +16×, beat-bound narration).
|
||||
- **Testbed green** at the last clean run: 38/38 invariants pass, 0 source-audit errors, 36/36 MP4 clips written, 279 PNGs sane, 12/12 resize cases pass.
|
||||
- **`origin/master` at `fb9bc9c`** — working tree was clean before this documentation/testing pass. After this pass: README.md/INSTALL.md/LICENSE/CrisisViz README&HANDOFF/package-dmg.sh/Python tests landed.
|
||||
- **Bundle pipeline works.** `./bundle.sh` produces a working `CrisisViz.app`. `./package-dmg.sh` produces a working `CrisisViz.dmg` (ad-hoc signed; first-open Gatekeeper warning, right-click → Open).
|
||||
|
||||
1. **`.id(engine.currentGlobal)` was destroying views on every scene change** (`ImmersiveView.swift:16`). Changed to `.id(engine.address.chapter)` so views only recreate on chapter transitions. Within a chapter, the Canvas stays alive and `sceneIndex` updates smoothly.
|
||||
If you can't run the testbed and confirm it's green, **stop and fix that first** before making curriculum changes.
|
||||
|
||||
2. **ALL time-based animations were broken across 9 of 10 chapters.** Every chapter used `timeline.date.timeIntervalSinceReferenceDate` (~800M seconds) as `time`, so `min(1.0, time * 0.2)` was always 1.0. Nothing ever animated — Ch07 sort, Ch09 erasure coding, Ch03 partition split, Ch10 shield appear — all frozen at final state. Fixed by adding scene-local time tracking (`sceneStartTime` + `lastSceneIndex` pattern) to Ch01, Ch03, Ch04, Ch05, Ch06, Ch07, Ch08, Ch09, Ch10. Ch02 already had it.
|
||||
---
|
||||
|
||||
3. **Scene duration was 4s but animations need 5-10s.** Changed `sceneDuration` from 4.0 to 8.0 in `SceneEngine.swift`.
|
||||
## 2. The pure-function timeline pattern (this is the architecture)
|
||||
|
||||
### What the User Has NOT Yet Verified
|
||||
|
||||
- The user has not yet tested the live app with these fixes. They restarted Claude Code before testing.
|
||||
- Smooth within-chapter transitions (the main complaint) — needs live verification
|
||||
- Whether 8s per scene is the right duration
|
||||
- Whether cross-fade on chapter transitions feels good
|
||||
|
||||
## Architecture (Key Files)
|
||||
Every chapter is split into two files:
|
||||
|
||||
```
|
||||
Sources/CrisisViz/
|
||||
App/CrisisApp.swift — entry point, --testbed flag
|
||||
Engine/SceneEngine.swift — navigation state, auto-advance timer, sceneDuration=8s
|
||||
Model/ChapterDefinitions.swift — SceneAddress, AllChapters (10 chapters, 36 scenes total)
|
||||
Model/SimulationData.swift — crisis_data.json parsing
|
||||
Model/ConsensusData.swift — DataManager, NodeSnapshot, VertexData, EdgeData
|
||||
Views/ImmersiveView.swift — full-screen container, .id(chapter) for cross-fade
|
||||
Views/SceneRouter.swift — switch on chapter → chapter view with sceneIndex
|
||||
Glass/GlassNarration.swift — Liquid Glass narration panel (bottom-left)
|
||||
Glass/GlassControls.swift — Liquid Glass control bar (bottom-center)
|
||||
Canvas/DAGLayoutEngine.swift — DAGLayout: position computation + Canvas drawing helpers
|
||||
Chapters/Ch01-Ch10_*.swift — one file per chapter, Canvas+TimelineView rendering
|
||||
Testbed/SceneCapture.swift — ImageRenderer-based PNG capture of all scenes
|
||||
Sources/CrisisViz/Engine/ChXXTimeline.swift pure model (state machine over t)
|
||||
Sources/CrisisViz/Chapters/ChXX_Foo.swift thin Canvas renderer (no logic)
|
||||
```
|
||||
|
||||
## Key Design Patterns
|
||||
The timeline file is the contract. It contains:
|
||||
|
||||
1. **A typed `BeatKind` enum** capturing the chapter's micro-events (e.g. `introduce(name:)`, `compose(by:)`, `sealAccepted(at:)`, `gossipFly(from:to:)`, `linkBroken`, …).
|
||||
2. **A `WorldState` struct** holding the cumulative state at a moment in time (introduced cast set, accepted vertices per lane, in-flight envelopes, broken links, vault contents, …).
|
||||
3. **A flat `[Beat]` list.** Each `Beat` has `(id, kind, durationSeconds, narration)`. Narration is one sentence describing what that beat _physically shows_.
|
||||
4. **A `state(at: Double) -> WorldState` pure function.** Replays every beat up to `t` produces the state. Pure ⇒ scrubbable + reverse-playable. **No monotonic accumulators, no hidden `@State`.**
|
||||
5. **A `ChXXScenes` enum** with `sceneStarts`, `sceneDurations`, `timelineT(sceneIndex, localTime)`, `narrationAt(sceneIndex, localTime)`. The chapter's existing scene count stays — scenes become navigation labels on the unified timeline.
|
||||
|
||||
The renderer file is thin:
|
||||
|
||||
### Scene-Local Time (every chapter must have this)
|
||||
```swift
|
||||
@State private var sceneStartTime: Double = 0
|
||||
@State private var lastSceneIndex: Int = -1
|
||||
|
||||
// Inside TimelineView { timeline in Canvas { ... } }:
|
||||
let now = timeline.date.timeIntervalSinceReferenceDate
|
||||
var localTime = now - sceneStartTime
|
||||
if lastSceneIndex != sceneIndex { localTime = 0 }
|
||||
|
||||
// Plus .onChange(of: sceneIndex) and .onAppear to reset sceneStartTime
|
||||
TimelineView(.animation) { timeline in
|
||||
Canvas { ctx, size in
|
||||
let t = ChXXScenes.timelineT(sceneIndex: sceneIndex, localTime: localTime)
|
||||
let world = ChXXTimeline.state(at: t)
|
||||
// draw the world from `world`. No per-scene switch.
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Progressive Reveal (Ch02)
|
||||
Layout is computed from the FULL dataset (step 9, all vertices). Only a subset is revealed per scene via `sceneVertexCount`. Positions never jump because layout input doesn't change.
|
||||
`SceneEngine.durationOverrides` gets per-scene durations matching the timeline's windows. `ImmersiveView.liveNarration` adds a case for the new chapter, reading from `ChXXScenes.narrationAt`.
|
||||
|
||||
### Navigation
|
||||
- Arrow keys ← → navigate scenes linearly across all chapters
|
||||
- Space = play/pause auto-advance
|
||||
- Within a chapter: sceneIndex changes, view stays alive, Canvas updates smoothly
|
||||
- Between chapters: `.id(chapter)` changes, cross-fade via `.transition(.opacity)`
|
||||
---
|
||||
|
||||
## User's Standing Complaints (from prior messages)
|
||||
## 3. Pedagogy invariants the renderer MUST hold
|
||||
|
||||
1. "transitions are like slide after slide" — **should be fixed** by the `.id(chapter)` change + scene-local time, but NOT YET VERIFIED by user
|
||||
2. "content cut off on right side" — fixed earlier via position clamping + asymmetric margins in DAGLayoutEngine
|
||||
3. "improve the UX harness so you can have the same experience like a human" — testbed is still static PNGs. Fundamental limitation: can't verify animation smoothness from snapshots. The testbed verifies layout/composition, not motion.
|
||||
These are not stylistic preferences — the testbed enforces them. Break one, the source audit fails.
|
||||
|
||||
## Testbed
|
||||
- **Strictly serial.** Never two simultaneous events on screen. Every beat owns its time window exclusively.
|
||||
- **Cast appears via `introduce(...)` beats only.** Lanes for not-yet-introduced cast are invisible. Their lane labels are also hidden.
|
||||
- **Lane = lifeline.** A vertex belonging to player $P$ sits exactly on $P$'s lane Y. No jitter, ever. Source audit forbids reintroducing `hashJitterY`.
|
||||
- **Composing and open-envelope share ONE fixed top-center slot** (`detailSlotRect`, y ≈ 16..146 + ~30pt caption). They never co-occur on the timeline.
|
||||
- **In-flight envelopes draw on a courier track 36pt above the lane axis** so they don't collide with the just-sealed accepted vertex on the sender's lane.
|
||||
- **Cast colors via `dm.castColor(for:)`** — never `palette[i]`. Lane order via `dm.castOrderedNodes()` — never raw `sim.nodes` (would put Dave at lane 8 below 5 peers).
|
||||
- **Beat tag** (small, faint, top-right) so PNG sweeps can be matched to a specific beat for debugging.
|
||||
- **Scene indicator** `CH X.Y (n/N)` badge on the narration panel, visible even when collapsed.
|
||||
|
||||
`swift run CrisisViz --testbed` captures 36 PNGs to `~/Desktop/CrisisViz_Testbed/`. Useful for layout verification. Cannot test animation quality — that requires running the live app.
|
||||
When designing a new beat, picture all of these on screen at once (lane labels left margin, cast circles with ~50pt halos, `detailSlotRect` top band, courier track at lane Y − 36, accepted-vertex rows right of cast circles, `GlassNarration` bottom-left ~340pt × 250pt expanded, `GlassControls` bottom ~80pt). If the new beat lands on top of any of those, redesign before shipping.
|
||||
|
||||
## Data
|
||||
---
|
||||
|
||||
`crisis_data.json` in Resources/ — pre-computed simulation with 9 honest + 1 byzantine node, 10 consensus steps. Each step has vertices, edges, round info, total ordering positions.
|
||||
## 4. Hard-won rules from past sessions
|
||||
|
||||
## Build
|
||||
These are the ones that bite repeatedly. Memorize them.
|
||||
|
||||
```bash
|
||||
| Rule | Why |
|
||||
|---|---|
|
||||
| Restart the live `.app` after Swift changes | The Dock icon launches `CrisisViz.app`, not the `swift-run` dev binary. Run `./bundle.sh --no-launch && open CrisisViz.app`. |
|
||||
| Testbed can't verify animation smoothness | Static PNG sweeps catch layout bugs; MP4 clips catch motion bugs; but the user-experience of scrub feel can only be evaluated live. Always restart and watch. |
|
||||
| Narration ≡ canvas | When a scene's title is a narrative beat ("Aaron speaks. Ben listens."), hand-curate the visible-vertex set to match. Progressive reveal by `sceneVertexCount` alone will under-show or over-show and make the narration lie. |
|
||||
| Arrows must be visible | Edges drawn as `Path` lines without arrowheads are not arrows. Use `drawArrowEdge`. Every narrated causal claim ("Ben copies Aaron") must be physically renderable from the data. |
|
||||
| Layout is computed from the full dataset | Compute positions from `sim.nodes[step=lastStep]`, reveal only a subset via `sceneVertexCount`. Positions never jump because layout input doesn't change. |
|
||||
|
||||
---
|
||||
|
||||
## 5. Test harness reference
|
||||
|
||||
`swift run CrisisViz --testbed` → `~/Desktop/CrisisViz_Testbed/`. Five layers:
|
||||
|
||||
| Layer | File | Catches |
|
||||
|---|---|---|
|
||||
| Narrative invariants | `Testbed/NarrativeInvariants.swift` | logical claims about staging, cast assignment, geometry, sim convergence |
|
||||
| Source pattern audit | `Testbed/SourceAudit.swift` | regex-forbidden patterns (lane jitter, `palette[i]`, hardcoded PIDs) |
|
||||
| Per-scene MP4 clips | `Testbed/SceneVideoCapture.swift` | animation continuity (36 clips at 8s/30fps) |
|
||||
| PNG time-scrubbing sweep | `Testbed/SceneCapture.swift` | frozen interpolators, all-black renders, label drift |
|
||||
| Window resize + sanity | `Testbed/SceneCapture.swift` | clamping correctness, tiny renders, byte-identical frames |
|
||||
|
||||
When adding a new chapter / scene / design rule, **update the corresponding layer in the same commit**:
|
||||
|
||||
- New chapter → add invariants in `NarrativeInvariants.swift` (expected visible vertex count, expected cast members, expected edges).
|
||||
- New design rule → add a `Rule` to `SourceAudit.rules` with the legitimate definition site whitelisted in `allowedFiles`.
|
||||
- New animation → confirm the scene's MP4 actually contains motion. Scrub it.
|
||||
|
||||
---
|
||||
|
||||
## 6. Known open items
|
||||
|
||||
Surfaced from working memory at handoff. None blocking, all valuable:
|
||||
|
||||
1. **DA-chapter polish (Ch07 / Ch08).** Both are on the serial-timeline pattern but the shard / vault animations are still abstract relative to the cast-on-lane discipline elsewhere. Could be tightened so shards are physically carried by Aaron / Ben / Carl / Dave lanes.
|
||||
2. **Per-scene visible-vertex-count invariants.** Currently 38 logical invariants; adding count assertions per scene would catch staging regressions before they ship.
|
||||
3. **`LaneRenderKit` extraction.** Geometry helpers `castLaneY`, `castPosition`, `castColor`, `drawIntroducedLanes`, `drawCastFigures` are duplicated across cast-heavy chapters (Ch00, Ch01, Ch02, Ch09 — four adopters now, more than enough). Factor into a shared file.
|
||||
4. **Animation smoothness verification protocol.** No testbed signal — only live-app eyeballing on Ch01 staging, Ch02 partition, Ch06 total-order convergence, Ch09 byzantine. Recurring blind spot; consider an MP4 difference-frame analyzer.
|
||||
5. **CrisisNode / gossip TCP integration tests.** The real distributed runtime (`src/crisis/node.py`, `src/crisis/gossip.py`) has zero tests. Not blocking the visualizer (which uses `SimulatedNode`), but the deployable side is currently unverified.
|
||||
|
||||
---
|
||||
|
||||
## 7. How to resume
|
||||
|
||||
```sh
|
||||
cd /Users/oho/GitClone/ClaudeCodeProjects/crisis/CrisisViz
|
||||
swift build # ~4s on Apple Silicon
|
||||
swift run CrisisViz # live app
|
||||
swift run CrisisViz --testbed # PNG captures
|
||||
|
||||
# 1. Compile + trust SourceKit
|
||||
swift build
|
||||
|
||||
# 2. After source changes: rebuild the bundle
|
||||
./bundle.sh --no-launch
|
||||
open CrisisViz.app
|
||||
|
||||
# 3. Verify the testbed before committing curriculum changes
|
||||
swift run CrisisViz --testbed
|
||||
# read ~/Desktop/CrisisViz_Testbed/INVARIANTS.md, SOURCE_AUDIT.md,
|
||||
# VIDEO_CLIPS.md, MANIFEST.md, SANITY.md — all must be green
|
||||
|
||||
# 4. For distribution
|
||||
./package-dmg.sh
|
||||
# produces CrisisViz.dmg, prints SHA-256
|
||||
```
|
||||
|
||||
Requires macOS 26 (Tahoe), swift-tools-version 6.2, .macOS(.v26).
|
||||
If something feels broken, check in this order: (1) is `crisis_data.json` present and non-empty? (2) does `swift build` succeed cleanly? (3) does the dev binary `swift run CrisisViz` produce a window? (4) does `./bundle.sh` succeed and `open CrisisViz.app` show the same window with a Dock icon? (5) does the testbed run to completion?
|
||||
|
|
|
|||
Loading…
Reference in a new issue