@jokerized/decksmith 0.9.2 → 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 (60) 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 +13464 -8876
  8. package/dist/ds-animate.js +1 -0
  9. package/dist/index.js +13245 -8657
  10. package/dist/mcp.js +9778 -7477
  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/source/fonts.d.ts +22 -0
  54. package/dist/types/types.d.ts +680 -4
  55. package/dist/types/verify/drift.d.ts +5 -0
  56. package/dist/types/verify/fidelity.d.ts +15 -1
  57. package/dist/types/verify/index.d.ts +27 -4
  58. package/dist/types/verify/scenes.d.ts +185 -5
  59. package/package.json +1 -1
  60. package/dist/types/bespoke/callouts.d.ts +0 -76
@@ -1,30 +1,3 @@
1
- /**
2
- * Deterministic repair of a generated scene the gates refused for a reason a
3
- * person would fix by hand in seconds — instead of throwing the scene away.
4
- *
5
- * ROUND 2 LOST 4 OF 21 BEATS to `text_overlap` / `graphic_crosses_text` after
6
- * the critique round: big type collides more, and a model fixing one collision
7
- * by rewriting coordinates makes the next one. Round 2 also kept three scenes
8
- * that ended on a frame of ghosts. None of that needs another model call:
9
- *
10
- * - LABEL COLLISIONS are solved as constraint relaxation over the frames the
11
- * probe measured. Each colliding label's unit — the `<text>`, or the
12
- * smallest `<g>` holding it with its plate and no other label — gets ONE
13
- * constant offset, the smallest (in rings of `STEP` px, sixteen directions
14
- * in a fixed order) that clears every other label by `LABEL_GAP`, every
15
- * stroke sample by `STROKE_GAP`, every shape painted over it, and stays in
16
- * the box, at EVERY measured frame, with the camera's scale at each frame
17
- * applied. The offset becomes a wrapping `<g transform="translate()">` in
18
- * the markup, so the scene's own tweens on the unit are untouched.
19
- * - A DIMMED END (`end_dimmed`): the elements dimming parts the scene had lit
20
- * are tweened back to opacity 1 just before the scene ends.
21
- * - A CAMERA LEFT ZOOMED (`camera_end`): the camera is tweened home.
22
- *
23
- * Every repair is appended as ordinary contract code (a `tl.to` with literal
24
- * vars at a literal second, or a wrapper `<g>`), goes through `checkFragment`
25
- * like a model's, and is gated again before it is used. Deterministic: the same
26
- * geometry gives the same bytes, so a repaired scene caches like any other.
27
- */
28
1
  import type { Geo, Layout } from "../verify/scenes.js";
29
2
  import { type Fragment } from "./contract.js";
30
3
  /** Error rules this module can fix. A scene failing anything else is not repaired. */
