miriad-viz 0.13.0 → 0.13.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -114,7 +114,8 @@ function generateVizCuration(script, clipDurations, densityInput, projectStart,
114
114
  speaker: line.speaker,
115
115
  text: line.text,
116
116
  progress: positions[i],
117
- duration: DEFAULT_PILL_DURATION
117
+ duration: DEFAULT_PILL_DURATION,
118
+ anchor: true
118
119
  });
119
120
  }
120
121
  }
package/dist-cli/index.js CHANGED
@@ -242,8 +242,9 @@ function buildCurationTable(narration, pills, densityInput, projectStart, projec
242
242
  output.push(` \u{1F4AC} ${pills.length} pills placed:`);
243
243
  for (const p of pills) {
244
244
  const truncText = p.text.length > 40 ? `${p.text.slice(0, 37)}...` : p.text;
245
+ const anchorTag = p.anchor ? " [anchor]" : "";
245
246
  output.push(
246
- ` @ ${p.progress.toFixed(1)} (${p.duration.toFixed(1)}s): ${p.speaker} \u2014 "${truncText}"`
247
+ ` @ ${p.progress.toFixed(1)} (${p.duration.toFixed(1)}s): ${p.speaker} \u2014 "${truncText}"${anchorTag}`
247
248
  );
248
249
  }
249
250
  }
