@jokerized/decksmith 0.9.3 → 0.9.4

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.
Files changed (59) hide show
  1. package/README.md +449 -90
  2. package/dist/animate/LICENSE +21 -0
  3. package/dist/animate/NOTICE +32 -0
  4. package/dist/animate/core.js +530 -0
  5. package/dist/animate/cut-paper.js +206 -0
  6. package/dist/animate/morph.js +131 -0
  7. package/dist/cli.js +13368 -8864
  8. package/dist/ds-animate.js +1 -0
  9. package/dist/index.js +13182 -8678
  10. package/dist/mcp.js +9805 -7587
  11. package/dist/types/bespoke/art.d.ts +172 -6
  12. package/dist/types/bespoke/cache.d.ts +10 -0
  13. package/dist/types/bespoke/cards.d.ts +66 -0
  14. package/dist/types/bespoke/contract.d.ts +0 -8
  15. package/dist/types/bespoke/databuild.d.ts +50 -0
  16. package/dist/types/bespoke/depth.d.ts +127 -0
  17. package/dist/types/bespoke/grammar.d.ts +73 -0
  18. package/dist/types/bespoke/inspect.d.ts +40 -1
  19. package/dist/types/bespoke/pipeline.d.ts +195 -11
  20. package/dist/types/bespoke/probe.d.ts +2 -2
  21. package/dist/types/bespoke/prompt.d.ts +110 -34
  22. package/dist/types/bespoke/references.d.ts +13 -14
  23. package/dist/types/bespoke/repair.d.ts +23 -29
  24. package/dist/types/bespoke/scene.d.ts +55 -10
  25. package/dist/types/bespoke/select.d.ts +28 -21
  26. package/dist/types/bespoke/sheet.d.ts +66 -1
  27. package/dist/types/bespoke/shots.d.ts +225 -22
  28. package/dist/types/build/files.d.ts +7 -0
  29. package/dist/types/build/piece.d.ts +8 -0
  30. package/dist/types/emit/animate-runtime.d.ts +149 -0
  31. package/dist/types/emit/archetypes/claim-figure.d.ts +84 -1
  32. package/dist/types/emit/archetypes/equation-walk.d.ts +2 -2
  33. package/dist/types/emit/archetypes/hero-number.d.ts +21 -0
  34. package/dist/types/emit/archetypes/index.d.ts +2 -2
  35. package/dist/types/emit/archetypes/kinetic.d.ts +2 -0
  36. package/dist/types/emit/archetypes/pipeline.d.ts +14 -2
  37. package/dist/types/emit/archetypes/stage.d.ts +57 -0
  38. package/dist/types/emit/backdrop.d.ts +114 -0
  39. package/dist/types/emit/kit.d.ts +8 -0
  40. package/dist/types/emit/look.d.ts +14 -0
  41. package/dist/types/emit/motion.d.ts +78 -46
  42. package/dist/types/emit/type.d.ts +48 -5
  43. package/dist/types/images/providers.d.ts +6 -0
  44. package/dist/types/plan/codex.d.ts +4 -2
  45. package/dist/types/plan/direct.d.ts +1 -1
  46. package/dist/types/plan/duration.d.ts +1 -1
  47. package/dist/types/plan/prompt.d.ts +1 -1
  48. package/dist/types/plan/refs.d.ts +2 -2
  49. package/dist/types/plan/variety.d.ts +74 -0
  50. package/dist/types/prefs.d.ts +20 -1
  51. package/dist/types/render/captions.d.ts +4 -5
  52. package/dist/types/render/capture.d.ts +7 -0
  53. package/dist/types/types.d.ts +680 -4
  54. package/dist/types/verify/drift.d.ts +5 -0
  55. package/dist/types/verify/fidelity.d.ts +15 -1
  56. package/dist/types/verify/index.d.ts +17 -0
  57. package/dist/types/verify/scenes.d.ts +185 -5
  58. package/package.json +1 -1
  59. package/dist/types/bespoke/callouts.d.ts +0 -76
@@ -56,6 +56,13 @@ export declare function isWriting(t: ReadText): boolean;
56
56
  * language's pass). Prints one JSON line per file.
57
57
  */
58
58
  export declare const OCR_SWIFT = "import Foundation\nimport Vision\nimport ImageIO\nfunc run(_ img: CGImage, _ langs: [String]) -> [[String: Any]] {\n let req = VNRecognizeTextRequest()\n req.recognitionLevel = .accurate\n req.usesLanguageCorrection = false\n req.recognitionLanguages = langs\n try? VNImageRequestHandler(cgImage: img, options: [:]).perform([req])\n var out: [[String: Any]] = []\n for o in req.results ?? [] {\n guard let c = o.topCandidates(1).first else { continue }\n let b = o.boundingBox\n out.append([\"s\": c.string, \"c\": c.confidence, \"b\": [b.minX, 1 - b.maxY, b.width, b.height], \"l\": langs[0]])\n }\n return out\n}\nfor path in CommandLine.arguments.dropFirst() {\n guard let src = CGImageSourceCreateWithURL(URL(fileURLWithPath: path) as CFURL, nil),\n let img = CGImageSourceCreateImageAtIndex(src, 0, nil) else {\n print(\"{\\\"error\\\":\\\"unreadable\\\"}\"); continue\n }\n var out: [[String: Any]] = []\n for langs in [[\"en-US\"], [\"ko-KR\"], [\"ja-JP\"], [\"zh-Hans\"]] { out += run(img, langs) }\n let data = try! JSONSerialization.data(withJSONObject: [\"texts\": out])\n print(String(data: data, encoding: .utf8)!)\n}\n";