@@ -87,16 +60,37 @@ export declare function fitPlates(markup: string, frames: readonly Geo[] | Geo |
87
60
  export declare function settleAt(duration: number, lastCueStart: number): number;
88
61
  /**
89
62
  * Bring the elements dimming lit parts back to full strength at `at`. Targets
90
- * are the probe's `tag:index[#id]`, ids in the probe deck's form (`sid-…`);
91
- * an element without an id gets one (`SCENEID-lit<k>`).
63
+ * are the probe's `tag:index[#id]@opacity`, ids in the probe deck's form
64
+ * (`sid-…`); an element without an id gets one (`SCENEID-lit<k>`). The opacity
65
+ * each holds is its tween's from state (every tween is a fromTo); a target
66
+ * without one is left alone.
92
67
  */
93
68
  export declare function relight(f: Fragment, targets: readonly string[], at: number, sid: string): Fragment;
94
69
  /** Send the camera home at `at`: the shell's wrapper, or the svg's viewBox. */
95
70
  export declare function homeCamera(f: Fragment, camOff: string, at: number): Fragment;
96
71
  /** What a repair did, for the report. */
72
+ /**
73
+ * The lowest opacity a word is dimmed to: dark ink at 0.6 over a pale ground
74
+ * is ~4:1, a pack tone ~3:1 (the `dim_text` floor). The prompt asks for it.
75
+ */
76
+ export declare const TEXT_ALPHA_MIN = 0.6;
77
+ /** The ids in `markup` whose element is, or holds, words (an SVG <text>, or text of its own). */
78
+ export declare function textHolders(markup: string): Set<string>;
79
+ /**
80
+ * `dim_text`'s repair: every literal `opacity`/`autoAlpha` in (0, TEXT_ALPHA_MIN)
81
+ * in a `tl.*`/`gsap.set` whose targets — `#id` selectors, literally or through a
82
+ * top-level variable — include an element holding words, raised to
83
+ * TEXT_ALPHA_MIN. Undefined when there is nothing it can read to lift.
84
+ */
85
+ export declare function liftText(f: Fragment): {
86
+ fragment: Fragment;
87
+ lifted: number;
88
+ } | undefined;
97
89
  export interface RepairNote {
98
90
  /** Overlapping tweens on one property were untangled (`seek_order`). */
99
91
  untangled?: boolean;
92
+ /** Dimming opacities on words raised to `TEXT_ALPHA_MIN` (`dim_text`). */
93
+ lifted?: number;
100
94
  moved: number;
101
95
  /** Plates grown to hold their text (`fitPlates`). */
102
96
  grown?: number;
@@ -1,9 +1,10 @@
1
- import type { EmitContext, Scene } from "../emit/kit.js";
2
- import type { Beat } from "../types.js";
1
+ import type { EmitContext, Scene, Theme } from "../emit/kit.js";
2
+ import type { Beat, Format } from "../types.js";
3
3
  import { type ArtRef } from "./art.js";
4
- import { type Zone } from "./callouts.js";
5
4
  import { type Fragment } from "./contract.js";
6
- import { type Box, type CamMove } from "./shots.js";
5
+ import type { DataBuild } from "./databuild.js";
6
+ import type { Grammar } from "./grammar.js";
7
+ import { type Box, type Staging } from "./shots.js";
7
8
  /** One beat's bespoke scene, ready to emit. */
8
9
  export interface BespokeEntry {
9
10
  /** Token form, already through `checkFragment`. */
@@ -24,6 +25,10 @@ export interface BespokeEntry {
24
25
  }>;
25
26
  duration: number;
26
27
  };
28
+ /** The camera grammar an illustrated scene is staged in (src/bespoke/grammar.ts). Absent: "tour". */
29
+ grammar?: Grammar;
30
+ /** A data scene's build (src/bespoke/databuild.ts), stamped on the scene for the deck gate. */
31
+ build?: DataBuild;
27
32
  }
28
33
  /** The file `build` writes beside a deck with the bespoke pass's account of itself. */
29
34
  export declare const BESPOKE_FILE = "bespoke.json";
@@ -36,8 +41,29 @@ export declare function bespokeRegion(beat: Beat, ctx: Pick<EmitContext, "format
36
41
  width: number;
37
42
  height: number;
38
43
  };
44
+ /**
45
+ * ROUND 6: an illustrated scene is the whole frame. Its picture is a full-bleed
46
+ * shot behind the deck's own headline (which keeps its place and its entrance),
47
+ * so its box is the frame, and the headline's band at the top is the one place
48
+ * the scene's own words may not go (`chromeBand`).
49
+ */
50
+ export declare function cineRegion(format: Format): {
51
+ width: number;
52
+ height: number;
53
+ };
54
+ /** How far down the frame the headline reaches, px: the scene's own words stay below it. */
55
+ export declare function chromeBand(beat: Beat, ctx: Pick<EmitContext, "format" | "theme">): number;
39
56
  /** Whether a scene's script moves the shell's camera (`#SCENEID-cam`). */
40
57
  export declare function usesCamera(script: string): boolean;
58
+ /**
59
+ * The shell's (round 6): every word under a scene's own camera keeps the size it
60
+ * declares. For each camera tween — the wrapper's `scale`, or the svg's
61
+ * `viewBox` — the words get the inverse scale about their own centres over the
62
+ * same span, so a push-in enlarges the drawing and never the type (the r1
63
+ * review found labels at 75-80px under zoom; `type_scale` holds the rendered
64
+ * size). Tweens marked `data: "shell"`, which `ui_motion` does not count.
65
+ */
66
+ export declare function quietWords(sid: string): string;
41
67
  /**
42
68
  * The illustration's href AND its placement, written by the shell into the
43
69
  * scene's one `<image data-art="1">` — the contract refuses an href from the
@@ -49,7 +75,7 @@ export declare function usesCamera(script: string): boolean;
49
75
  export declare function withArt(markup: string, art: ArtRef | undefined, box?: {
50
76
  width: number;
51
77
  height: number;
52
- }): string;
78
+ }, clip?: string): string;
53
79
  /**
54
80
  * The subjects' boxes as the gates see them: invisible rects inside the camera
55
81
  * (so they move with it), after the scene's markup (so the repair pass's
@@ -57,18 +83,37 @@ export declare function withArt(markup: string, art: ArtRef | undefined, box?: {
57
83
  */
58
84
  export declare function subjectLayer(subjects: readonly Box[], width: number, height: number): string;
59
85
  /**
60
- * An illustrated entry's subjects in body px, its labels' zones, and the camera
61
- * moves its shots compile to — each push-in framing the subject together with
62
- * its label's zone, so the label the camera arrives for is in the shot.
86
+ * An illustrated entry's subjects in box px and the camera moves its shots
87
+ * compile to. In a depth scene (round 6) the subjects are where the depth
88
+ * planes put them.
63
89
  */
64
90
  export declare function staging(entry: BespokeEntry, box: {
65
91
  width: number;
66
92
  height: number;
67
93
  }): {
68
94
  subjects: Box[];
69
- zones: Zone[];
70
- moves: CamMove[];
95
+ staged: Staging;
71
96
  };
97
+ /** The picture's subjects in box px: from its depth planes when it has them. */
98
+ export declare function subjectsOf(art: ArtRef, box: {
99
+ width: number;
100
+ height: number;
101
+ }): Box[];
102
+ /**
103
+ * ROUND 6: the picture as depth planes, far to near, each a frame-sized layer
104
+ * the camera script moves at its own distance (shots.ts `planeFrame`). The
105
+ * backdrop's planes cover the frame; each subject is its own plane, holding a
106
+ * `.ds-life` wrapper its breathing moves. Then the air in front of them: the
107
+ * light that sweeps, the vignette, and the scrims under the headline and the
108
+ * subtitles. Nothing here is text, and the farthest plane keeps round 5's
109
+ * `#sid-plate` id (the gates read the backdrop's parallax off it). A plane in
110
+ * a push-in is mostly outside the frame by design, so each carries
111
+ * hyperframes' `data-layout-allow-overflow` (its `escaped_container` warning
112
+ * would otherwise send every scene to a critique call).
113
+ */
114
+ export declare function depthLayers(sid: string, art: ArtRef, W: number, H: number, theme: Theme, band: number): string;
115
+ /** `#rrggbb` with an alpha, as `rgba()`. */
116
+ export declare function hexA(hex: string, a: number): string;
72
117
  /** CSS feather for an illustration's edges: 6% at the sides, 10% top and bottom. */
73
118
  export declare const ART_FEATHER = "-webkit-mask-image:linear-gradient(to right,transparent,#000 6%,#000 94%,transparent),linear-gradient(to bottom,transparent,#000 10%,#000 90%,transparent);-webkit-mask-composite:source-in;mask-composite:intersect";
74
119
  /** Whether a scene's script tweens `morphSVG` (MorphSVGPlugin must be registered first). */
@@ -1,29 +1,36 @@
1
1
  /**
2
- * Which beats get a bespoke scene. A deterministic rule, so the same storyboard
3
- * always asks the model about the same beats, and a cache hit is a cache hit.
2
+ * Which beats get a bespoke scene: under `--design v2`, every beat that can
3
+ * have one — title, numbers, comparisons and closing claims as well as
4
+ * mechanisms. Until 2026-10-10 this picked 4-6 "mechanism" beats and left the
5
+ * rest to the archetype templates; the founder's verdict on those ("why do I
6
+ * need to see those hardcoded same UI blocks in every slide") is why the
7
+ * templates are now only the fallback for a scene that fails its gates.
4
8
  *
5
- * WHAT IS WORTH A MODEL CALL. A bespoke scene earns its cost where the fixed
6
- * vocabulary is weakest: a beat that explains a MECHANISM — a process, an
7
- * equation, a curve, a comparison — and that the narration talks over for many
8
- * seconds while the v2 archetype has finished building after five (measured in
9
- * the spike: v2's s3 and s7 froze for 15-20s of speech). Title cards, callouts
10
- * and tables say a thing rather than show one, so they never qualify.
11
- *
12
- * WHAT NEVER QUALIFIES, whatever it scores:
13
- * - a beat with no narration: its cues are the keyframes, and without them a
14
- * generated scene has nothing to stay in step with;
9
+ * WHAT NEVER QUALIFIES:
10
+ * - a beat with no narration cue: its cues are the keyframes, and without them
11
+ * a generated scene has nothing to stay in step with;
15
12
  * - a beat a camera enters or leaves (`inside`): the dive is aimed at a part
16
13
  * the archetype drew, which a bespoke scene does not draw;
17
- * - a beat the planner marked `bespoke: false`.
14
+ * - a beat the planner marked `bespoke: false` that cites a figure or a table
15
+ * of the paper — the two reasons the planner is told a `false` is for (a
16
+ * real figure the viewer must see, a table read row by row).
17
+ *
18
+ * A `false` on a beat that cites NEITHER is not obeyed (2026-10-10). r3's
19
+ * b12 was one: a comparison citing only a section, which the planner kept as
20
+ * six grey row plates held still for 16 seconds — exactly the "hardcoded UI
21
+ * blocks" the founder named, and with nothing the archetype had to keep.
18
22
  *
19
- * The planner's `bespoke: true` is a strong hint, not an order: it adds a large
20
- * bonus, so it wins a tie with anything, but an ineligible beat stays ineligible.
23
+ * Deterministic, so the same storyboard always asks about the same beats and a
24
+ * cache hit is a cache hit. When the call cap cannot pay two calls for every
25
+ * beat (`max`), the most mechanical beats are drawn and the rest keep their
26
+ * archetype. Picks come back in deck order.
21
27
  */
22
28
  import type { z } from "zod";
23
29
  import type { Beat, segmentSchema } from "../types.js";
24
30
  type Segment = z.infer<typeof segmentSchema>;
25
31
  export interface Pick {
26
32
  beatId: string;
33
+ /** Priority when the cap cannot pay for every beat. */
27
34
  score: number;
28
35
  /** Why it scored what it did, for `bespoke.json`. */
29
36
  why: string[];
@@ -39,14 +46,14 @@ export interface Selection {
39
46
  export interface SelectOptions {
40
47
  /** Hashed with each beat id to break ties, so two decks tie differently. */
41
48
  seed: string;
42
- /** Narration, by beat id. A beat without segments is ineligible. */
49
+ /** Narration, by beat id. A beat without a cue is ineligible. */
43
50
  narration?: Readonly<Record<string, readonly Segment[]>>;
44
- /** Fewest beats to aim for when that many are eligible. Default 4. */
45
- min?: number;
46
- /** Most beats. Default 6; the call cap may lower it further. */
51
+ /**
52
+ * Most beats: what the call cap pays for at two calls a beat. When fewer
53
+ * than the eligible, the most mechanical win (a mechanism gains most from a
54
+ * scene that moves with the voice); the rest keep their archetype.
55
+ */
47
56
  max?: number;
48
57
  }
49
- /** The number of beats a deck of `n` kept beats aims for: a third, held to [min, max]. */
50
- export declare function target(n: number, min?: number, max?: number): number;
51
58
  export declare function selectBespoke(beats: readonly Beat[], opts: SelectOptions): Selection;
52
59
  export {};
@@ -9,18 +9,83 @@ export declare function contactSheets(sheets: ReadonlyArray<{
9
9
  frames: readonly SheetFrame[];
10
10
  path: string;
11
11
  }>, cols?: number): Promise<void>;
12
+ /** The resolution planes are stored at, as a multiple of the frame: sharp up to this push-in. */
13
+ export declare const PLANE_SCALE = 1.4;
14
+ /**
15
+ * How out of focus a plane's soft twin is, frame px. Rack focus cross-fades a
16
+ * plane with this twin instead of tweening a CSS blur: MEASURED 2026-10-10 in
17
+ * the gates' browser (SwiftShader), a CSS \`filter: blur()\` on six frame-sized
18
+ * layers cost ~1.5s a frame (2.2s against 0.55s without), which made one deck's
19
+ * motion gates take 672s.
20
+ */
21
+ export declare const SOFT_PX = 6;
22
+ /** One backdrop plane as the deck stores it: frame-shaped, covering the frame at rest. */
23
+ export interface PlaneCopy {
24
+ webp: Buffer;
25
+ /** The same plane out of focus: blurred `SOFT_PX`, stored at a quarter of the size. */
26
+ soft: Buffer;
27
+ /** Mean relative inverse depth of the band it holds (0 far .. 1 near); absent when flat. */
28
+ u?: number;
29
+ }
30
+ /** One subject, cut out of the subjects' picture: its own WebP and its box in frame px. */
31
+ export interface SubjectCopy {
32
+ /** Which subject (0-based, the inspection's left-to-right order). */
33
+ index: number;
34
+ webp: Buffer;
35
+ soft: Buffer;
36
+ box: {
37
+ x: number;
38
+ y: number;
39
+ w: number;
40
+ h: number;
41
+ };
42
+ }
43
+ /**
44
+ * ROUND 6. The backdrop sliced into depth planes (src/bespoke/depth.ts), and
45
+ * each subject cut out on its own, every one already cropped to what covers
46
+ * `frame` and stored at `PLANE_SCALE` times it. A plane holds its depth band's
47
+ * real pixels; where a NEARER band covers it, it holds colours pushed and
48
+ * pulled in from its own band at quarter resolution — what a nearer plane
49
+ * uncovers when it slides — and it is transparent where nothing at or behind
50
+ * its band is. The farthest plane is opaque everywhere. Without a depth map
51
+ * (`depth` undefined) the backdrop is one plane. The pixel work runs at a
52
+ * quarter of the stored size and the full-size planes are composited by the
53
+ * canvas, so a picture takes ~3s, not minutes (MEASURED 2026-10-10: a per-pixel
54
+ * pass at full size did not finish in 10 minutes).
55
+ */
56
+ export declare function depthPlanes(plate: Buffer, depth: Buffer | undefined, cuts: readonly number[], subjects: Buffer, boxes: ReadonlyArray<readonly [number, number, number, number]>, frame: {
57
+ width: number;
58
+ height: number;
59
+ }, quality?: number): Promise<{
60
+ planes: PlaneCopy[];
61
+ subjects: SubjectCopy[];
62
+ scale: number;
63
+ }>;
12
64
  /**
13
65
  * The deck's copy of an illustration, and the draft call's copy with its
14
66
  * subjects boxed and numbered, drawn on a canvas by the same Chrome. WebP,
15
67
  * because a flat picture's PNG is 1-2 MB and its WebP at 0.84 is ~5-10% of
16
68
  * that (round 3 shipped 7-10 MB of PNG per deck); the repository has no image
17
69
  * encoder, and Chrome is already every gate's dependency.
70
+ *
71
+ * ROUND 5, two layers. With a backdrop (`plate`) the subjects stand in front
72
+ * of it: the subjects' copy keeps its transparent ground (a picture drawn on a
73
+ * flat ground after all is keyed: its ground made transparent), the backdrop
74
+ * gets the feathered edges, and `composite` is the two as the box shows them
75
+ * (each scaled to cover the box, centred) — what the draft call is shown, with
76
+ * `boxed` drawn over it in the same coordinates.
18
77
  */
19
78
  export declare function pictureCopies(png: Buffer, boxes: ReadonlyArray<readonly [number, number, number, number]>, feather?: {
20
79
  width: number;
21
80
  height: number;
22
- }, quality?: number): Promise<{
81
+ }, quality?: number, opts?: {
82
+ plate?: Buffer;
83
+ ground?: string;
84
+ cutout?: boolean;
85
+ }): Promise<{
23
86
  webp: Buffer;
24
87
  boxed: Buffer;
25
88
  feathered: boolean;
89
+ plateWebp?: Buffer;
90
+ composite?: Buffer;
26
91
  }>;
@@ -21,7 +21,16 @@
21
21
  *
22
22
  * Shots closer than `MIN_HOLD` to the previous one are dropped: a shot the
23
23
  * audience cannot read is a shake.
24
+ *
25
+ * A SECOND GRAMMAR, `close-open` (2026-10-10): one grammar for every
26
+ * illustrated scene made every one of them move alike — r1's three illustrated
27
+ * scenes were the same tour of three subjects in a row, and read as a template.
28
+ * `close-open` OPENS CLOSE on the subject of the first shot, held from t=0, moves
29
+ * subject to subject, and opens out to the whole picture only at the reveal —
30
+ * the picture is discovered rather than surveyed. The pass alternates the two
31
+ * over a deck's illustrated beats in deck order.
24
32
  */
33
+ import type { Grammar } from "./grammar.js";
25
34
  import type { UnitBox } from "./inspect.js";
26
35
  /** One entry of a scene's shot list: on cue `cue` (1-based), `at` of the way in, frame `subject` (0 = the whole picture). */
27
36
  export interface Shot {
@@ -44,8 +53,10 @@ export interface CamMove {
44
53
  x: number;
45
54
  y: number;
46
55
  ease: string;
47
- /** The subject framed (0 = the whole picture). */
56
+ /** The subject framed (0 = the whole picture, or a two-shot). */
48
57
  subject: number;
58
+ /** What kind of move it is in its grammar (the report and the gates read it). */
59
+ kind?: "push" | "whip" | "track" | "cut" | "pull" | "two-shot" | "creep" | "reveal";
49
60
  }
50
61
  /** The camera's largest push-in: past it a picture turns to mush. */
51
62
  export declare const CAMERA_MAX_SCALE = 2.2;
@@ -59,15 +70,14 @@ export declare const REVEAL = 1.3;
59
70
  export declare const MIN_HOLD = 1.6;
60
71
  /** The establishing shot holds at least this long into the first cue. */
61
72
  export declare const ESTABLISH = 1.6;
73
+ /** A held shot creeps this much further in, so a hold is never a still frame. */
74
+ export declare const CREEP = 1.03;
62
75
  /**
63
- * The band at the top of an illustrated scene's box that the picture leaves
64
- * clear: the subjects' labels go above them (src/bespoke/callouts.ts), and a
65
- * picture that covered the whole box put its subjects' heads at its top edge,
66
- * where the only room for a label was across the subject's face (round 4's
67
- * first full run, en s2: "observations" over a robot's eyes).
76
+ * Where the picture sits in the box: covering all of it. Round 4 kept a 120px
77
+ * band clear at the top for the shell's subject labels; round 6 draws no
78
+ * labels (the narration and the subtitles name the subjects), so the band and
79
+ * its labels are gone.
68
80
  */
69
- export declare const ART_BAND = 120;
70
- /** Where the picture sits in the body box: under the band, covering the rest. */
71
81
  export declare function artPlacement(box: {
72
82
  width: number;
73
83
  height: number;
@@ -86,34 +96,208 @@ export declare function subjectsInBox(units: readonly UnitBox[], art: {
86
96
  width: number;
87
97
  height: number;
88
98
  }): Box[];
89
- /** The inverse of `subjectsInBox` for boxes inside the placement: box px back to shares of the picture. */
90
- export declare function unitsInPicture(boxes: readonly Box[], art: {
99
+ /**
100
+ * The camera's scale and offset that frame `b` (origin 0 0), held to the box's
101
+ * edges. `sMax` is the picture's own ceiling (`sharpMax`): past it the push-in
102
+ * shows the picture's pixels; `sMin` the closest a framing may be widened to.
103
+ */
104
+ export declare function frameOn(b: Box, W: number, H: number, sMax?: number, sMin?: number): {
105
+ s: number;
106
+ x: number;
107
+ y: number;
108
+ };
109
+ /** The camera that puts point (cx, cy) of the box at the centre of the view at scale `s`, held to the box. */
110
+ export declare function frameAt(cx: number, cy: number, s: number, W: number, H: number): {
111
+ s: number;
112
+ x: number;
113
+ y: number;
114
+ };
115
+ /**
116
+ * EFFECTIVE RESOLUTION. The image tool draws ~1.57 megapixels whatever it is
117
+ * asked (MEASURED 2026-10-10: 1672x941, 1774x887, 1915x821 — no size control
118
+ * exists on the tool). Covering a ~1730px-wide box, one picture pixel already
119
+ * spans ~0.9-1.0 output pixels, so a 2.2x push-in shows each picture pixel
120
+ * over ~2.2 output pixels: round 4's push-ins looked soft. The shell now caps
121
+ * every push-in at the scale where a picture pixel spans at most 1/`EFF_MIN`
122
+ * output pixels (`sharpMax`), and the report and the gate read the effective
123
+ * resolution at the closest shot (`effectiveAt`).
124
+ *
125
+ * ROUND 6 raises the bar from 0.6 to 0.85: pictures are upscaled 2x before they
126
+ * are cut into planes (src/bespoke/depth.ts), so a 1.55x push-in can be sharp.
127
+ * A picture that could not be upscaled is still pushed in at `PUSH_FLOOR` (the
128
+ * staging wins) and the report says how soft it was.
129
+ */
130
+ export declare const EFF_MIN = 0.85;
131
+ /** Picture pixels per output pixel when the picture covers `box`'s placement at camera scale `s`. */
132
+ export declare function effectiveAt(art: {
91
133
  width: number;
92
134
  height: number;
135
+ depth?: {
136
+ scale: number;
137
+ };
93
138
  }, box: {
94
139
  width: number;
95
140
  height: number;
96
- }): UnitBox[];
97
- /** The camera's scale and offset that frame `b` (origin 0 0), held to the box's edges. */
98
- export declare function frameOn(b: Box, W: number, H: number): {
99
- s: number;
100
- x: number;
101
- y: number;
102
- };
141
+ }, s: number): number;
142
+ /** The closest the camera may push in on this picture and stay at `EFF_MIN` or sharper. */
143
+ export declare function sharpMax(art: {
144
+ width: number;
145
+ height: number;
146
+ depth?: {
147
+ scale: number;
148
+ };
149
+ } | undefined, box: {
150
+ width: number;
151
+ height: number;
152
+ }): number;
103
153
  /**
104
154
  * The default shot list, for a scene whose own is missing or unusable: the
105
155
  * subjects in order, one per cue after the first (the first cue is the
106
156
  * establishing shot), so every subject the picture has is pointed at once.
107
157
  */
108
158
  export declare function defaultShots(cues: number, subjects: number): Shot[];
159
+ /** One step of a wiped-on picture: from `t`, over `dur`, the picture shows up to `share` of the box's width. */
160
+ export interface WipeStep {
161
+ t: number;
162
+ dur: number;
163
+ share: number;
164
+ /** The subject this step uncovers (0: the rest of the picture). */
165
+ subject: number;
166
+ }
167
+ /** Everything the shell's camera does in one illustrated scene (src/bespoke/grammar.ts). */
168
+ export interface Staging {
169
+ grammar: Grammar;
170
+ /** Where the camera is at t=0: home, except a zoom-out, which opens close on `subject`. */
171
+ open: {
172
+ s: number;
173
+ x: number;
174
+ y: number;
175
+ subject?: number;
176
+ };
177
+ moves: CamMove[];
178
+ /** A wiped-on picture's steps (grammar "wipe"); empty otherwise. */
179
+ wipes: WipeStep[];
180
+ }
181
+ interface Timed {
182
+ t: number;
183
+ subject: number;
184
+ }
185
+ interface Ctx {
186
+ cues: ReadonlyArray<{
187
+ t0: number;
188
+ t1: number;
189
+ }>;
190
+ subjects: readonly Box[];
191
+ W: number;
192
+ H: number;
193
+ sMax: number;
194
+ first: {
195
+ t0: number;
196
+ t1: number;
197
+ };
198
+ last: {
199
+ t0: number;
200
+ t1: number;
201
+ };
202
+ /** Home, with the reveal finished, before the settled end frame. */
203
+ latest: number;
204
+ /** Nothing moves before the establishing shot has been held. */
205
+ earliest: number;
206
+ }
109
207
  /**
110
- * The camera moves for one scene. `cues` on the scene's clock; `duration` the
111
- * scene's length (the end frame is photographed at `duration - 0.5`).
208
+ * The named subjects' shot times, fitted to the scene: each as the voice names
209
+ * it, but no earlier than the establishing shot or `gap` after the one before,
210
+ * and early enough that every later one still fits before the reveal — a
211
+ * subject named in the last second is shot a little early rather than not at
212
+ * all (MEASURED 2026-10-10: short scenes naming their second subject late held
213
+ * one push-in, and `shot_variety` sent them back). Subjects that cannot fit
214
+ * even so are dropped from the end.
112
215
  */
216
+ export declare function scheduled(list: readonly Timed[], c: Pick<Ctx, "earliest" | "latest">, gap: number): Timed[];
217
+ /**
218
+ * The camera of one illustrated scene in its grammar. `cues` on the scene's
219
+ * clock; `duration` the scene's length (the end frame is photographed at
220
+ * `duration - 0.5`); `sMax` the picture's sharpness ceiling (`sharpMax`).
221
+ */
222
+ export declare function compileStaging(grammar: Grammar, shots: readonly Shot[], cues: ReadonlyArray<{
223
+ t0: number;
224
+ t1: number;
225
+ }>, duration: number, subjects: readonly Box[], W: number, H: number, sMax?: number): Staging;
226
+ /** Round 4's one grammar, `tour`: the moves alone. */
113
227
  export declare function compileShots(shots: readonly Shot[], cues: ReadonlyArray<{
114
228
  t0: number;
115
229
  t1: number;
116
- }>, duration: number, subjects: readonly Box[], W: number, H: number): CamMove[];
230
+ }>, duration: number, subjects: readonly Box[], W: number, H: number, sMax?: number): CamMove[];
231
+ /** The closest the camera gets in a staging (the open frame included). */
232
+ export declare function closest(st: Pick<Staging, "open" | "moves">): number;
233
+ /**
234
+ * ROUND 6: a depth plane's framing for a camera framing (src/bespoke/depth.ts).
235
+ *
236
+ * The camera's framing `f` is of the subjects' plane (distance 1): scale `s`
237
+ * and offset `x, y` with origin 0 0, which is what `#sid-cam` is given. Read as
238
+ * a pinhole camera looking at the frame's centre `c`, that framing is a dolly
239
+ * `tz = 1 - 1/s` and a truck `f·t = (c(1-s) - x) / s`. A plane at distance `z`
240
+ * then projects EXACTLY to scale `s_z = z / (z - tz)` and offset
241
+ * `c(1 - s_z) - f·t·s_z / z` — so a farther plane zooms and slides less, a
242
+ * nearer one more: parallax with no approximation (a multiplane image is a
243
+ * stack of homographies, and for fronto-parallel planes those are scale +
244
+ * offset). `z = 1` returns `f` itself.
245
+ *
246
+ * NO PLANE EVER NEEDS ENLARGING TO COVER THE FRAME. A plane's left edge lands
247
+ * at or left of the frame's exactly when `x <= 0` — the camera's own framing
248
+ * covers the box — whatever the plane's distance: substituting `s_z` and `f·t`,
249
+ * `x_z <= 0` reduces to `x / (s·z) <= 0`. The same holds at every edge, so the
250
+ * shell's framings (held to the box by `frameAt`) cover with every plane.
251
+ * (Round 6's first cut carried an overscan search; it never returned more than
252
+ * 1, and a reversed copy of it was caught by nothing — so it is gone, and the
253
+ * test holds this property instead.)
254
+ */
255
+ export declare function planeFrame(f: {
256
+ s: number;
257
+ x: number;
258
+ y: number;
259
+ }, z: number, W: number, H: number): {
260
+ s: number;
261
+ x: number;
262
+ y: number;
263
+ };
264
+ /**
265
+ * RACK FOCUS (round 6): what tells the audience where to look, instead of a
266
+ * label plate. A plane at distance `z` is blurred by how far it sits from the
267
+ * plane in focus, in dioptres (|1/z - 1/zf|, as a lens's circle of confusion
268
+ * grows), more in a close shot than in the whole view (a longer lens has less
269
+ * depth of field). px at the plane's own scale.
270
+ */
271
+ export declare const BLUR_WIDE = 3.5;
272
+ export declare const BLUR_CLOSE = 5;
273
+ export declare const BLUR_MAX = 6;
274
+ export declare function blurAt(z: number, focusZ: number, s: number): number;
275
+ /**
276
+ * The blur as the deck draws it: the opacity of the plane's out-of-focus twin
277
+ * (src/bespoke/sheet.ts, blurred `BLUR_MAX`px) over the sharp plane. A CSS
278
+ * blur tween cost ~1.5s a frame in the gates' browser; a cross-fade costs
279
+ * nothing measurable.
280
+ */
281
+ export declare function softAt(z: number, focusZ: number, s: number): number;
282
+ /** What `cameraScript` moves besides the camera in a depth-staged scene (round 6). */
283
+ export interface DepthStage {
284
+ /** Backdrop planes, far to near: `#sid-plate` is the farthest, `#sid-planeN` the rest. */
285
+ planes: ReadonlyArray<{
286
+ z: number;
287
+ }>;
288
+ /** The subjects' planes `#sid-subjK` (K 1-based), with their distances. */
289
+ subjects: ReadonlyArray<{
290
+ subject: number;
291
+ z: number;
292
+ box: Box;
293
+ }>;
294
+ /** The scene's length: the light's sweep and the subjects' breathing end before it. */
295
+ duration: number;
296
+ }
297
+ /** The element id of backdrop plane `i` (far to near). The farthest keeps round 5's `-plate`. */
298
+ export declare function planeId(sid: string, i: number): string;
299
+ /** The element id of subject `k`'s plane (1-based). Not `-sK`: a scene's own ids use that. */
300
+ export declare function subjectId(sid: string, k: number): string;
117
301
  /**
118
302
  * The camera as GSAP source, for the shell to put before the scene's own
119
303
  * script. Every tween is a `fromTo` with explicit from-values and
@@ -122,7 +306,26 @@ export declare function compileShots(shots: readonly Shot[], cues: ReadonlyArray
122
306
  * centre of the view (x' = W/2 - (W/2 - x)·k), so a hold is never a still
123
307
  * frame; the next move starts from where the creep ended. Nothing moves after
124
308
  * the reveal: the end frame is the whole picture, exactly home.
309
+ *
310
+ * ROUND 6, with depth planes (`depth`): every camera tween has a twin on each
311
+ * plane at `planeFrame` of its values for the plane's distance — the
312
+ * parallax — with the plane's blur racked to the subject framed (`blurAt`);
313
+ * the light sweeps once; the subjects breathe; and wipe steps light the
314
+ * subjects in turn instead of uncovering a clip.
125
315
  */
126
- export declare function cameraScript(sid: string, moves: readonly CamMove[], W: number, H: number): string;
316
+ export declare function cameraScript(sid: string, moves: readonly CamMove[], W: number, H: number, opts?: {
317
+ open?: {
318
+ s: number;
319
+ x: number;
320
+ y: number;
321
+ subject?: number;
322
+ };
323
+ wipes?: readonly WipeStep[];
324
+ /** Round 6: the depth planes that move with the camera, each at its own distance. */
325
+ depth?: DepthStage;
326
+ }): string;
127
327
  /** What a scene's camera does, for the report and the prompt: one word per move. */
128
- export declare function shotSummary(moves: readonly CamMove[]): string;
328
+ export declare function shotSummary(moves: readonly CamMove[], grammar?: string, open?: {
329
+ s: number;
330
+ }): string;
331
+ export {};
@@ -69,8 +69,15 @@ export declare function vendorScripts(out: string, composition: string): Promise
69
69
  * downloadable file at all. Named here because this set is the ONLY thing that
70
70
  * reaches the built deck — a poster left out is simply absent, and the deck's
71
71
  * own runtime reports it as `http_error 404`, a long way from this line.
72
+ *
73
+ * A PIECE IS WRITTEN, NOT COPIED. Its `src` is the author's animate scene file,
74
+ * and what the deck loads under the same name is that file assembled with the
75
+ * vendored kit (`assemblePiece`, src/build/piece.ts) — the containment proof
76
+ * below applies to it exactly as to an image.
72
77
  */
73
78
  export declare function copyAssets(sourceDir: string, out: string, figures: readonly {
79
+ id?: string;
80
+ kind?: string;
74
81
  src: string;
75
82
  poster?: string;
76
83
  }[], step: Step): Promise<string[]>;