@jokerized/decksmith 0.9.0 → 0.9.1
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.
- package/README.md +212 -0
- package/dist/cli.js +20183 -14325
- package/dist/deck-player-element.js +1 -1
- package/dist/deck-player.js +1 -1
- package/dist/index.js +10605 -4957
- package/dist/mcp.js +1558 -319
- package/dist/types/bespoke/art.d.ts +173 -0
- package/dist/types/bespoke/cache.d.ts +69 -0
- package/dist/types/bespoke/callouts.d.ts +76 -0
- package/dist/types/bespoke/contract.d.ts +57 -0
- package/dist/types/bespoke/inspect.d.ts +69 -0
- package/dist/types/bespoke/pipeline.d.ts +207 -0
- package/dist/types/bespoke/probe.d.ts +34 -0
- package/dist/types/bespoke/prompt.d.ts +182 -0
- package/dist/types/bespoke/references.d.ts +73 -0
- package/dist/types/bespoke/repair.d.ts +121 -0
- package/dist/types/bespoke/scene.d.ts +97 -0
- package/dist/types/bespoke/select.d.ts +52 -0
- package/dist/types/bespoke/sheet.d.ts +26 -0
- package/dist/types/bespoke/shots.d.ts +128 -0
- package/dist/types/bespoke/untangle.d.ts +5 -0
- package/dist/types/emit/archetypes/index.d.ts +0 -7
- package/dist/types/emit/composition.d.ts +37 -2
- package/dist/types/emit/kit.d.ts +17 -0
- package/dist/types/emit/themes/pick.d.ts +6 -3
- package/dist/types/index.d.ts +17 -0
- package/dist/types/plan/codex.d.ts +36 -0
- package/dist/types/prefs.d.ts +10 -1
- package/dist/types/render/capture.d.ts +6 -0
- package/dist/types/render/timing.d.ts +3 -0
- package/dist/types/types.d.ts +71 -0
- package/dist/types/verify/check.d.ts +1 -1
- package/dist/types/verify/index.d.ts +14 -0
- package/dist/types/verify/scenes.d.ts +325 -0
- package/package.json +2 -1
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import type { Theme } from "../emit/kit.js";
|
|
2
|
+
import type { Runner } from "../plan/codex.js";
|
|
3
|
+
import type { UnitBox } from "./inspect.js";
|
|
4
|
+
/** Bump with any change to the art prompt: it is part of every art key, and so of every scene key. */
|
|
5
|
+
export declare const ART_VERSION = "art-2";
|
|
6
|
+
/** A picture bigger than this is not one the tool made for a slide. */
|
|
7
|
+
export declare const MAX_ART_BYTES: number;
|
|
8
|
+
/** Effort for the art call: MEASURED 2026-10-09, low drew the pictures above in 36-49s. */
|
|
9
|
+
export declare const ART_EFFORT = "low";
|
|
10
|
+
/** One beat's illustration, as a deck uses it. */
|
|
11
|
+
export interface ArtRef {
|
|
12
|
+
/** Content key: the cache name and the deck's file name. */
|
|
13
|
+
key: string;
|
|
14
|
+
/** The file name under the deck's `assets/bespoke/` (WebP when it could be encoded). */
|
|
15
|
+
name: string;
|
|
16
|
+
/** Where the deck's bytes are now (the cache), for `build` to copy. */
|
|
17
|
+
file: string;
|
|
18
|
+
width: number;
|
|
19
|
+
height: number;
|
|
20
|
+
/** What the picture shows, left to right, in the illustrator's words. */
|
|
21
|
+
depicts: string;
|
|
22
|
+
/** The PNG as drawn: what the scene call is shown. Absent: `file` is that PNG. */
|
|
23
|
+
png?: string;
|
|
24
|
+
/** The PNG with its subjects boxed and numbered, for the draft call (src/bespoke/sheet.ts). */
|
|
25
|
+
boxed?: string;
|
|
26
|
+
/** The subjects, left to right, as shares of the picture (src/bespoke/inspect.ts). */
|
|
27
|
+
subjects?: UnitBox[];
|
|
28
|
+
/** What the inspection measured, for the report. */
|
|
29
|
+
check?: ArtCheck;
|
|
30
|
+
/** The deck's copy carries its own feathered edges (alpha): the shell adds no CSS mask. */
|
|
31
|
+
feathered?: boolean;
|
|
32
|
+
}
|
|
33
|
+
/** What the inspection said about the picture kept, and how many draws it took. */
|
|
34
|
+
export interface ArtCheck {
|
|
35
|
+
/** `flatness().score`; flat at `FLAT_MIN` or more. */
|
|
36
|
+
flat: number;
|
|
37
|
+
flatOk: boolean;
|
|
38
|
+
/** Writing found in it, or null when nothing could read it. */
|
|
39
|
+
text: string[] | null;
|
|
40
|
+
/** Draws made for this beat (2 when the first was rejected). */
|
|
41
|
+
attempts: number;
|
|
42
|
+
/** Why a draw was rejected, per rejected draw. */
|
|
43
|
+
rejected: string[];
|
|
44
|
+
}
|
|
45
|
+
/** What the illustrator is told about one beat. Paper-derived fields are untrusted. */
|
|
46
|
+
export interface ArtBrief {
|
|
47
|
+
lang: string;
|
|
48
|
+
headline: string;
|
|
49
|
+
intent: string;
|
|
50
|
+
claim?: string;
|
|
51
|
+
narration?: string;
|
|
52
|
+
/** A short excerpt of the paper; untrusted. */
|
|
53
|
+
context: string;
|
|
54
|
+
theme: Theme;
|
|
55
|
+
pack: string;
|
|
56
|
+
}
|
|
57
|
+
/** The illustrator's reply. `--output-schema` holds it to this. */
|
|
58
|
+
export declare const ART_SCHEMA: {
|
|
59
|
+
readonly type: "object";
|
|
60
|
+
readonly additionalProperties: false;
|
|
61
|
+
readonly required: readonly ["ok", "file", "reason", "depicts"];
|
|
62
|
+
readonly properties: {
|
|
63
|
+
readonly ok: {
|
|
64
|
+
readonly type: "boolean";
|
|
65
|
+
};
|
|
66
|
+
readonly file: {
|
|
67
|
+
readonly anyOf: readonly [{
|
|
68
|
+
readonly type: "string";
|
|
69
|
+
}, {
|
|
70
|
+
readonly type: "null";
|
|
71
|
+
}];
|
|
72
|
+
};
|
|
73
|
+
readonly reason: {
|
|
74
|
+
readonly anyOf: readonly [{
|
|
75
|
+
readonly type: "string";
|
|
76
|
+
}, {
|
|
77
|
+
readonly type: "null";
|
|
78
|
+
}];
|
|
79
|
+
};
|
|
80
|
+
readonly depicts: {
|
|
81
|
+
readonly type: "string";
|
|
82
|
+
};
|
|
83
|
+
};
|
|
84
|
+
};
|
|
85
|
+
/** The art key: everything the picture is drawn from. */
|
|
86
|
+
export declare function artKey(brief: ArtBrief, model: string): string;
|
|
87
|
+
/**
|
|
88
|
+
* The deck's picture style, word for word the same in every art call of every
|
|
89
|
+
* deck, so the pictures of one deck look like one illustrator's. ROUND 4: round
|
|
90
|
+
* 3 asked for "flat vector … soft gradients and gentle shading, a little depth"
|
|
91
|
+
* and got soft 3D toy renders — the shading words won. This names the look by
|
|
92
|
+
* what it is made of (flat fills, hard edges) and forbids, by name, every way
|
|
93
|
+
* a picture turns into a render.
|
|
94
|
+
*/
|
|
95
|
+
export declare const ART_STYLE = "STYLE \u2014 FLAT 2D VECTOR, strictly. The look of a modern explainer channel's flat illustration (Kurzgesagt-like flat design) or a vector editorial infographic: every shape is ONE solid, uniform colour with crisp hard edges, like cut paper; simple geometric forms with rounded corners; a darker flat shape of the same hue may mark a side or a fold (a hard-edged shape, never a blend). Characters and objects are simplified and iconic, drawn front-on or in clean side view. FORBIDDEN, every one: gradients of any kind, airbrushed or soft shading, ambient occlusion, glossy or specular highlights, reflections, rim light, soft or drop shadows (not even under the subjects), glow, blur, depth of field, texture, grain, noise, 3D rendering, clay, plastic, vinyl-toy or Pixar-style characters, isometric 3D, photorealism, outlines thinner than the shapes they bound. If in doubt, flatter.";
|
|
96
|
+
export declare function artPrompt(b: ArtBrief, retry?: string): string;
|
|
97
|
+
/** Width and height from a PNG's IHDR, or undefined when the bytes are not a PNG. */
|
|
98
|
+
export declare function pngSize(bytes: Buffer): {
|
|
99
|
+
width: number;
|
|
100
|
+
height: number;
|
|
101
|
+
} | undefined;
|
|
102
|
+
/** `$CODEX_HOME`, else `~/.codex` — where the image tool saves what it draws. */
|
|
103
|
+
export declare function codexHome(env?: NodeJS.ProcessEnv): string;
|
|
104
|
+
interface ArtMeta {
|
|
105
|
+
version: 1;
|
|
106
|
+
key: string;
|
|
107
|
+
width: number;
|
|
108
|
+
height: number;
|
|
109
|
+
depicts: string;
|
|
110
|
+
model: string;
|
|
111
|
+
artVersion: string;
|
|
112
|
+
subjects?: UnitBox[];
|
|
113
|
+
check?: ArtCheck;
|
|
114
|
+
feathered?: boolean;
|
|
115
|
+
}
|
|
116
|
+
/** Copies a picture may have beside its PNG: the deck's WebP, the draft call's boxed PNG. */
|
|
117
|
+
export interface ArtCopies {
|
|
118
|
+
webp?: Buffer;
|
|
119
|
+
boxed?: Buffer;
|
|
120
|
+
/** The WebP's edges are feathered into transparency for the beat's box. */
|
|
121
|
+
feathered?: boolean;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Pictures beside the scene cache, under `art/`: `<key>.png` (as drawn),
|
|
125
|
+
* `<key>.webp` (the deck's copy), `<key>.boxed.png` (subjects numbered, for the
|
|
126
|
+
* draft call) and `<key>.json`.
|
|
127
|
+
*/
|
|
128
|
+
export declare class ArtCache {
|
|
129
|
+
readonly dir: string;
|
|
130
|
+
constructor(dir: string);
|
|
131
|
+
private ref;
|
|
132
|
+
get(key: string): Promise<ArtRef | undefined>;
|
|
133
|
+
put(key: string, bytes: Buffer, meta: Omit<ArtMeta, "version" | "key">, copies?: ArtCopies): Promise<ArtRef>;
|
|
134
|
+
}
|
|
135
|
+
export interface DrawArtOptions {
|
|
136
|
+
run: Runner;
|
|
137
|
+
/** Scratch: the prompt, the schema and the reply go here. */
|
|
138
|
+
work: string;
|
|
139
|
+
tag: string;
|
|
140
|
+
model: string;
|
|
141
|
+
timeoutMs: number;
|
|
142
|
+
/** `-c` overrides: tool-less except the image tool (`artCodexConfig`). */
|
|
143
|
+
config: readonly string[];
|
|
144
|
+
bin?: string;
|
|
145
|
+
onUsage?: (tokens: number) => void;
|
|
146
|
+
/** Where the image tool saves: `codexHome()` unless a test says otherwise. */
|
|
147
|
+
home?: string;
|
|
148
|
+
/** Why the previous picture of this beat was rejected, for the second attempt. */
|
|
149
|
+
retry?: string;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* One art call. Resolves to the picture's bytes and what it depicts, or throws
|
|
153
|
+
* with the reason — which the pass reports, and then draws the beat without.
|
|
154
|
+
*/
|
|
155
|
+
export declare function drawArt(brief: ArtBrief, opts: DrawArtOptions): Promise<{
|
|
156
|
+
bytes: Buffer;
|
|
157
|
+
width: number;
|
|
158
|
+
height: number;
|
|
159
|
+
depicts: string;
|
|
160
|
+
}>;
|
|
161
|
+
/**
|
|
162
|
+
* The picture at `path`, if it is one the image tool saved: inside
|
|
163
|
+
* `<home>/generated_images/`, after symlinks; a PNG by its magic bytes; under
|
|
164
|
+
* the size cap. Anything else is refused by name — the path is model output.
|
|
165
|
+
*/
|
|
166
|
+
export declare function readPicture(path: string, home: string): Promise<Buffer>;
|
|
167
|
+
/** Copy every illustration a bespoke map uses into `<out>/assets/bespoke/`. */
|
|
168
|
+
export declare function copyArt(entries: Iterable<{
|
|
169
|
+
art?: ArtRef;
|
|
170
|
+
}>, out: string): Promise<string[]>;
|
|
171
|
+
/** Where a deck's page finds an illustration. */
|
|
172
|
+
export declare function artHref(art: ArtRef): string;
|
|
173
|
+
export {};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { Fragment } from "./contract.js";
|
|
2
|
+
/** Bump when the shape of an entry changes. */
|
|
3
|
+
export declare const CACHE_VERSION = 1;
|
|
4
|
+
/** What a key is computed from. Every field is part of the scene's meaning. */
|
|
5
|
+
export interface KeyInput {
|
|
6
|
+
promptVersion: string;
|
|
7
|
+
contractVersion: string;
|
|
8
|
+
/** `codex exec --model`, or `"default"` for the account's own. */
|
|
9
|
+
model: string;
|
|
10
|
+
lang: string;
|
|
11
|
+
beat: {
|
|
12
|
+
id: string;
|
|
13
|
+
archetype: string;
|
|
14
|
+
intent: string;
|
|
15
|
+
claim?: string;
|
|
16
|
+
narration?: string;
|
|
17
|
+
params: unknown;
|
|
18
|
+
};
|
|
19
|
+
/** The paper excerpts and equations the prompt quoted, verbatim. */
|
|
20
|
+
context: string;
|
|
21
|
+
/** Scene-relative cue windows and their words. */
|
|
22
|
+
cues: ReadonlyArray<{
|
|
23
|
+
t0: number;
|
|
24
|
+
t1: number;
|
|
25
|
+
text: string;
|
|
26
|
+
}>;
|
|
27
|
+
duration: number;
|
|
28
|
+
holds: readonly number[];
|
|
29
|
+
region: {
|
|
30
|
+
width: number;
|
|
31
|
+
height: number;
|
|
32
|
+
};
|
|
33
|
+
/** The pack: name, colours, faces. */
|
|
34
|
+
pack: unknown;
|
|
35
|
+
/** The illustration's art key (src/bespoke/art.ts), when the scene was drawn around one. */
|
|
36
|
+
art?: string;
|
|
37
|
+
}
|
|
38
|
+
export interface CacheEntry {
|
|
39
|
+
version: number;
|
|
40
|
+
key: string;
|
|
41
|
+
verdict: "accepted" | "rejected";
|
|
42
|
+
fragment?: Fragment;
|
|
43
|
+
/** Why it was rejected, or which round's scene was kept. */
|
|
44
|
+
note: string;
|
|
45
|
+
/** Codex calls it took to reach this verdict. */
|
|
46
|
+
calls: number;
|
|
47
|
+
model: string;
|
|
48
|
+
promptVersion: string;
|
|
49
|
+
/**
|
|
50
|
+
* `GATES_VERSION` when the verdict was reached. A rejection is the gates'
|
|
51
|
+
* opinion, so one from other gates is not trusted; an acceptance is re-gated
|
|
52
|
+
* on every build anyway.
|
|
53
|
+
*/
|
|
54
|
+
gates?: string;
|
|
55
|
+
/** The art key of the illustration the scene places, when it has one. */
|
|
56
|
+
art?: string;
|
|
57
|
+
}
|
|
58
|
+
/** JSON with sorted keys, so two equal objects hash equal whatever order they were built in. */
|
|
59
|
+
export declare function canonical(value: unknown): string;
|
|
60
|
+
export declare function cacheKey(input: KeyInput): string;
|
|
61
|
+
/** `$XDG_CACHE_HOME/decksmith/bespoke`, else `~/.cache/decksmith/bespoke`. */
|
|
62
|
+
export declare function defaultCacheDir(env?: NodeJS.ProcessEnv): string;
|
|
63
|
+
export declare class SceneCache {
|
|
64
|
+
readonly dir: string;
|
|
65
|
+
constructor(dir: string);
|
|
66
|
+
get(key: string): Promise<CacheEntry | undefined>;
|
|
67
|
+
/** Written to a temp name and renamed, so a reader never sees half an entry. */
|
|
68
|
+
put(entry: CacheEntry): Promise<void>;
|
|
69
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The labels of an illustrated scene, drawn by the shell ON their subjects.
|
|
3
|
+
*
|
|
4
|
+
* WHY THE SHELL AND NOT THE SCENE. Round 3's labels were plates in a row along
|
|
5
|
+
* the top of the box. Round 4's first run (2026-10-09, en deck) asked the scene
|
|
6
|
+
* to put each label on its subject itself: the labels went on their subjects
|
|
7
|
+
* (3-4 of 3-4 within 96px in every draft), and four of five scenes then failed
|
|
8
|
+
* on their own plates and leader lines running through their own text — the
|
|
9
|
+
* model guesses a label's width, and at 56px a guess is wrong by more than the
|
|
10
|
+
* plate's margin. The shell knows the width (the same estimator the archetypes
|
|
11
|
+
* lay out with), so the scene says WHAT each subject is called (`labels`) and
|
|
12
|
+
* the shell says WHERE: a plate in a zone just above the subject (or across
|
|
13
|
+
* its top when there is no room above), a leader line to a dot on it, entering
|
|
14
|
+
* when the camera first arrives on that subject. One grammar for every deck.
|
|
15
|
+
*
|
|
16
|
+
* Zones are fixed from the subjects alone, before the scene is written, so the
|
|
17
|
+
* prompt can tell the scene where they are and to keep its own drawing out.
|
|
18
|
+
*/
|
|
19
|
+
import type { Theme } from "../emit/kit.js";
|
|
20
|
+
import type { Box, CamMove } from "./shots.js";
|
|
21
|
+
/** One subject's label, as the scene names it. */
|
|
22
|
+
export interface Label {
|
|
23
|
+
subject: number;
|
|
24
|
+
text: string;
|
|
25
|
+
}
|
|
26
|
+
/** Where a subject's label goes: its zone (box px), and the dot its leader ends on. */
|
|
27
|
+
export interface Zone extends Box {
|
|
28
|
+
subject: number;
|
|
29
|
+
/** "above" the subject, or across its "top" inside it. */
|
|
30
|
+
side: "above" | "top";
|
|
31
|
+
dot: {
|
|
32
|
+
x: number;
|
|
33
|
+
y: number;
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
/** The label's size, and the floor it may shrink to to fit its zone. */
|
|
37
|
+
export declare const LABEL_PX = 56;
|
|
38
|
+
export declare const LABEL_MIN_PX = 44;
|
|
39
|
+
/**
|
|
40
|
+
* Each subject's label zone: centred over the subject, as wide as half the
|
|
41
|
+
* distance to each neighbour allows (so neighbours' zones never meet), above
|
|
42
|
+
* the subject when there is room, else across its top.
|
|
43
|
+
*/
|
|
44
|
+
export declare function calloutZones(subjects: readonly Box[], W: number): Zone[];
|
|
45
|
+
/** How a label is set: its size and its line(s). */
|
|
46
|
+
export interface Fit {
|
|
47
|
+
fs: number;
|
|
48
|
+
lines: string[];
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* How a label fits its zone: one line from `LABEL_PX` down to `LABEL_MIN_PX`,
|
|
52
|
+
* else two lines (the plate grows away from the subject, as far as the box
|
|
53
|
+
* allows), else not at all.
|
|
54
|
+
*/
|
|
55
|
+
export declare function fitLabel(text: string, zone: Zone, theme: Theme, boxH?: number): Fit | undefined;
|
|
56
|
+
/**
|
|
57
|
+
* Roughly how many characters fit a zone at the floor size, for the prompt
|
|
58
|
+
* ("at most N characters"): Latin lowercase if the deck is Latin, else the
|
|
59
|
+
* deck's script, at `LABEL_MIN_PX`. The fit itself is `fitLabel`'s.
|
|
60
|
+
*/
|
|
61
|
+
export declare function longestFit(zone: Zone, theme: Theme): number;
|
|
62
|
+
/** What the camera frames for a subject: the subject and its label zone together. */
|
|
63
|
+
export declare function withZone(s: Box, z: Zone | undefined): Box;
|
|
64
|
+
/** When each subject's label enters: as the camera arrives on it, else at the reveal. */
|
|
65
|
+
export declare function entrances(moves: readonly CamMove[], subjects: number): Map<number, number>;
|
|
66
|
+
/**
|
|
67
|
+
* The labels as markup and GSAP for the shell to put in the scene: a layer of
|
|
68
|
+
* `<g data-subject="K">` groups (so `label_anchor` measures them like any
|
|
69
|
+
* label), each hidden until its entrance, then the plate rises in, the leader
|
|
70
|
+
* draws to the dot. Labels that do not fit their zone are left out (the
|
|
71
|
+
* static check refuses a scene that has one, so this is belt and braces).
|
|
72
|
+
*/
|
|
73
|
+
export declare function calloutLayer(sid: string, labels: readonly Label[], zones: readonly Zone[], moves: readonly CamMove[], theme: Theme, W: number, H: number): {
|
|
74
|
+
markup: string;
|
|
75
|
+
script: string;
|
|
76
|
+
};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { type Label } from "./callouts.js";
|
|
2
|
+
import type { Shot } from "./shots.js";
|
|
3
|
+
/** What a model hands back for one beat. Token form: ids are `SCENEID-…`. */
|
|
4
|
+
export interface Fragment {
|
|
5
|
+
markup: string;
|
|
6
|
+
css: string;
|
|
7
|
+
script: string;
|
|
8
|
+
/**
|
|
9
|
+
* An illustrated scene's shot list: which subject of the picture the shell's
|
|
10
|
+
* camera frames on which cue (src/bespoke/shots.ts). Absent on any other scene.
|
|
11
|
+
*/
|
|
12
|
+
shots?: Shot[];
|
|
13
|
+
/**
|
|
14
|
+
* An illustrated scene's subject labels, which the shell draws ON their
|
|
15
|
+
* subjects (src/bespoke/callouts.ts). Absent on any other scene.
|
|
16
|
+
*/
|
|
17
|
+
labels?: Label[];
|
|
18
|
+
}
|
|
19
|
+
/** One reason a fragment may not be used. */
|
|
20
|
+
export interface StaticFinding {
|
|
21
|
+
rule: string;
|
|
22
|
+
message: string;
|
|
23
|
+
}
|
|
24
|
+
/** The placeholder scene id a fragment is written against. */
|
|
25
|
+
export declare const SID_TOKEN = "SCENEID";
|
|
26
|
+
/** Bounds on what one scene may weigh. A scene that needs more is not a scene. */
|
|
27
|
+
export declare const MAX_SCRIPT = 40000;
|
|
28
|
+
export declare const MAX_MARKUP = 60000;
|
|
29
|
+
export declare const MAX_CSS = 12000;
|
|
30
|
+
/** The fragment with the real scene id in place of the token. */
|
|
31
|
+
export declare function instantiate(f: Fragment, sid: string): Fragment;
|
|
32
|
+
/** Every reason this fragment may not be built. Empty means it may. */
|
|
33
|
+
export declare function checkFragment(f: Fragment, ctx?: FragmentContext): StaticFinding[];
|
|
34
|
+
export declare function scopedSelector(selector: string): boolean;
|
|
35
|
+
/** What `checkFragment` needs to know about the beat beyond the fragment. */
|
|
36
|
+
export interface FragmentContext {
|
|
37
|
+
/** Whether the beat has an illustration a scene may place (`<image data-art="1">`). */
|
|
38
|
+
art?: boolean;
|
|
39
|
+
/** How many subjects the illustration's inspection found (src/bespoke/inspect.ts). */
|
|
40
|
+
subjects?: number;
|
|
41
|
+
/** How many narration cues the scene has, for its shot list. */
|
|
42
|
+
cues?: number;
|
|
43
|
+
/** Whether a label's text fits its subject's zone (src/bespoke/callouts.ts `fitLabel`). */
|
|
44
|
+
labelFits?: (text: string, subject: number) => boolean;
|
|
45
|
+
}
|
|
46
|
+
export declare function checkMarkup(markup: string, ctx?: FragmentContext): StaticFinding[];
|
|
47
|
+
export declare function checkCss(css: string): StaticFinding[];
|
|
48
|
+
export declare function checkScript(script: string): StaticFinding[];
|
|
49
|
+
/**
|
|
50
|
+
* The kinds of motion a scene's timeline uses, read off its tweens' vars —
|
|
51
|
+
* for the report, and for the rubric probe that decides whether a scene that
|
|
52
|
+
* passed every gate still needs the critique round. Static, so it is a
|
|
53
|
+
* statement about what the script ASKS for; the frames say whether it shows.
|
|
54
|
+
*/
|
|
55
|
+
export declare const MOTION_KINDS: readonly ["draw", "morph", "camera", "counter", "stagger", "flow", "move", "scale", "focus", "recolor"];
|
|
56
|
+
export type MotionKind = (typeof MOTION_KINDS)[number];
|
|
57
|
+
export declare function motionKinds(script: string): MotionKind[];
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { type Frame } from "../verify/fidelity.js";
|
|
2
|
+
/** A subject's box as a share of the picture: x, y, w, h in 0..1. */
|
|
3
|
+
export type UnitBox = [number, number, number, number];
|
|
4
|
+
/** At most this many subjects are named, largest first. */
|
|
5
|
+
export declare const MAX_SUBJECTS = 5;
|
|
6
|
+
/**
|
|
7
|
+
* The ground: the median colour of the picture's outer frame (2% each side).
|
|
8
|
+
* The illustrator is asked for the pack's exact background; what comes back is
|
|
9
|
+
* close to it, and the frame is what the scene's feathered edge blends into.
|
|
10
|
+
*/
|
|
11
|
+
export declare function groundOf(f: Frame): [number, number, number];
|
|
12
|
+
/**
|
|
13
|
+
* The subjects' boxes, left to right, as shares of the picture. Components of
|
|
14
|
+
* the foreground mask after a small dilation (so a subject's own parts join),
|
|
15
|
+
* boxed on the UNdilated cells, specks dropped, the largest `MAX_SUBJECTS` kept.
|
|
16
|
+
*/
|
|
17
|
+
export declare function subjectBoxes(f: Frame, ground?: [number, number, number]): UnitBox[];
|
|
18
|
+
/** How flat a picture is drawn: the measures `flatEnough` reads. */
|
|
19
|
+
export interface Flatness {
|
|
20
|
+
/** Share of the subjects' pixel pairs that differ a little (shading), 0..1. */
|
|
21
|
+
soft: number;
|
|
22
|
+
/** Share of the subjects' pixels in their 16 commonest colours (4 bits a channel), 0..1. */
|
|
23
|
+
palette: number;
|
|
24
|
+
/** `palette - soft`: high for flat fills with hard edges, low for a shaded render. */
|
|
25
|
+
score: number;
|
|
26
|
+
/** Share of the picture that is not ground. */
|
|
27
|
+
coverage: number;
|
|
28
|
+
}
|
|
29
|
+
export declare function flatness(f: Frame, ground?: [number, number, number]): Flatness;
|
|
30
|
+
/**
|
|
31
|
+
* The flat-art bar on `score`. CALIBRATED 2026-10-09 on 21 round-3 pictures
|
|
32
|
+
* (soft 3D toy renders by eye: 0.04-0.46, one at 0.557 — a shaded robot beside
|
|
33
|
+
* a big flat curtain) and 58 pictures drawn by the round-4 prompt (0.48-0.84).
|
|
34
|
+
* First set at 0.57, it refused two pictures at 0.56 and 0.565 that are flat by
|
|
35
|
+
* eye (many small flat colours), each a 60s redraw on the critical path; the
|
|
36
|
+
* one round-4 picture under 0.52 (0.48) has soft shading on its figures. So
|
|
37
|
+
* 0.52: 20 of 21 round-3 renders refused, no flat picture seen refused. The
|
|
38
|
+
* margin is thin both ways; a near miss costs one redraw, never the picture
|
|
39
|
+
* (`illustrate` keeps the flatter of two attempts).
|
|
40
|
+
*/
|
|
41
|
+
export declare const FLAT_MIN = 0.52;
|
|
42
|
+
export declare function flatEnough(m: Flatness): boolean;
|
|
43
|
+
/** One run of text Vision read, with its box as shares of the picture (top-left origin). */
|
|
44
|
+
export interface ReadText {
|
|
45
|
+
s: string;
|
|
46
|
+
c: number;
|
|
47
|
+
b: UnitBox;
|
|
48
|
+
l?: string;
|
|
49
|
+
}
|
|
50
|
+
/** Whether a run is writing and not a shape Vision guessed at: confident, or long and fairly sure. */
|
|
51
|
+
export declare function isWriting(t: ReadText): boolean;
|
|
52
|
+
/**
|
|
53
|
+
* The Swift helper: Vision's accurate recogniser, once per script the decks
|
|
54
|
+
* are written in (one request with all four reads only Latin reliably —
|
|
55
|
+
* MEASURED 2026-10-09: 학습 단계, 学習の流れ, 训练过程 were found only by their own
|
|
56
|
+
* language's pass). Prints one JSON line per file.
|
|
57
|
+
*/
|
|
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
|
+
/** The writing in a picture; rejects (with why) when nothing here can read it. */
|
|
60
|
+
export declare function readText(png: string, toolDir: string): Promise<ReadText[]>;
|
|
61
|
+
/** Everything the inspection says about one picture. */
|
|
62
|
+
export interface Inspection {
|
|
63
|
+
subjects: UnitBox[];
|
|
64
|
+
flat: Flatness;
|
|
65
|
+
/** Writing found, or null when no reader was available (`unread` says why). */
|
|
66
|
+
text: string[] | null;
|
|
67
|
+
unread?: string;
|
|
68
|
+
}
|
|
69
|
+
export declare function inspectPicture(bytes: Buffer, file: string, toolDir: string): Promise<Inspection>;
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import { type DeckNarration } from "../emit/composition.js";
|
|
2
|
+
import { type Runner } from "../plan/codex.js";
|
|
3
|
+
import type { Prefs } from "../prefs.js";
|
|
4
|
+
import type { Beat, Format, Source, Storyboard } from "../types.js";
|
|
5
|
+
import { type Layout } from "../verify/scenes.js";
|
|
6
|
+
import { type ArtCheck, type ArtCopies } from "./art.js";
|
|
7
|
+
import { type Label } from "./callouts.js";
|
|
8
|
+
import { type Inspection } from "./inspect.js";
|
|
9
|
+
import { type Measured } from "./prompt.js";
|
|
10
|
+
import { type RepairNote } from "./repair.js";
|
|
11
|
+
import { type BespokeMap } from "./scene.js";
|
|
12
|
+
import { type Skip } from "./select.js";
|
|
13
|
+
import { type Shot } from "./shots.js";
|
|
14
|
+
export type BespokePrefs = NonNullable<Prefs["bespoke"]>;
|
|
15
|
+
/** What a gate round says about one beat's candidate scene. */
|
|
16
|
+
export interface GateResult {
|
|
17
|
+
/** One line per finding, fed verbatim to the critique round. */
|
|
18
|
+
findings: string[];
|
|
19
|
+
failed: boolean;
|
|
20
|
+
/** A contact sheet of the probe frames, for `codex exec -i`. */
|
|
21
|
+
sheet?: string;
|
|
22
|
+
/** What each cell of the sheet is. */
|
|
23
|
+
legend?: string;
|
|
24
|
+
/** What the probe measured: the rubric probe reads it, the critique round is told it. */
|
|
25
|
+
metrics?: Measured;
|
|
26
|
+
/** Warnings about the scene itself (not the storyboard's), by rule. Any one sends it to critique. */
|
|
27
|
+
warnings?: string[];
|
|
28
|
+
/** Every graded frame's layout with its repair geometry (src/bespoke/repair.ts reads it). */
|
|
29
|
+
layout?: Layout[];
|
|
30
|
+
/** The scene id the probe deck drew it at (to read ids in `layout` back to token form). */
|
|
31
|
+
sid?: string;
|
|
32
|
+
}
|
|
33
|
+
/** Which probe: the drafts, the final candidates, or a repair round's. */
|
|
34
|
+
export type GateRound = "draft" | "final" | "repair";
|
|
35
|
+
/** Builds a deck with these candidates, gates each, and says what it found — keyed by beat id. */
|
|
36
|
+
export type GateFn = (candidates: BespokeMap, round: GateRound) => Promise<Map<string, GateResult>>;
|
|
37
|
+
export interface BespokeInput {
|
|
38
|
+
storyboard: Storyboard;
|
|
39
|
+
source: Source;
|
|
40
|
+
format: Format;
|
|
41
|
+
narration: DeckNarration;
|
|
42
|
+
/** The resolved pack name, as `build` picked it. */
|
|
43
|
+
theme: string;
|
|
44
|
+
speed: number;
|
|
45
|
+
prefs: BespokePrefs;
|
|
46
|
+
/** Builds and gates a probe deck. `build` passes the browser gate (src/bespoke/probe.ts). */
|
|
47
|
+
gate: GateFn;
|
|
48
|
+
onStep?: (message: string) => void;
|
|
49
|
+
/** Swappable for tests; the production runner is `codex exec`. */
|
|
50
|
+
run?: Runner;
|
|
51
|
+
now?: () => number;
|
|
52
|
+
/** Scratch for prompts, replies and sheets. Default: a temp dir, removed at the end. */
|
|
53
|
+
work?: string;
|
|
54
|
+
/** Where the image tool saves pictures. Default `$CODEX_HOME` or `~/.codex` (tests point it elsewhere). */
|
|
55
|
+
codexHome?: string;
|
|
56
|
+
/** Inspects a drawn picture (src/bespoke/inspect.ts); swappable so tests need no Swift or Vision. */
|
|
57
|
+
inspect?: (bytes: Buffer, file: string) => Promise<Inspection>;
|
|
58
|
+
/** Draws the deck's WebP and the boxed copy (src/bespoke/sheet.ts); swappable so tests need no Chrome. */
|
|
59
|
+
copies?: (png: Buffer, boxes: Inspection["subjects"], box: {
|
|
60
|
+
width: number;
|
|
61
|
+
height: number;
|
|
62
|
+
}) => Promise<ArtCopies>;
|
|
63
|
+
}
|
|
64
|
+
export interface SceneReport {
|
|
65
|
+
beat: string;
|
|
66
|
+
archetype: string;
|
|
67
|
+
status: "bespoke" | "fallback";
|
|
68
|
+
/** Where the scene came from, when there is one. */
|
|
69
|
+
from?: "cache" | "draft" | "critique";
|
|
70
|
+
/** Why it fell back, or a note on the scene kept. */
|
|
71
|
+
reason?: string;
|
|
72
|
+
calls: number;
|
|
73
|
+
/** The last round's findings, for whoever reads this file. */
|
|
74
|
+
findings: string[];
|
|
75
|
+
key?: string;
|
|
76
|
+
/** The scene id it was drawn at, filled in once the deck is emitted. */
|
|
77
|
+
sid?: string;
|
|
78
|
+
/** Motion kinds the kept scene's script uses. */
|
|
79
|
+
kinds?: string[];
|
|
80
|
+
/** The probe's measures of the kept scene (or of the last candidate, on a fallback). */
|
|
81
|
+
metrics?: Measured;
|
|
82
|
+
/** Whether a critique call was made, skipped because the rubric probe was clean, or never reached. */
|
|
83
|
+
critique?: "ran" | "skipped" | "none";
|
|
84
|
+
/** The illustration the scene was built around, and what its inspection said. */
|
|
85
|
+
art?: {
|
|
86
|
+
key: string;
|
|
87
|
+
depicts: string;
|
|
88
|
+
subjects?: number;
|
|
89
|
+
check?: ArtCheck;
|
|
90
|
+
};
|
|
91
|
+
/** A data beat: drawn as a chart, never on a picture. */
|
|
92
|
+
data?: boolean;
|
|
93
|
+
/** Why there is none, when one was wanted. */
|
|
94
|
+
artNote?: string;
|
|
95
|
+
/** What the deterministic repair did to the kept scene (src/bespoke/repair.ts). */
|
|
96
|
+
repair?: RepairNote & {
|
|
97
|
+
rounds: number;
|
|
98
|
+
};
|
|
99
|
+
/** The kept scene passed only after a repair: without one, this beat fell back. */
|
|
100
|
+
savedByRepair?: boolean;
|
|
101
|
+
/**
|
|
102
|
+
* The scene id the probe deck had it at when the gates passed the kept bytes:
|
|
103
|
+
* `build` skips re-running the full motion gates on a scene at that same id
|
|
104
|
+
* (it still seeks it), because the pass already ran them on exactly this scene.
|
|
105
|
+
*/
|
|
106
|
+
gatedAt?: string;
|
|
107
|
+
}
|
|
108
|
+
export interface BespokeReport {
|
|
109
|
+
version: 1;
|
|
110
|
+
model: string;
|
|
111
|
+
promptVersion: string;
|
|
112
|
+
contractVersion: string;
|
|
113
|
+
gates: string;
|
|
114
|
+
caps: {
|
|
115
|
+
calls: number;
|
|
116
|
+
seconds: number;
|
|
117
|
+
};
|
|
118
|
+
calls: number;
|
|
119
|
+
tokens: number;
|
|
120
|
+
seconds: number;
|
|
121
|
+
quota: boolean;
|
|
122
|
+
/**
|
|
123
|
+
* Illustrations: the deck's cap, calls made, pictures used (drawn or cached),
|
|
124
|
+
* redraws made after a rejection, and pictures rejected for text / for style.
|
|
125
|
+
*/
|
|
126
|
+
art: {
|
|
127
|
+
cap: number;
|
|
128
|
+
calls: number;
|
|
129
|
+
used: number;
|
|
130
|
+
redraws: number;
|
|
131
|
+
rejectedText: number;
|
|
132
|
+
rejectedStyle: number;
|
|
133
|
+
rejectedSubjects: number;
|
|
134
|
+
};
|
|
135
|
+
/** Wall seconds of the pass's stages, for profiling (`build` adds its own). */
|
|
136
|
+
stages?: Record<string, number>;
|
|
137
|
+
scenes: SceneReport[];
|
|
138
|
+
skipped: Skip[];
|
|
139
|
+
}
|
|
140
|
+
export interface BespokeResult {
|
|
141
|
+
map: BespokeMap;
|
|
142
|
+
report: BespokeReport;
|
|
143
|
+
}
|
|
144
|
+
/** Repair rounds after the final gates, each one probe build of the repaired scenes only. */
|
|
145
|
+
export declare const REPAIR_ROUNDS = 2;
|
|
146
|
+
export declare class Budget {
|
|
147
|
+
readonly cap: number;
|
|
148
|
+
readonly deadline: number;
|
|
149
|
+
readonly now: () => number;
|
|
150
|
+
calls: number;
|
|
151
|
+
/** Illustration calls, capped apart from `calls` (`prefs.art`). */
|
|
152
|
+
art: number;
|
|
153
|
+
/** Second draws after a rejected picture: one per beat at most, under the same cap plus one each. */
|
|
154
|
+
redraws: number;
|
|
155
|
+
rejectedText: number;
|
|
156
|
+
rejectedStyle: number;
|
|
157
|
+
rejectedSubjects: number;
|
|
158
|
+
tokens: number;
|
|
159
|
+
quota: boolean;
|
|
160
|
+
constructor(cap: number, deadline: number, now: () => number);
|
|
161
|
+
/** Undefined if a call may go ahead (and is counted), else why not. */
|
|
162
|
+
take(): string | undefined;
|
|
163
|
+
/** As `take`, for an illustration call against its own cap. */
|
|
164
|
+
takeArt(cap: number): string | undefined;
|
|
165
|
+
/** One redraw of a rejected picture: not against the art cap, but against the clock and the quota. */
|
|
166
|
+
takeRedraw(): string | undefined;
|
|
167
|
+
seconds(): number;
|
|
168
|
+
}
|
|
169
|
+
/** Below this share of the frame, a cue "barely moves" by the rubric's measure (static_hold's floor is 0.015%). */
|
|
170
|
+
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
|
+
/**
|
|
174
|
+
* The share of its box a settled drawing should paint. MEASURED 2026-10-08:
|
|
175
|
+
* round 1's sixteen scenes painted 3.9-15.6% (median ~9%) — thin outlines and
|
|
176
|
+
* small captions — and round 2's first twenty 10-57% (median ~25%).
|
|
177
|
+
*/
|
|
178
|
+
export declare const RUBRIC_MASS = 0.12;
|
|
179
|
+
/**
|
|
180
|
+
* More than this share of the drawn parts under 0.6 opacity at the settled
|
|
181
|
+
* frame, and the end is not a summary. Round 2's first full run left zh s6 and
|
|
182
|
+
* en s6 ending on a frame of ghosts with one lit part.
|
|
183
|
+
*/
|
|
184
|
+
export declare const RUBRIC_DIMMED = 0.5;
|
|
185
|
+
/**
|
|
186
|
+
* The rubric probe: is a scene that passed every gate also clean by the
|
|
187
|
+
* rubric's MEASURABLE half? Free — it reads what the probe already measured —
|
|
188
|
+
* and it decides whether the critique call (~4 minutes, ~30k tokens) is spent.
|
|
189
|
+
* Empty means clean. What it cannot measure (is this the right picture for the
|
|
190
|
+
* sentence?) it does not pretend to; that is the reason a clean scene is still
|
|
191
|
+
* looked at by a person in the preview.
|
|
192
|
+
*/
|
|
193
|
+
export declare function rubricProbe(m: Measured | undefined, warnings?: readonly string[], art?: boolean): string[];
|
|
194
|
+
/**
|
|
195
|
+
* Archetypes whose beat IS its numbers. Round 3 illustrated them like any other
|
|
196
|
+
* beat and the scene painted the table over the picture (ja s12: nine numbers
|
|
197
|
+
* on a robot). A data beat gets no picture and the chart that builds instead.
|
|
198
|
+
*/
|
|
199
|
+
export declare const DATA_ARCHETYPES: readonly string[];
|
|
200
|
+
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
|
+
/** A reply's shot list, kept only where it is the shape the schema promised. */
|
|
204
|
+
export declare function readShots(raw: unknown): Shot[];
|
|
205
|
+
/** The paper the prompt may quote for this beat: its cited sections, equations and figure captions. */
|
|
206
|
+
export declare function contextFor(beat: Beat, source: Source): string;
|
|
207
|
+
export declare function bespokePass(input: BespokeInput): Promise<BespokeResult>;
|