59
+ export declare function exec(bin: string, args: string[], timeoutMs: number): Promise<string>;
60
+ /**
61
+ * A compiled Swift helper, `<dir>/<name>-<hash of its source>`, compiled on
62
+ * first use (MEASURED ~25s once; then ~0.5s a picture). Rejects with the
63
+ * reason when this machine cannot build it — no macOS, no Swift toolchain.
64
+ */
65
+ export declare function swiftBinary(name: string, source: string, dir: string): Promise<string>;
59
66
  /** The writing in a picture; rejects (with why) when nothing here can read it. */
60
67
  export declare function readText(png: string, toolDir: string): Promise<ReadText[]>;
61
68
  /** Everything the inspection says about one picture. */
@@ -65,5 +72,37 @@ export interface Inspection {
65
72
  /** Writing found, or null when no reader was available (`unread` says why). */
66
73
  text: string[] | null;
67
74
  unread?: string;
75
+ /** The picture has a transparent ground (a subjects layer for a backdrop). */
76
+ cutout?: boolean;
77
+ /** The flattened copy the reader read, when the picture is a cutout. */
78
+ flatFile?: string;
68
79
  }
69
- export declare function inspectPicture(bytes: Buffer, file: string, toolDir: string): Promise<Inspection>;
80
+ /**
81
+ * Inspect a drawn picture. A subjects layer drawn on a transparent ground
82
+ * (round 5) is inspected as it will be seen against the pack's ground: laid
83
+ * on `ground` first, and the text reader reads that flattened copy, written
84
+ * beside `file` as `<file>.flat.png` (Vision reads a transparent pixel as
85
+ * black, which would hide dark writing).
86
+ */
87
+ export declare function inspectPicture(bytes: Buffer, file: string, toolDir: string, ground?: readonly [number, number, number]): Promise<Inspection>;
88
+ /** "#rrggbb" (or "#rgb") as RGB; mid grey for anything else. */
89
+ export declare function hexRgb(hex: string): [number, number, number];
90
+ /** Share of an RGBA picture's pixels that are (nearly) transparent, sampled. */
91
+ export declare function transparentShare(f: Frame): number;
92
+ /** An RGBA picture laid over a flat ground, as RGB. */
93
+ export declare function flattenOn(f: Frame, ground: readonly number[]): Frame;
94
+ /** A minimal PNG encoder (8-bit RGB or RGBA, filter 0): the repository decodes PNGs, and the text reader needs one written. */
95
+ export declare function encodePng(f: Frame): Buffer;
96
+ /**
97
+ * The Swift helper for REPETITION across a deck's pictures: Vision's image
98
+ * feature print (`VNGenerateImageFeaturePrintRequest`), a learned embedding of
99
+ * what a picture shows, local and free. Prints one JSON line per file with the
100
+ * print as a float array. Two pictures of the same friendly robot sit close
101
+ * whatever their colours; two different scenes sit far apart (calibration in
102
+ * `SIMILAR_MAX`).
103
+ */
104
+ export declare const PRINT_SWIFT = "import Foundation\nimport Vision\nimport ImageIO\nfor path in CommandLine.arguments.dropFirst() {\n guard let src = CGImageSourceCreateWithURL(URL(fileURLWithPath: path) as CFURL, nil),\n let img = CGImageSourceCreateImageAtIndex(src, 0, nil) else {\n print(\"{\\\"error\\\":\\\"unreadable\\\"}\"); continue\n }\n let req = VNGenerateImageFeaturePrintRequest()\n try? VNImageRequestHandler(cgImage: img, options: [:]).perform([req])\n guard let o = req.results?.first as? VNFeaturePrintObservation else {\n print(\"{\\\"error\\\":\\\"no print\\\"}\"); continue\n }\n var v = [Float](repeating: 0, count: o.elementCount)\n o.data.withUnsafeBytes { raw in\n let p = raw.bindMemory(to: Float.self)\n for i in 0..<min(o.elementCount, p.count) { v[i] = p[i] }\n }\n let data = try! JSONSerialization.data(withJSONObject: [\"v\": v.map { Double($0) }])\n print(String(data: data, encoding: .utf8)!)\n}\n";
105
+ /** Each picture's feature print; rejects when this machine has no Vision. */
106
+ export declare function featurePrints(pngs: readonly string[], toolDir: string): Promise<number[][]>;
107
+ /** Vision's distance between two prints (Euclidean, as `computeDistance`). */
108
+ export declare function printDistance(a: readonly number[], b: readonly number[]): number;
@@ -1,17 +1,20 @@
1
1
  import { type DeckNarration } from "../emit/composition.js";
2
- import { type Runner } from "../plan/codex.js";
2
+ import { type Runner, type RunnerArgs } from "../plan/codex.js";
3
3
  import type { Prefs } from "../prefs.js";
4
4
  import type { Beat, Format, Source, Storyboard } from "../types.js";
5
5
  import { type Layout } from "../verify/scenes.js";