@@ -480,45 +481,20 @@ function buildVizTimingInProgress(_progress, densityInput, scriptLines, projectS
480
481
  output.push(" 2.0 = viz covers project time 2x faster (LESS screen time)");
481
482
  output.push(" 0.5 = viz covers project time 2x slower (MORE screen time)");
482
483
  output.push("");
483
- output.push(
484
- " Also edit pacing.json for fine-grained control \u2014 you can change ONLY these 4 fields per line:"
485
- );
486
- output.push(" pauseAfter \u2014 seconds of silence after this clip (default: 0.5)");
487
- output.push(" vizSpeed \u2014 viz speed DURING this clip (default: 1.0)");
488
- output.push(
489
- " gapVizSpeed \u2014 viz speed BETWEEN this clip and the next (default: same as defaultVizSpeed)"
490
- );
491
- output.push(" note \u2014 your reasoning (for yourself, not rendered)");
492
- output.push("");
493
- output.push(" vizSpeed is a MULTIPLIER on project-time-per-second:");
494
- output.push(" 1.0 = default rate");
495
- output.push(" 2.0 = viz covers project time 2x faster (LESS screen time)");
496
- output.push(" 0.5 = viz covers project time 2x slower (MORE screen time)");
497
- output.push("");
498
- output.push(" HOW SPEED FIELDS MAP TO THE TIMELINE:");
499
- output.push(" \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u250C\u2500\u2500\u2500\u2500\u2500\u2510 \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u250C\u2500\u2500\u2500\u2500\u2500\u2510");
500
- output.push(" \u2502 narrator-01 clip\u2502 \u2502 gap \u2502 \u2502 narrator-02 clip\u2502 \u2502 gap \u2502");
501
- output.push(" \u2502 vizSpeed: 1.0 \u2502 \u2502 2.0 \u2502 \u2502 vizSpeed: 0.5 \u2502 \u2502 1.0 \u2502");
502
- output.push(" \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2514\u2500\u2500\u2500\u2500\u2500\u2518 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2514\u2500\u2500\u2500\u2500\u2500\u2518");
503
- output.push(" \u2191 speed DURING clip \u2191 gapVizSpeed \u2191 speed DURING clip \u2191 gapVizSpeed");
504
- output.push(" (audio playing) (silence/pause) (audio playing) (silence/pause)");
505
- output.push("");
506
- output.push(" SPEED REASONING (creative choice, not formula):");
507
- output.push(" Dead time (nothing happening) \u2192 high gapVizSpeed (skip through it)");
508
- output.push(" Lots of action \u2192 normal or slow vizSpeed (let viewer see the frenzy)");
509
- output.push(" Dramatic reveal \u2192 big pauseAfter (tension before the next line)");
510
- output.push(" These are guidelines. Trust your creative judgment.");
484
+ output.push(" HOW TO CONTROL PACING:");
485
+ output.push(" Move progress points CLOSER together \u2192 smaller gap \u2192 lower vizSpeed \u2192 slower");
486
+ output.push(" Move progress points FURTHER apart \u2192 larger gap \u2192 higher vizSpeed \u2192 faster");
487
+ output.push(" Dense areas (lots of action) \u2192 cluster narration points (smaller gaps)");
488
+ output.push(" Dead zones (nothing happening) \u2192 space out narration points (larger gaps)");
511
489
  output.push("");
512
490
  output.push(" AFTER EVERY EDIT, run ONE command:");
513
- output.push(" npx miriad-viz preview --from-pacing");
491
+ output.push(" npx miriad-viz preview --from-curation");
514
492
  output.push("");
515
493
  output.push(" This auto-runs: generate-timing \u2192 transform \u2192 preview (all three).");
516
- output.push(" You edit ONE file, run ONE command, see the result.");
494
+ output.push(" You edit ONE file (viz-curation.json), run ONE command, see the result.");
517
495
  output.push("");
518
496
  output.push(" Then ask the human to watch the preview and give feedback.");
519
- output.push(
520
- " Edit viz-curation.json or pacing.json based on feedback \u2192 run the command again \u2192 repeat."
521
- );
497
+ output.push(" Edit viz-curation.json based on feedback \u2192 run the command again \u2192 repeat.");
522
498
  output.push("");
523
499
  output.push(" When human approves: npx miriad-viz next --timing-approved");
524
500
  const vizTimingDoc = readBundledDoc("miriad-viz-viz-timing");
@@ -534,7 +510,7 @@ function buildVizTimingApprovalError() {
534
510
  return [
535
511
  "",
536
512
  "\u2717 Cannot approve timing \u2014 no timing.json found.",
537
- " Run `npx miriad-viz preview --from-pacing` first to generate timing.json,",
513
+ " Run `npx miriad-viz preview --from-curation` first to generate timing.json,",
538
514
  " then approve with: npx miriad-viz next --timing-approved"
539
515
  ];
540
516
  }
@@ -819,28 +795,38 @@ function computeNext(progress, existingFiles, logTails, flags = {}, dataSummary,
819
795
  }
820
796
  output.push("", " Run `npx miriad-viz curate` to generate scaffolds.");
821
797
  output.push("");
822
- output.push(" Remember: the scaffold gives you RAW events (activity density).");
823
- output.push(" You must ADD milestone events with labels at narration-aligned timestamps.");
824
- output.push(" Each narration line needs at least one labeled event in its time range.");
825
- output.push(
826
- " Raw messages (messages-full.json) are your SOURCE \u2014 read them to find content."
827
- );
828
- output.push(" timeline-events.json is your OUTPUT \u2014 the chat pills that appear on screen.");
829
- output.push("");
830
- output.push(" Edit viz-curation.json for narration placement + chat pills:");
831
- output.push(" \u2022 narration array \u2014 progress points with clipDuration");
832
- output.push(" \u2022 pills array \u2014 progress + duration + speaker + text");
833
- output.push(" Run `npx miriad-viz next` again to see updated vizSpeed and table.");
798
+ if (vizCuration && vizCuration.narration.length > 0) {
799
+ output.push(" Edit viz-curation.json for narration placement + chat pills:");
800
+ output.push(" \u2022 narration array \u2014 progress points with clipDuration");
801
+ output.push(" \u2022 pills array \u2014 progress + duration + speaker + text");
802
+ output.push(" \u2022 Anchor pills (auto-generated from quotes) are marked [anchor]");
803
+ output.push(" Run `npx miriad-viz next` again to see updated vizSpeed and table.");
804
+ } else {
805
+ output.push(" Remember: the scaffold gives you RAW events (activity density).");
806
+ output.push(" You must ADD milestone events with labels at narration-aligned timestamps.");
807
+ output.push(" Each narration line needs at least one labeled event in its time range.");
808
+ output.push(
809
+ " Raw messages (messages-full.json) are your SOURCE \u2014 read them to find content."
810
+ );
811
+ output.push(
812
+ " timeline-events.json is your OUTPUT \u2014 the chat pills that appear on screen."
813
+ );
814
+ output.push("");
815
+ output.push(" Edit viz-curation.json for narration placement + chat pills:");
816
+ output.push(" \u2022 narration array \u2014 progress points with clipDuration");
817
+ output.push(" \u2022 pills array \u2014 progress + duration + speaker + text");
818
+ output.push(" Run `npx miriad-viz next` again to see updated vizSpeed and table.");
819
+ }
834
820
  output.push("", " When done: npx miriad-viz next");
835
821
  return { action: "waiting_for_edit", step, progress, output };
836
822
  }
837
823
  output.push("", "\u{1F4CB} Curate Editorial Content");
838
824
  output.push("");