6
- import { type ArtCheck, type ArtCopies } from "./art.js";
7
- import { type Label } from "./callouts.js";
6
+ import { type ArtCheck, type ArtCopies, type ArtRef, type DepthCopies } from "./art.js";
7
+ import { type DataBuild } from "./databuild.js";
8
+ import { type CutInput, type DepthInfo } from "./depth.js";
9
+ import { type Grammar, type Role } from "./grammar.js";
8
10
  import { type Inspection } from "./inspect.js";
9
- import { type Measured } from "./prompt.js";
11
+ import { type DeviceBeat, type Measured } from "./prompt.js";
10
12
  import { type RepairNote } from "./repair.js";
11
13
  import { type BespokeMap } from "./scene.js";
12
14
  import { type Skip } from "./select.js";
13
15
  import { type Shot } from "./shots.js";
14
16
  export type BespokePrefs = NonNullable<Prefs["bespoke"]>;
17
+ export type { DeviceBeat };
15
18
  /** What a gate round says about one beat's candidate scene. */
16
19
  export interface GateResult {
17
20
  /** One line per finding, fed verbatim to the critique round. */
@@ -31,7 +34,7 @@ export interface GateResult {
31
34
  sid?: string;
32
35
  }
33
36
  /** Which probe: the drafts, the final candidates, or a repair round's. */
34
- export type GateRound = "draft" | "final" | "repair";
37
+ export type GateRound = "draft" | "final" | "repair" | "second";
35
38
  /** Builds a deck with these candidates, gates each, and says what it found — keyed by beat id. */
36
39
  export type GateFn = (candidates: BespokeMap, round: GateRound) => Promise<Map<string, GateResult>>;
37
40
  export interface BespokeInput {
@@ -54,12 +57,20 @@ export interface BespokeInput {
54
57
  /** Where the image tool saves pictures. Default `$CODEX_HOME` or `~/.codex` (tests point it elsewhere). */
55
58
  codexHome?: string;
56
59
  /** Inspects a drawn picture (src/bespoke/inspect.ts); swappable so tests need no Swift or Vision. */
57
- inspect?: (bytes: Buffer, file: string) => Promise<Inspection>;
60
+ inspect?: (bytes: Buffer, file: string, ground?: [number, number, number]) => Promise<Inspection>;
58
61
  /** Draws the deck's WebP and the boxed copy (src/bespoke/sheet.ts); swappable so tests need no Chrome. */
59
62
  copies?: (png: Buffer, boxes: Inspection["subjects"], box: {
60
63
  width: number;
61
64
  height: number;
65
+ }, opts: {
66
+ plate?: Buffer;
67
+ ground?: string;
68
+ cutout?: boolean;
62
69
  }) => Promise<ArtCopies>;
70
+ /** Vision feature prints of pictures (src/bespoke/inspect.ts); swappable so tests need no Swift. */
71
+ prints?: (pngs: readonly string[]) => Promise<number[][]>;
72
+ /** Cuts a picture into depth planes (src/bespoke/depth.ts); swappable so tests need no Chrome or Core ML. */
73
+ cut?: (i: CutInput) => Promise<DepthCopies>;
63
74
  }
64
75
  export interface SceneReport {
65
76
  beat: string;
@@ -87,9 +98,35 @@ export interface SceneReport {
87
98
  depicts: string;
88
99
  subjects?: number;
89
100
  check?: ArtCheck;
101
+ /** Drawn with a backdrop (two layers), and the setting it shows. */
102
+ plate?: boolean;
103
+ setting?: string;
104
+ motifs?: string[];
105
+ /** Round 6: its depth planes' distances (far to near), the subjects', and how they were made. */
106
+ depth?: {
107
+ planes: number[];
108
+ subjects: number[];
109
+ scale: number;
110
+ info: DepthInfo;
111
+ };
90
112
  };
113
+ /** Given a picture by the device pass's floor, not by the model's choice (`pictureFloor`). */
114
+ pictureForced?: boolean;
115
+ /** The camera grammar the scene was staged in (src/bespoke/grammar.ts), and its rhetorical role. */
116
+ grammar?: string;
117
+ role?: string;
118
+ /** What the camera did, one word per move (`shotSummary`). */
119
+ shots?: string;
120
+ /** Picture pixels per output pixel at the scene's closest shot (`effectiveAt`; gate `EFF_MIN`). */
121
+ effective?: number;
122
+ /** A data scene's build (src/bespoke/databuild.ts). */
123
+ build?: string;
91
124
  /** A data beat: drawn as a chart, never on a picture. */
92
125
  data?: boolean;
126
+ /** The scene's visual device (`assignDevices`). */
127
+ device?: string;
128
+ /** A second critique call was made for it (round 4: its first critique's scene failed). */
129
+ second?: boolean;
93
130
  /** Why there is none, when one was wanted. */
94
131
  artNote?: string;
95
132
  /** What the deterministic repair did to the kept scene (src/bespoke/repair.ts). */
@@ -131,9 +168,28 @@ export interface BespokeReport {
131
168
  rejectedText: number;
132
169
  rejectedStyle: number;
133
170
  rejectedSubjects: number;
171
+ /** Pictures refused for repeating another picture of the deck (a motif, a stock subject, or a near-identical print). */
172
+ rejectedRepeat: number;
173
+ /** Pictures drawn with a backdrop (two layers). */
174
+ plates: number;
175
+ };
176
+ /**
177
+ * How much the deck's pictures repeat each other: the closest pair's
178
+ * Vision feature-print distance (`SIMILAR_MAX` is the gate) and the mean,
179
+ * and every subject noun two pictures share.
180
+ */
181
+ repetition?: {
182
+ minPrint?: number;
183
+ meanPrint?: number;
184
+ shared: string[];
134
185
  };
135
186
  /** Wall seconds of the pass's stages, for profiling (`build` adds its own). */
136
187
  stages?: Record<string, number>;
188
+ /** The deck-order device pass: where its names came from, and why any were replaced. */
189
+ devices?: {
190
+ from: DeviceAssignment["from"];
191
+ note?: string;
192
+ };
137
193
  scenes: SceneReport[];
138
194
  skipped: Skip[];
139
195
  }
@@ -141,12 +197,21 @@ export interface BespokeResult {
141
197
  map: BespokeMap;
142
198
  report: BespokeReport;
143
199
  }
200
+ /**
201
+ * What a cached verdict was reached under: the browser's gates and the static
202
+ * `card_row`. A rejection stamped otherwise is asked about again.
203
+ */
204
+ export declare const GATE_STAMP = "gates-7+cards-2:0.08/0.6/0.8/0.2/0.12";
205
+ /** A second critique starts only with this much of the wall-time cap left, seconds. */
206
+ export declare const SECOND_SECONDS = 420;
144
207
  /** Repair rounds after the final gates, each one probe build of the repaired scenes only. */
145
208
  export declare const REPAIR_ROUNDS = 2;
146
209
  export declare class Budget {
147
210
  readonly cap: number;
148
211
  readonly deadline: number;
149
212
  readonly now: () => number;
213
+ /** The per-call ceiling, seconds: the per-scene timeout. */
214
+ readonly callSeconds: number;
150
215
  calls: number;
151
216
  /** Illustration calls, capped apart from `calls` (`prefs.art`). */
152
217
  art: number;
@@ -155,21 +220,45 @@ export declare class Budget {
155
220
  rejectedText: number;
156
221
  rejectedStyle: number;
157
222
  rejectedSubjects: number;
223
+ rejectedRepeat: number;
224
+ /** Device-pass calls (one a deck at most). */
225
+ devices: number;
158
226
  tokens: number;
159
227
  quota: boolean;
160
- constructor(cap: number, deadline: number, now: () => number);
228
+ constructor(cap: number, deadline: number, now: () => number,
229
+ /** The per-call ceiling, seconds: the per-scene timeout. */
230
+ callSeconds?: number);
161
231
  /** Undefined if a call may go ahead (and is counted), else why not. */
162
232
  take(): string | undefined;
163
233
  /** As `take`, for an illustration call against its own cap. */
164
234
  takeArt(cap: number): string | undefined;
235
+ /** The deck's one device call: apart from the scene cap, but not past the quota or the clock. */
236
+ takeDevice(): string | undefined;
165
237
  /** One redraw of a rejected picture: not against the art cap, but against the clock and the quota. */
166
238
  takeRedraw(): string | undefined;
167
239
  seconds(): number;
168
240
  }
241
+ /**
242
+ * `run`, with at most `n` calls in flight across every kind — pictures, the
243
+ * device call and scenes alike. The lanes of `pool` bound the scene calls only;
244
+ * every beat's picture starts at once, and with every beat of a deck now drawn
245
+ * that would put a dozen Codex processes on the machine together.
246
+ *
247
+ * `admit` is asked once a lane is free, just before the call starts: a call
248
+ * can wait minutes for one (drafts queue behind 240s pictures), so a budget or
249
+ * timeout decided when it was queued is stale by then. It returns the args to
250
+ * run with, or throws to refuse.
251
+ */
252
+ export declare function lanes(run: Runner, n: number, admit?: (args: RunnerArgs) => RunnerArgs): Runner;
253
+ /**
254
+ * The wall-time cap, applied when a call actually starts (`lanes`' admit): a
255
+ * call that waited past the deadline is refused, and none may run past it.
256
+ */
257
+ export declare function deadlineAdmit(deadline: number, now: () => number): (args: RunnerArgs) => RunnerArgs;
258
+ /** Grammars that need two subjects or more: a one-subject picture is staged as a zoom-out instead. */
259
+ export declare const ONE_SUBJECT_UNSTAGEABLE: readonly string[];
169
260
  /** Below this share of the frame, a cue "barely moves" by the rubric's measure (static_hold's floor is 0.015%). */
170
261
  export declare const RUBRIC_CUE_CHANGE = 0.005;
171
- /** The focal size the prompt asks for; the gate's floor is `KEY_TYPE_PX`. */
172
- export declare const RUBRIC_FOCAL_PX = 88;
173
262
  /**
174
263
  * The share of its box a settled drawing should paint. MEASURED 2026-10-08:
175
264
  * round 1's sixteen scenes painted 3.9-15.6% (median ~9%) — thin outlines and
@@ -198,10 +287,105 @@ export declare function rubricProbe(m: Measured | undefined, warnings?: readonly
198
287
  */
199
288
  export declare const DATA_ARCHETYPES: readonly string[];
200
289
  export declare function isDataBeat(beat: Pick<Beat, "archetype">): boolean;
201
- /** A reply's labels, kept only where they are the shape the schema promised. */
202
- export declare function readLabels(raw: unknown): Label[];
203
290
  /** A reply's shot list, kept only where it is the shape the schema promised. */
204
291
  export declare function readShots(raw: unknown): Shot[];
205
292
  /** The paper the prompt may quote for this beat: its cited sections, equations and figure captions. */
206
293
  export declare function contextFor(beat: Beat, source: Source): string;
294
+ /** One beat's device, as the deck-order pass decided it. */
295
+ export interface BeatDevice {
296
+ beatId: string;
297
+ /** Kebab-case name of the scene's main visual device. Unique in the deck. */
298
+ device: string;
299
+ /** Whether the beat gets an illustration; otherwise its scene is pure motion graphics. */
300
+ illustrate: boolean;
301
+ /** What the scene shows, how it is composed and how it moves, when the model gave it. */
302
+ idea?: string;
303
+ /** An illustrated beat's planned setting and subjects (round 5): the deck's pictures planned together. */
304
+ setting?: string;
305
+ subjects?: string[];
306
+ /** Where the name came from: the model, or the rule catalogue (no call, a bad or repeated name). */
307
+ from: "codex" | "rule";
308
+ /** Illustrated only because of the picture floor (`pictureFloor`), not by the model's choice. */
309
+ forced?: true;
310
+ /** What the beat's narration does (src/bespoke/grammar.ts `roleOf`). */
311
+ role: Role;
312
+ /** An illustrated beat's camera grammar (`chooseGrammar`), never the previous illustrated beat's. */
313
+ grammar?: Grammar;
314
+ /** A data beat's build (src/bespoke/databuild.ts `chooseBuild`), never one an earlier data beat used. */
315
+ build?: DataBuild;
316
+ }
317
+ /** What `assignDevices` returns: one entry per beat, in deck order. */
318
+ export interface DeviceAssignment {
319
+ beats: BeatDevice[];
320
+ /** `cache`: read back from a previous run, no call. */
321
+ from: "codex" | "cache" | "rule";
322
+ /** Why the model's answer was not used, or was used only in part. */
323
+ note?: string;
324
+ }
325
+ export interface DeviceOptions {
326
+ /** The model runner. Absent: the rule catalogue only, no call. */
327
+ run?: Runner;
328
+ /** Asked before the call: undefined lets it go ahead (src/bespoke/pipeline.ts `Budget`). */
329
+ take?: () => string | undefined;
330
+ /** Illustrations the deck may draw. */
331
+ artCap: number;
332
+ model?: string;
333
+ bin?: string;
334
+ config?: readonly string[];
335
+ /** Scratch for the prompt, schema and reply. */
336
+ work: string;
337
+ timeoutMs: number;
338
+ /** Where a decided assignment is kept, so a rerun asks nothing. */
339
+ cacheDir?: string;
340
+ onUsage?: (tokens: number) => void;
341
+ }
342
+ /** Bump with any change to the device prompt or the rules applied to its answer. */
343
+ export declare const DEVICE_VERSION = "devices-2";
344
+ /** Whether a device name is type or a UI element rather than a picture (`UI_DEVICE`). */
345
+ export declare function isUiDevice(name: string): boolean;
346
+ /**
347
+ * The fewest pictures a deck's device pass leaves it (round 6): every eligible
348
+ * beat, at most the art cap — the motion is the picture's own.
349
+ */
350
+ export declare function pictureFloor(eligible: number, artCap: number): number;
351
+ /** A model's name made a kebab-case device name, or "" when nothing is left of it. */
352
+ export declare function deviceName(raw: unknown): string;
353
+ /**
354
+ * THE DECK-ORDER DEVICE PASS. Runs once, before any picture or scene is asked
355
+ * for — scenes generate two at a time, so their devices cannot come from
356
+ * finished scenes — and gives every bespoke beat one visual device, no two
357
+ * alike, with one line on how its scene is composed. One Codex call names them
358
+ * from the beats' content; whatever it does not name well (or at all, or
359
+ * twice), and every beat when there is no call, takes the next unused name from
360
+ * the rule catalogue. Illustration is decided here too: every beat with two
361
+ * cues, data beats included (round 6), at most `artCap`.
362
+ *
363
+ * STICKY PER BEAT. A decided device is also cached under its beat's own content,
364
+ * and a later run whose deck changed (one beat edited, a different call cap)
365
+ * keeps every unchanged beat's device and idea — the call is told they are
366
+ * decided — so the scenes cached under them still hit; only the changed beats
367
+ * are named anew. Every beat decided already: no call at all.
368
+ *
369
+ * Round 5 adds a camera grammar and a data build to this same pass, in deck
370
+ * order: keep it one function, one typed result. It never throws: no answer, a
371
+ * broken cache file or a cache it cannot write all end in names.
372
+ */
373
+ export declare function assignDevices(beats: readonly DeviceBeat[], opts: DeviceOptions): Promise<DeviceAssignment>;
374
+ /**
375
+ * Two pictures of a deck closer than this by Vision's feature print are the
376
+ * same picture drawn twice. CALIBRATED 2026-10-10 on round 4's 38 pictures:
377
+ * pairs within a deck — all of them friendly robots, in other poses — sat at
378
+ * 0.49-0.89 (cross-deck 0.53-0.90), so a print alone does not see a repeated
379
+ * MOTIF; that is `sharedMotifs`' job. This bar catches the near-duplicate.
380
+ */
381
+ export declare const SIMILAR_MAX = 0.45;
382
+ /** The subject nouns `motifs` shares with any of `others`. */
383
+ export declare function sharedMotifs(motifs: readonly string[], others: ReadonlyArray<readonly string[]>): string[];
384
+ /** The stock subjects (`STOCK_MOTIFS`) a picture draws that its beat does not name. */
385
+ export declare function stockDrawn(drawn: {
386
+ motifs: readonly string[];
387
+ depicts: string;
388
+ }, allowed: readonly string[]): string[];
389
+ /** How much a deck's kept pictures repeat each other (`BespokeReport.repetition`). */
390
+ export declare function repetitionOf(arts: readonly ArtRef[]): BespokeReport["repetition"];
207
391
  export declare function bespokePass(input: BespokeInput): Promise<BespokeResult>;
@@ -27,8 +27,8 @@ export declare function attribute(f: Finding, windows: readonly SceneWindow[]):
27
27
  export declare function pinPageErrors(findings: Finding[], candidates: BespokeMap, sidOf: ReadonlyMap<string, string>): Finding[];
28
28
  /**
29
29
  * What an illustrated scene's frames say about its staging: the distinct
30
- * push-ins held at cue ends, whether it opens wide, and how many subjects are
31
- * named by a label within reach at the end. Empty for a scene with no subjects.
30
+ * push-ins held at cue ends and whether it opens wide. Empty for a scene with
31
+ * no subjects.
32
32
  */
33
33
  export declare function stagingMeasures(rows: readonly Layout[], cams: readonly CamSample[], open?: number): Partial<Measured>;
34
34
  export declare function browserGate(deck: ProbeDeck): GateFn;
@@ -28,12 +28,14 @@
28
28
  * The placement grid is Code2Video's visual anchor prompt (arXiv 2510.01174).
29
29
  */
30
30
  import type { Theme } from "../emit/kit.js";
31
+ import { type DataBuild } from "./databuild.js";
32
+ import { type Grammar } from "./grammar.js";
31
33
  import { type Box, CAMERA_MAX_SCALE } from "./shots.js";
32
34
  export { CAMERA_MAX_SCALE };
33
35
  /** Bump with any change to either prompt or to a reference: it is part of every cache key. */
34
- export declare const PROMPT_VERSION = "bespoke-5";
36
+ export declare const PROMPT_VERSION = "bespoke-13";
35
37
  /** Bump with any change to what `checkFragment` accepts. Also part of every key. */
36
- export declare const CONTRACT_VERSION = "contract-4";
38
+ export declare const CONTRACT_VERSION = "contract-5";
37
39
  /** What the model is told about one beat. */
38
40
  export interface Brief {
39
41
  lang: string;
@@ -65,30 +67,114 @@ export interface Brief {
65
67
  /**
66
68
  * The beat's illustration, attached to the call as an image (src/bespoke/art.ts),
67
69
  * and its subjects in body-box px, left to right (src/bespoke/inspect.ts):
68
- * what the labels are anchored to and the shots frame.
70
+ * what the shots frame.
69
71
  */
70
72
  art?: {
71
73
  depicts: string;
72
74
  width: number;
73
75
  height: number;
76
+ /** The picture has a backdrop layer the shell draws behind the box (round 5). */
77
+ plate?: boolean;
78
+ setting?: string;
74
79
  subjects?: Box[];
75
- /** Where the shell draws each subject's label (src/bespoke/callouts.ts), and its longest fit. */
76
- zones?: Array<Box & {
77
- subject: number;
78
- chars: number;
79
- }>;
80
+ /**
81
+ * Round 6: the picture is a full-frame depth shot (src/bespoke/depth.ts) and
82
+ * the headline reaches this far down it, px; the scene's own layer is fixed.
83
+ */
84
+ band?: number;
80
85
  };
81
- /** A data beat (a chart or a table): built as a chart, never on a picture. */
86
+ /** A data beat (a chart or a table). Round 6 pictures it too; without a picture it builds its chart. */
82
87
  data?: boolean;
88
+ /**
89
+ * The scene's main visual device, a short kebab-case name ('spike-train',
90
+ * 'fog-lift', 'edge-sweep'), given by the deck-order pass `assignDevices`
91
+ * (src/bespoke/pipeline.ts) before any scene is generated. Unique in a deck.
92
+ */
93
+ device: string;
94
+ /**
95
+ * The device pass's one line on what this scene shows, how it is composed and
96
+ * how it moves. The pass sees the whole deck, so this line — not a list of
97
+ * the other scenes' names — is what keeps neighbouring scenes from being
98
+ * composed alike; and it is the beat's own, so editing one beat does not
99
+ * re-key every scene after it.
100
+ */
101
+ idea?: string;
102
+ /** An illustrated scene's camera grammar (src/bespoke/grammar.ts), from the same pass. */
103
+ grammar?: Grammar;
104
+ /** A data scene's build (src/bespoke/databuild.ts), from the same pass. */
105
+ build?: DataBuild;
83
106
  }
107
+ /** One beat as the deck-order device pass sees it. */
108
+ export interface DeviceBeat {
109
+ id: string;
110
+ archetype: string;
111
+ headline: string;
112
+ intent: string;
113
+ claim?: string;
114
+ narration?: string;
115
+ /** Narration cues the scene will be keyed to. Fewer than two: no picture (the shots need two). */
116
+ cues: number;
117
+ /** A chart or table beat: never illustrated. */
118
+ data: boolean;
119
+ }
120
+ /** The device pass's structured reply. */
121
+ export declare const DEVICE_SCHEMA: {
122
+ readonly type: "object";
123
+ readonly additionalProperties: false;
124
+ readonly required: readonly ["beats"];
125
+ readonly properties: {
126
+ readonly beats: {
127
+ readonly type: "array";
128
+ readonly items: {
129
+ readonly type: "object";
130
+ readonly additionalProperties: false;
131
+ readonly required: readonly ["id", "device", "illustrate", "idea", "setting", "subjects"];
132
+ readonly properties: {
133
+ readonly id: {
134
+ readonly type: "string";
135
+ };
136
+ readonly device: {
137
+ readonly type: "string";
138
+ };
139
+ readonly illustrate: {
140
+ readonly type: "boolean";
141
+ };
142
+ readonly idea: {
143
+ readonly type: "string";
144
+ };
145
+ readonly setting: {
146
+ readonly type: "string";
147
+ };
148
+ readonly subjects: {
149
+ readonly type: "array";
150
+ readonly items: {
151
+ readonly type: "string";
152
+ };
153
+ };
154
+ };
155
+ };
156
+ };
157
+ };
158
+ };
159
+ /**
160
+ * The deck-order device pass: one call that names, for every beat at once, the
161
+ * visual device its scene is built on — so no two beats of a deck look alike,
162
+ * which per-beat calls running two at a time cannot arrange among themselves.
163
+ */
164
+ export declare function devicePrompt(beats: readonly DeviceBeat[], artCap: number, decided?: ReadonlyMap<string, {
165
+ device: string;
166
+ idea?: string;
167
+ }>): string;
84
168
  /** What the probe measured about a candidate, quoted to the critique round. */
85
169
  export interface Measured {
86
170
  /** Bounding box of the drawing over the body box, settled frame. */
87
171
  fill?: number;
88
172
  /** Share of a 6x4 grid over the box something is drawn in. */
89
173
  cells?: number;
90
- /** Largest label at the settled frame, px. */
174
+ /** Largest label at the settled frame, px (as rendered). */
91
175
  maxType?: number;
176
+ /** Largest font-size a label declares at the settled frame (KaTeX excluded), px. */
177
+ maxDeclared?: number;
92
178
  /** Share of the box the settled drawing paints (filled shapes count, hairlines barely). */
93
179
  mass?: number;
94
180
  /** Share of the drawn parts left dimmed (under 0.6 opacity) at the end. */
@@ -101,18 +187,18 @@ export interface Measured {
101
187
  subjects?: number;
102
188
  /** Distinct push-ins the held frames show (illustrated scenes, `shotsOf`). */
103
189
  shots?: number;
104
- /** Whether the first cue opens on the whole picture. */
190
+ /** Whether the first cue opens as its grammar says (the whole picture; close, for a zoom-out). */
105
191
  establishing?: boolean;
106
- /** Subjects named by a label within reach of them, at the end. */
107
- anchored?: number;
108
- /** The farthest a subject's label sits from it, box px, at the end. */
109
- anchorMax?: number;
192
+ /** The camera grammar the scene was staged in. */
193
+ grammar?: string;
194
+ /** Round 6: the scene's own tweens that animate UI into place (`ui_motion`). */
195
+ uiMotion?: readonly string[];
110
196
  }
111
197
  /** The structured reply. `--output-schema` holds the model to it. */
112
198
  export declare const REPLY_SCHEMA: {
113
199
  readonly type: "object";
114
200
  readonly additionalProperties: false;
115
- readonly required: readonly ["review", "plan", "markup", "css", "script", "shots", "labels"];
201
+ readonly required: readonly ["review", "plan", "markup", "css", "script", "shots"];
116
202
  readonly properties: {
117
203
  readonly review: {
118
204
  readonly type: "string";
@@ -129,22 +215,6 @@ export declare const REPLY_SCHEMA: {
129
215
  readonly script: {
130
216
  readonly type: "string";
131
217
  };
132
- readonly labels: {
133
- readonly type: "array";
134
- readonly items: {
135
- readonly type: "object";
136
- readonly additionalProperties: false;
137
- readonly required: readonly ["subject", "text"];
138
- readonly properties: {
139
- readonly subject: {
140
- readonly type: "integer";
141
- };
142
- readonly text: {
143
- readonly type: "string";
144
- };
145
- };
146
- };
147
- };
148
218
  readonly shots: {
149
219
  readonly type: "array";
150
220
  readonly items: {
@@ -168,15 +238,21 @@ export declare const REPLY_SCHEMA: {
168
238
  };
169
239
  /** The subjects as the prompt lists them: box px, left to right. */
170
240
  export declare function subjectLines(subjects: readonly Box[]): string;
241
+ /**
242
+ * The beat's visual device, from the deck-order pass (`assignDevices`), and the
243
+ * devices the deck has already spent. Round 4's scenes kept arriving at the same
244
+ * labelled cards whatever the beat said; naming the device, and the ones not to
245
+ * reuse, is what makes neighbouring scenes differ.
246
+ */
247
+ export declare function deviceSection(b: Pick<Brief, "device" | "idea" | "grammar" | "build">): string;
171
248
  /** The first call: draw this beat. */
172
249
  export declare function generatePrompt(b: Brief): string;
173
250
  /** The rubric the critique round scores, and the probe reads off the measures. */
174
- export declare const RUBRIC: readonly ["FILLS THE STAGE: the drawing uses the whole box (>= 80% of its area, no empty third or band) with visual mass — filled shapes, not hairlines", "ONE FOCUS PER CUE: in every cue one element is clearly what to look at; the rest is dimmed", "MOTION EXPLAINS: the moves are the idea (flow, transform, morph, camera, counter), at least three kinds, nothing decorative, nothing still while the voice speaks; the camera goes where the voice is", "LEGIBLE HIERARCHY: focal label/number 88px+, labels 52px+, nothing under 44px, high contrast, no collisions or clipping", "CONSISTENT WITH THE PACK: only the pack's colours, big flat shapes, strokes 5-8px, the deck's font", "IN SYNC: each cue's change starts within 0.5s of its words and nothing appears before the cue that names it"];
251
+ export declare const RUBRIC: readonly ["FILLS THE STAGE: the drawing uses the whole box (>= 80% of its area, no empty third or band) with visual mass — filled shapes, not hairlines", "ONE FOCUS PER CUE: in every cue one element is clearly what to look at; the rest is dimmed", "MOTION EXPLAINS: the moves are the idea (flow, transform, morph, camera, counter), nothing decorative, nothing still while the voice speaks; the camera goes where the voice is; NO UI MOTION — no label, plate, chip, card or bar popped, slid or grown into place", "QUIET TYPE: few words, 40-44px, one key number or word up to 56px, nothing bigger, high contrast, no collisions or clipping — the subtitles carry the words", "CONSISTENT WITH THE PACK: only the pack's colours, big flat shapes, strokes 5-8px, the deck's font", "IN SYNC: each cue's change starts within 0.5s of its words and nothing appears before the cue that names it"];
175
252
  /** The second call: score the frames against the rubric, then fix what is wrong. */
176
253
  export declare function critiquePrompt(b: Brief, current: {
177
254
  markup: string;
178
255
  css: string;
179
256
  script: string;
180
257
  shots?: readonly unknown[];
181
- labels?: readonly unknown[];
182
258
  }, findings: readonly string[], legend: string | undefined, measured?: Measured): string;
@@ -13,8 +13,14 @@
13
13
  * motion-design habits round 1 lacked:
14
14
  *
15
15
  * - THE STAGE IS FILLED: every reference spans >= 85% of its box at the end.
16
- * - HIERARCHY: one 88-120px focal element per cue; labels 48-60px; nothing
17
- * under 44px.
16
+ * - CUE 1 STANDS FROM THE FIRST FRAME (round 6): the seam cuts to it, so the
17
+ * stage is never empty between scenes (`seam_blank`).
18
+ * - EVERY TWEEN IS A fromTo (AGENTS.md invariant 2), its start written out; a
19
+ * later one on the same property has immediateRender:false.
20
+ * - QUIET TYPE (the v2 scale, `TYPE_SCALE`, since 2026-10-10): one focal
21
+ * label or number per cue at 56px at most; labels 40-44px; nothing under
22
+ * 40px. They were 88-120px focal and 48-60px labels, and the founder's
23
+ * verdict on that was "the fonts are too large".
18
24
  * - ONE FOCUS PER CUE: what the voice is naming is lit (accent, full opacity);
19
25
  * what it is not is dimmed to ~0.3 — never removed, so the viewer keeps place.
20
26
  * - IDENTITY IS KEPT: a thing that changes state moves or morphs into the new
@@ -45,13 +51,6 @@ export interface Reference {
45
51
  }>;
46
52
  duration: number;
47
53
  fragment: Fragment;
48
- /** An illustrated reference's subjects, box px: what the prompt would list for its picture. */
49
- subjects?: ReadonlyArray<{
50
- x: number;
51
- y: number;
52
- w: number;
53
- h: number;
54
- }>;
55
54
  }
56
55
  /** The box every reference is drawn for. */
57
56
  export declare const REFERENCE_BOX: {
@@ -59,15 +58,15 @@ export declare const REFERENCE_BOX: {
59
58
  readonly height: 732;
60
59
  };
61
60
  export declare const REFERENCES: readonly Reference[];
62
- /** Whether a reference places the beat's illustration (and so needs a beat that has one). */
63
- export declare function placesArt(r: Reference): boolean;
64
61
  /** A reference with its `{{token}}` colours filled from `theme`. */
65
62
  export declare function paint(f: Fragment, theme: Theme): Fragment;
66
63
  /**
67
64
  * The two references a beat is shown: the best fit for its archetype first,
68
65
  * then the best fit that teaches a DIFFERENT motion, so every prompt carries
69
- * at least two verbs beyond fade-and-draw. An illustrated beat leads with the
70
- * staged reference and is never shown a scene that moves its own camera (the
71
- * shell's shots do that); a data beat leads with the chart that builds.
66
+ * at least two verbs beyond fade-and-draw. A beat with a picture is never shown
67
+ * a scene that moves its own camera (the shell's shots do that); a data beat
68
+ * leads with the chart that builds. (Round 6: a scene on a depth picture is
69
+ * shown no reference at all — src/bespoke/prompt.ts — and round 4's
70
+ * "illustrated" reference, labels and spotlight over the picture, is gone.)
72
71
  */
73
72
  export declare function pickReferences(archetype: string, n?: number, art?: boolean, data?: boolean): Reference[];