839
825
  output.push(" This step generates scaffold files for editorial content:");
826
+ output.push(" \u2022 viz-curation.json \u2014 unified narration + pills (PRIMARY editing target)");
840
827
  output.push(" \u2022 retro-page-data.json \u2014 phases, milestones, featured quotes, narration");
841
828
  output.push(" \u2022 retro-page-quotes.json \u2014 additional standout quotes");
842
- output.push(" \u2022 timeline-events.json \u2014 scaffolded from extracted data (YOU MUST EDIT THIS)");
843
- output.push(" \u2022 viz-curation.json \u2014 unified narration + pills (progress-based)");
829
+ output.push(" \u2022 timeline-events.json \u2014 legacy/retro-page format (optional, for retro page)");
844
830
  output.push("");
845
831
  output.push(" \u26A0\uFE0F Three concepts \u2014 understand these before editing:");
846
832
  output.push(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
@@ -868,6 +854,7 @@ function computeNext(progress, existingFiles, logTails, flags = {}, dataSummary,
868
854
  output.push(" - Cluster narration in dense areas, space out in dead zones");
869
855
  output.push(" - ADD 15-20 milestone events with labels as pills");
870
856
  output.push(' - Each pill: { speaker: "...", text: "...", progress: N, duration: N }');
857
+ output.push(" - Auto-generated anchor pills are marked with anchor: true");
871
858
  output.push(" - Goal: every narration line should have related pills nearby");
872
859
  output.push(" - Anchors shown in next output \u2014 place related pills nearby");
873
860
  output.push(" - Run next again to see updated vizSpeed and table");
@@ -882,7 +869,7 @@ function computeNext(progress, existingFiles, logTails, flags = {}, dataSummary,
882
869
  output.push("");
883
870
  output.push(" \u2501\u2501\u2501 SCHEMA EXAMPLES (copy these as starting templates) \u2501\u2501\u2501");
884
871
  output.push("");
885
- output.push(" \u{1F4C4} viz-curation.json:");
872
+ output.push(" \u{1F4C4} viz-curation.json (PRIMARY \u2014 edit this):");
886
873
  output.push(" ```json");
887
874
  output.push(" {");
888
875
  output.push(' "meta": { "totalClipDuration": 226.5, "totalProgress": 100,');
@@ -899,7 +886,7 @@ function computeNext(progress, existingFiles, logTails, flags = {}, dataSummary,
899
886
  output.push(" ],");
900
887
  output.push(' "pills": [');
901
888
  output.push(' { "speaker": "snorre", "text": "THE NEW CLIP DOES NOT HAVE SOUND",');
902
- output.push(' "progress": 7.1, "duration": 2.0 },');
889
+ output.push(' "progress": 7.1, "duration": 2.0, "anchor": true },');
903
890
  output.push(' { "speaker": "bob", "text": "rendering now with narration scale 3.5",');
904
891
  output.push(' "progress": 8.0, "duration": 1.5 }');
905
892
  output.push(" ]");
@@ -908,8 +895,11 @@ function computeNext(progress, existingFiles, logTails, flags = {}, dataSummary,
908
895
  output.push(" \u26A0\uFE0F narration.progress is a SINGLE number (0-100), not a range.");
909
896
  output.push(" \u26A0\uFE0F pills are a SEPARATE top-level array, not nested under narration lines.");
910
897
  output.push(" \u26A0\uFE0F vizSpeed is COMPUTED (gap/clipDuration) \u2014 shown in the table, not stored.");
898
+ output.push(
899
+ " \u26A0\uFE0F anchor: true marks auto-generated pills \u2014 add your own pills WITHOUT anchor."
900
+ );
911
901
  output.push("");
912
- output.push(" \u{1F4C4} timeline-events.json:");
902
+ output.push(" \u{1F4C4} timeline-events.json (legacy \u2014 for retro page only):");
913
903
  output.push(" ```json");
914
904
  output.push(" [");
915
905
  output.push(' { "type": "message", "t": "2026-02-20T14:30:00Z", "from": "bob",');
@@ -2095,7 +2085,7 @@ async function main() {
2095
2085
  case "scaffold-curation": {
2096
2086
  const { projectDir, progress } = requireProject();
2097
2087
  const { ScriptFileSchema } = await import("./script-V3LKE4ZU.js");
2098
- const { generateVizCuration } = await import("./generate-viz-curation-KRFC2ZSB.js");
2088
+ const { generateVizCuration } = await import("./generate-viz-curation-R6SUTONK.js");
2099
2089
  const { readAudioDurations } = await import("./read-audio-durations-TIGU4A7K.js");
2100
2090
  const { writeFileSync, mkdirSync: mkdirSync2 } = await import("fs");
2101
2091
  const dataDir = resolve3(projectDir, progress.project.dataDir);
@@ -133,19 +133,19 @@ See [Voice Generation Guide](./miriad-viz-voice-generation.md) for the full guid
133
133
 
134
134
  See [Viz Timing Guide](./miriad-viz-viz-timing.md) for the full guide.
135
135
 
136
- **This is where the video's rhythm is crafted.** You scaffold a pacing file, annotate speed and pauses, and iterate with the human watching the preview.
136
+ **This is where the video's rhythm is crafted.** You scaffold a curation file, adjust narration placement and pills, and iterate with the human watching the preview.
137
137
 
138
138
  **The iterate loop:**
139
139
  ```bash
140
- # 1. Scaffold pacing file (once)
141
- npx miriad-viz@latest scaffold-pacing
140
+ # 1. Scaffold curation file (once)
141
+ npx miriad-viz@latest scaffold-curation
142
142
 
143
- # 2. Edit pacing.json, then run ONE command:
144
- npx miriad-viz@latest preview --from-pacing
143
+ # 2. Edit viz-curation.json, then run ONE command:
144
+ npx miriad-viz@latest preview --from-curation
145
145
  # This auto-chains: generate-timing → transform → preview
146
146
  ```
147
147
 
148
- You edit ONE file (`pacing.json`), run ONE command, see the result. Repeat until the human approves.
148
+ You edit ONE file (`viz-curation.json`), run ONE command, see the result. Repeat until the human approves.
149
149
 
150
150
  **Timeline padding:**
151
151
  ```bash
@@ -185,7 +185,7 @@ See [Video Export](./miriad-viz-video.md) for the full guide. Mechanical step
185
185
  | Extract (git) | `npx miriad-viz@latest extract &> extract.log &` |
186
186
  | Extract (chat) | `./extract-channel.sh ./raw-data &> chat-extract.log &` |
187
187
  | Transform | `npx miriad-viz@latest transform &> transform.log &` |
188
- | Preview (from pacing) | `npx miriad-viz@latest preview --from-pacing &> preview.log &` |
188
+ | Preview (from curation) | `npx miriad-viz@latest preview --from-curation &> preview.log &` |
189
189
  | Render (video) | `pnpm render &> render.log &` |
190
190
 
191
191
  Pattern: start in background → set alarm (1-min for extract/transform, 3-min for render) → poll + report → repeat until done.
@@ -4,10 +4,11 @@
4
4
 
5
5
  Place narration on the timeline and decide how fast the viz moves. This is where the video's **pacing** is crafted — the rhythm of fast and slow, tension and release.
6
6
 
7
- Voice clip durations are fixed facts (from Step 3). You control **three things:**
8
- 1. How long to pause between lines (`pauseAfter`)
9
- 2. How fast the viz moves during a line (`vizSpeed`)
10
- 3. How fast the viz moves during pauses (`gapVizSpeed`)
7
+ Voice clip durations are fixed facts (from Step 3). You control **two things:**
8
+ 1. Where each narration line sits on the 0-100 progress scale (`progress`)
9
+ 2. Which chat pills appear and where (`pills` array)
10
+
11
+ vizSpeed is **computed mechanically**: `gap / clipDuration`. You control it indirectly by moving progress points closer (slower) or further apart (faster).
11
12
 
12
13
  ## Files
13
14
 
@@ -15,131 +16,131 @@ Voice clip durations are fixed facts (from Step 3). You control **three things:*
15
16
  |------|-----------|-------------|
16
17
  | `data/script.json` | Read | The narrative script |
17
18
  | `audio/*.mp3` | Read | Voice clips (durations measured automatically) |
18
- | `data/pacing.json` | **Write** | Your pacing annotations |
19
- | `data/timing.json` | Generated | Engine-facing timing (generated by `generate-timing`) |
20
- | `output/viz-data.json` | Generated | Final viz data (generated by `transform`) |
19
+ | `data/viz-curation.json` | **Write** | Your narration placement + chat pills |
20
+ | `data/timing.json` | Generated | Engine-facing timing (generated by transform) |
21
+ | `output/viz-data.json` | Generated | Final viz data (generated by transform) |
21
22
 
22
23
  ## Getting Started
23
24
 
24
- ### 1. Scaffold the Pacing File
25
+ ### 1. Scaffold the Curation File
25
26
 
26
27
  ```bash
27
- npx miriad-viz@latest scaffold-pacing
28
+ npx miriad-viz@latest scaffold-curation
28
29
  ```
29
30
 
30
- This reads `script.json` + measures audio clip durations → generates `pacing.json` with all defaults filled in. The output is ready to preview immediately — you only edit where you want to deviate from defaults.
31
+ This reads `script.json` + measures audio clip durations → generates `viz-curation.json` with density-proportional defaults. The output is ready to preview immediately — you only edit where you want to deviate from defaults.
31
32
 
32
- ### 2. Understand the Pacing File
33
+ ### 2. Understand the Curation File
33
34
 
34
35
  ```json
35
36
  {
36
- "version": 1,
37
- "leadInSec": 1.0,
38
- "tailOutSec": 2.0,
39
- "defaultPauseSec": 0.5,
40
- "defaultVizSpeed": 1.0,
41
- "lines": [
42
- { "id": "narrator-01", "text": "February 9th. Midnight.", "clipDuration": 3.2 },
43
- { "id": "narrator-02", "text": "The team had been shipping for 12 hours.",
44
- "clipDuration": 4.8, "vizSpeed": 1.5, "pauseAfter": 2.0, "gapVizSpeed": 0.5,
45
- "note": "montage of commits, then let it breathe" },
46
- { "id": "lead-03", "text": "Adding agents is free.", "clipDuration": 2.1 },
47
- { "id": "snorre-04", "text": "Full throttle.", "clipDuration": 1.4,
48
- "vizSpeed": 2.0, "pauseAfter": 3.0, "gapVizSpeed": 0.3,
49
- "note": "dramatic silence before crisis reveal" },
50
- { "id": "narrator-05", "text": "Then everything broke.", "clipDuration": 3.6,
51
- "vizSpeed": 0.5, "note": "slow down for impact" }
37
+ "meta": { "totalClipDuration": 226.5, "totalProgress": 100,
38
+ "phases": [{ "id": "bootstrap", "label": "Bootstrap", "progress": [0, 22] }] },
39
+ "narration": [
40
+ { "id": "narrator-01", "type": "narrator", "speaker": "narrator",
41
+ "text": "February 9th. Midnight.", "clipDuration": 3.2, "progress": 3.5 },
42
+ { "id": "narrator-02", "type": "narrator", "speaker": "narrator",
43
+ "text": "The team had been shipping for 12 hours.",
44
+ "clipDuration": 4.8, "progress": 8.3 },
45
+ { "id": "snorre-04", "type": "quote", "speaker": "snorre",
46
+ "text": "Full throttle.", "clipDuration": 1.4, "progress": 45.0 }
47
+ ],
48
+ "pills": [
49
+ { "speaker": "snorre", "text": "Full throttle.", "progress": 45.0,
50
+ "duration": 2.0, "anchor": true },
51
+ { "speaker": "bob", "text": "rendering now", "progress": 46.5,
52
+ "duration": 1.5 }
52
53
  ]
53
54
  }
54
55
  ```
55
56
 
56
- ### What You Can Edit (4 fields per line)
57
+ ### What You Can Edit
58
+
59
+ **Narration array** — adjust `progress` (0-100) to control where each line plays:
60
+ - Move points **closer together** → smaller gap → lower vizSpeed → slower playback
61
+ - Move points **further apart** → larger gap → higher vizSpeed → faster playback
57
62
 
58
- | Field | Default | Description |
59
- |-------|---------|-------------|
60
- | `pauseAfter` | `defaultPauseSec` | Seconds of silence after this line |
61
- | `vizSpeed` | `defaultVizSpeed` | Viz speed multiplier DURING this clip |
62
- | `gapVizSpeed` | `defaultVizSpeed` | Viz speed multiplier DURING the pause after this clip |
63
- | `note` | | Your reasoning (not rendered, just for you and the human) |
63
+ **Pills array** add/edit/remove chat pills:
64
+ - `speaker` — who says it
65
+ - `text` what appears on screen (can be fabricated)
66
+ - `progress` where it appears (0-100)
67
+ - `duration` how long it stays visible (in progress-space units)
68
+ - Pills with `anchor: true` are auto-generated from quote lines add your own WITHOUT anchor
64
69
 
65
70
  ### What You Must NOT Edit (read-only)
66
71
 
67
72
  | Field | Why |
68
73
  |-------|-----|
69
74
  | `id` | Matches script.json and audio filenames |
70
- | `text` | From script.json — shown so you can reason about content → pacing |
75
+ | `text` (narration) | From script.json — shown so you can reason about content |
71
76
  | `clipDuration` | Measured from audio clips — this is a fact, not a choice |
77
+ | `meta` | Computed by the lib — phases, totalClipDuration |
72
78
 
73
- ### Global Settings
79
+ ## How vizSpeed Works
74
80
 
75
- | Field | Description |
76
- |-------|-------------|
77
- | `leadInSec` | Silence before the first line (intro breathing room) |
78
- | `tailOutSec` | Silence after the last line (outro) |
79
- | `defaultPauseSec` | Default pause between lines (when `pauseAfter` is absent) |
80
- | `defaultVizSpeed` | Default viz speed (when `vizSpeed`/`gapVizSpeed` are absent) |
81
-
82
- ## How Speed Works
81
+ vizSpeed is **computed**, not set directly:
83
82
 
84
83
  ```
85
- ┌─────────────────┐ ┌─────┐ ┌─────────────────┐ ┌─────┐
86
- │ narrator-01 clip│ │ gap │ │ narrator-02 clip│ │ gap │
87
- │ vizSpeed: 1.0 │ 2.0 │ vizSpeed: 0.5 │ 1.0 │
88
- └─────────────────┘ └─────┘ └─────────────────┘ └─────┘
89
- ↑ speed DURING clip ↑ gapVizSpeed ↑ speed DURING clip ↑ gapVizSpeed
90
- (audio playing) (silence/pause) (audio playing) (silence/pause)
84
+ vizSpeed = gap / clipDuration
85
+
86
+ gap = distance to next narration point in progress-space
91
87
  ```
92
88
 
89
+ **Example:**
90
+ - narrator-01 at progress 3.5, clipDuration 5.0s
91
+ - narrator-02 at progress 8.3
92
+ - gap = 8.3 - 3.5 = 4.8
93
+ - vizSpeed = 4.8 / 5.0 = 0.96x
94
+
93
95
  **vizSpeed is a multiplier on project-time-per-second:**
94
96
  - `1.0` = default rate
95
97
  - `2.0` = viz covers project time 2× faster → **less screen time** (fast-forward)
96
98
  - `0.5` = viz covers project time 2× slower → **more screen time** (slow motion)
97
99
 
98
- **Key insight:** higher vizSpeed = faster = less screen time. Lower = slower = more screen time. The timing preview table (printed by `generate-timing`) shows concrete screen-time-per-line numbers so you don't have to do the math.
99
-
100
- **`gapVizSpeed` defaults to `defaultVizSpeed`, NOT to the line's `vizSpeed`.** This is intentional — a slow dramatic line (`vizSpeed: 0.5`) shouldn't automatically make its pause slow too. The pause speed is a separate creative choice.
100
+ **Key insight:** wider gaps = higher vizSpeed = faster. Narrower gaps = lower vizSpeed = slower. The curation table (shown by `next`) displays concrete vizSpeed per line so you don't have to do the math.
101
101
 
102
102
  ## The Iterate Loop
103
103
 
104
- After every edit to `pacing.json`, run ONE command:
104
+ After every edit to `viz-curation.json`, run ONE command:
105
105
 
106
106
  ```bash
107
- npx miriad-viz@latest preview --from-pacing
107
+ npx miriad-viz@latest preview --from-curation
108
108
  ```
109
109
 
110
110
  This auto-chains three operations:
111
- 1. `generate-timing` — computes `timing.json` from pacing + project time range
111
+ 1. Derives `timing.json` from viz-curation.json + project time range
112
112
  2. `transform` — produces `viz-data.json` with timing-aware narration
113
113
  3. Opens the preview viewer
114
114
 
115
- **You edit ONE file, run ONE command, see the result.** The three-step chain is an implementation detail.
115
+ **You edit ONE file (viz-curation.json), run ONE command, see the result.** The three-step chain is an implementation detail.
116
116
 
117
- ### The Timing Preview Table
117
+ ### The Curation Table
118
118
 
119
- `generate-timing` prints a table showing concrete screen time per line:
119
+ `next` prints a table showing computed vizSpeed and density per line:
120
120
 
121
121
  ```
122
- narrator-01 (3.2s clip + 0.5s pause) covers 2h at 1.0x = 10.4s screen
123
- narrator-02 (4.8s clip + 2.0s pause) → covers 4h at 0.5x = 52.8s screen ⚠️ LONG
124
- lead-03 (2.1s clip + 0.5s pause) → covers 1h at 1.0x = 5.2s screen
125
- Total: 4:32 video
122
+ LINE | CLIP | PROGRESS | GAP | vizSpeed | MSGS | COMMITS
123
+ narrator-01 | 5.2s | @ 3.5 | 3.6 | 0.69x | 182 | 12
124
+ snorre-01 | 3.2s | @ 7.1 | 4.9 | 1.53x | 47 | 3
125
+ narrator-02 | 4.1s | @ 12.0 | 16.0 | 3.90x | 0 | 0 ← dead zone
126
126
  ```
127
127
 
128
128
  Use this to catch problems **before watching the preview:**
129
- - `⚠️ LONG` — this segment takes a lot of screen time. Is that intentional?
130
- - Very short segmentswill the audience have time to read the narration?
131
- - Total durationdoes the video length feel right for the content?
129
+ - `← dead zone` — zero activity in this region. Space out narration (larger gaps) to skip through it.
130
+ - High vizSpeed (> 3x) — the viz is moving very fast here. Is that intentional?
131
+ - Low vizSpeed (< 0.5x) — the viz is very slow. Enough content to fill the screen time?
132
+ - Anchors — quote-type lines auto-generate pills. Place related pills nearby.
132
133
 
133
- ## Speed Reasoning — Creative Choice, Not Formula
134
+ ## Pacing Reasoning — Creative Choice, Not Formula
134
135
 
135
- The `next` output shows **event density** per script line — how many PRs, commits, and messages happened during each line's time range. Use this as input, not as a rule:
136
+ The `next` output shows **event density** per narration line — how many messages and commits happened in each line's progress window. Use this as input, not as a rule:
136
137
 
137
138
  **Guidelines (not rules):**
138
- - **Dead time** (few events, nothing happening) → high `gapVizSpeed` (skip through it)
139
- - **Intense action** (many PRs, message bursts) → normal or low `vizSpeed` (let the viewer see the frenzy)
140
- - **Dramatic reveal** → big `pauseAfter` (tension before the next line)
141
- - **Emotional moment** → low `vizSpeed` (slow down for impact)
142
- - **Montage** → high `vizSpeed` during clip (time-lapse of activity)
139
+ - **Dead zones** (zero events) → space out narration points (larger gaps, higher vizSpeed)
140
+ - **Dense areas** (many events) → cluster narration points (smaller gaps, lower vizSpeed)
141
+ - **Dramatic reveal** → place narration points close together before the reveal (slow buildup)
142
+ - **Emotional moment** → narrow gap (low vizSpeed, slow down for impact)
143
+ - **Montage** → wide gap (high vizSpeed, time-lapse of activity)
143
144
 
144
145
  **Trust your creative judgment.** The data tells you what happened. You and the human decide how it should feel. A quiet period might deserve slow pacing if it's the calm before the storm. A busy period might deserve fast pacing if it's routine work.
145
146
 
@@ -148,23 +149,15 @@ The `next` output shows **event density** per script line — how many PRs, comm
148
149
  Share the preview link and watch together. The human evaluates:
149
150
 
150
151
  - **Does the pacing feel right?** Too fast? Too slow? Uneven?
151
- - **Do the pauses land?** Is there enough silence before dramatic moments?
152
+ - **Do the pills land?** Are they readable? Too many? Too few?
152
153
  - **Does the narration sync with the visuals?** When the narrator says "then everything broke," is the viz showing the crisis?
153
154
  - **Is the total duration right?** Too long for the content? Too short to absorb?
154
155
 
155
156
  Common feedback and how to respond:
156
- - *"Slow down the middle section"* → lower `vizSpeed` on those lines
157
- - *"The pause before the crisis isn't dramatic enough"* → increase `pauseAfter`
158
- - *"Skip through the overnight period faster"* → increase `gapVizSpeed` on lines covering that range
159
- - *"The whole thing is too long"* → increase `defaultVizSpeed` globally, or increase specific `gapVizSpeed` values
160
-
161
- ## Validation
162
-
163
- `generate-timing` validates the pacing file and warns about:
164
- - Total wall-clock time < 30s or > 30min (unusual for typical scripts)
165
- - Viz coverage < 50% (narration only covers half the timeline — intentional?)
166
- - Negative values (invalid)
167
- - Missing line IDs (script.json has lines not in pacing.json)
157
+ - *"Slow down the middle section"* → move narration points closer together in that region
158
+ - *"Skip through the overnight period faster"* → space out narration points in that region
159
+ - *"Add a pill when Bob says that"* → add a pill to the pills array at the right progress point
160
+ - *"The whole thing is too long"* → space out narration points globally (wider gaps)
168
161
 
169
162
  ## What You Cannot Control (v1)
170
163
 
@@ -172,12 +165,12 @@ Be honest with the human about these boundaries:
172
165
 
173
166
  | Cannot control | Why | Future |
174
167
  |---|---|---|
175
- | **Visual emphasis** — can't zoom into an agent, hide a band, or highlight a node at a specific moment | Viz is a pure function of progress. No per-moment visual overrides. | Camera directives in pacing file |
176
- | **Cross-fades between lines** — every line cuts to the next, no overlap or blend | Audio clips are discrete files, no mixing in v1 | Crossfade field on PacingLine |
177
- | **What data appears** — can't add/remove agents, PRs, or events from the viz | Data comes from extraction, not pacing | Selective data filtering |
168
+ | **Visual emphasis** — can't zoom into an agent, hide a band, or highlight a node at a specific moment | Viz is a pure function of progress. No per-moment visual overrides. | Camera directives |
169
+ | **Cross-fades between lines** — every line cuts to the next, no overlap or blend | Audio clips are discrete files, no mixing in v1 | Crossfade support |
170
+ | **What data appears** — can't add/remove agents, PRs, or events from the viz | Data comes from extraction, not curation | Selective data filtering |
178
171
  | **Layout or visual style** — can't change colors, positions, or rendering | Renderer is fixed, driven by viz-data | Style overrides per phase |
179
172
 
180
- You control **pacing** (how fast time moves) and **audio** (what you hear). The visual content is determined by the extracted data and the renderer. This is by design — it keeps the pacing file simple and the iterate loop fast.
173
+ You control **pacing** (via narration placement) and **pills** (what text appears on screen). The visual content is determined by the extracted data and the renderer. This is by design — it keeps the curation file simple and the iterate loop fast.
181
174
 
182
175
  ## When to Stop for Human Review
183
176
 
@@ -187,4 +180,4 @@ You control **pacing** (how fast time moves) and **audio** (what you hear). The
187
180
  npx miriad-viz@latest next --timing-approved
188
181
  ```
189
182
 
190
- **Timing is now locked.** Step 5 (Sound Design) adds SFX and music on top of the locked timing. Changes to pacing after this point require going back to this step.
183
+ **Timing is now locked.** Step 5 (Sound Design) adds SFX and music on top of the locked timing. Changes to curation after this point require going back to this step.
@@ -21,7 +21,7 @@ echo $ELEVENLABS_API_KEY
21
21
 
22
22
  **If missing:** Tell the human: "ElevenLabs API key not configured. To add narration, set `ELEVENLABS_API_KEY` in your environment. Without it, I can skip voices — the viz will use estimated durations based on text length, and the video will be silent."
23
23
 
24
- **No API key is not a blocker.** The pipeline continues without audio — `scaffold-pacing` estimates clip durations from text length, and the viz renders with text-only narration. Audio can be added later.
24
+ **No API key is not a blocker.** The pipeline continues without audio — `scaffold-curation` estimates clip durations from text length, and the viz renders with text-only narration. Audio can be added later.
25
25
 
26
26
  ## The Filename Convention
27
27
 
@@ -35,7 +35,7 @@ script.json line: { "id": "lead-03", ... }
35
35
  audio file: audio/lead-03.mp3
36
36
  ```
37
37
 
38
- `scaffold-pacing` (Step 4) looks up clips by this convention. Wrong filenames = missing audio = broken pacing.
38
+ `scaffold-curation` (Step 4) looks up clips by this convention. Wrong filenames = missing audio = broken timing.
39
39
 
40
40
  ## Workflow
41
41
 
@@ -103,7 +103,7 @@ After generating all clips, measure their durations:
103
103
  ffprobe -v quiet -show_entries format=duration -of csv=p=0 audio/narrator-01.mp3
104
104
  ```
105
105
 
106
- You don't need to record these manually — `scaffold-pacing` (Step 4) reads them automatically from the audio files.
106
+ You don't need to record these manually — `scaffold-curation` (Step 4) reads them automatically from the audio files.
107
107
 
108
108
  ### 5. Human Review
109
109
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "miriad-viz",
3
- "version": "0.13.0",
3
+ "version": "0.13.2",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/snorrees/miriad-viz.git"