@jokerized/decksmith 0.9.0 → 0.9.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +213 -0
- package/dist/cli.js +20037 -14179
- 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 +22 -0
- package/dist/types/verify/scenes.d.ts +325 -0
- package/package.json +2 -1
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
import type { DeckPage } from "../render/capture.js";
|
|
2
|
+
import type { Timing } from "../render/timing.js";
|
|
3
|
+
import type { Finding } from "../types.js";
|
|
4
|
+
import { type Frame } from "./fidelity.js";
|
|
5
|
+
/** A pixel counts as changed when its RGB moved by more than this, summed. Same as the spike's. */
|
|
6
|
+
export declare const PIXEL_DELTA = 24;
|
|
7
|
+
/**
|
|
8
|
+
* Fewer changed pixels than this between two seeks of one instant is rasterising,
|
|
9
|
+
* not state. MEASURED: a never-seeked page against a warm one differs by up to
|
|
10
|
+
* 644px on a scene that looks identical — antialiasing along the headline's
|
|
11
|
+
* glyphs, which the shell's own entrance had moved — and a KaTeX glyph by 529px.
|
|
12
|
+
* The leaks this gate exists for are an order of magnitude past it: 13,958 and
|
|
13
|
+
* 22,037px for the equation-walk bug, a dimmed label in the test at several
|
|
14
|
+
* thousand.
|
|
15
|
+
*/
|
|
16
|
+
export declare const SEEK_TOLERANCE_PX = 1500;
|
|
17
|
+
/** Bump whenever a gate's verdict can change: cached rejections from another version are retried. */
|
|
18
|
+
export declare const GATES_VERSION = "gates-5";
|
|
19
|
+
/**
|
|
20
|
+
* A cue whose picture changes by less than this share of the frame held still:
|
|
21
|
+
* about 310 px at 1080p. A frame that does not move renders to the same bytes,
|
|
22
|
+
* so the floor only has to clear antialiasing; a 14px dot crossing a wire
|
|
23
|
+
* changes ~1,200 px and passes, which is right — that dot IS the explanation.
|
|
24
|
+
* Whether the motion is worth watching is the critique round's question and a
|
|
25
|
+
* person's, not a pixel count's.
|
|
26
|
+
*/
|
|
27
|
+
export declare const MIN_CHANGE = 0.00015;
|
|
28
|
+
/** Cues shorter than this are exempt from `static_hold`: there is no room to show anything. */
|
|
29
|
+
export declare const MIN_CUE_SECONDS = 1.2;
|
|
30
|
+
/** Below this the text is not text the audience can read (AGENTS.md invariant 5). */
|
|
31
|
+
export declare const TYPE_FLOOR_PX = 39.5;
|
|
32
|
+
/** One narration cue on a scene's own clock. */
|
|
33
|
+
export interface SceneCue {
|
|
34
|
+
t0: number;
|
|
35
|
+
t1: number;
|
|
36
|
+
text: string;
|
|
37
|
+
}
|
|
38
|
+
/** A scene to probe, with the cues spoken over it. */
|
|
39
|
+
export interface SceneWindow {
|
|
40
|
+
sid: string;
|
|
41
|
+
beatId?: string;
|
|
42
|
+
/** Absolute composition seconds. */
|
|
43
|
+
start: number;
|
|
44
|
+
duration: number;
|
|
45
|
+
cues: SceneCue[];
|
|
46
|
+
}
|
|
47
|
+
export interface CueChange {
|
|
48
|
+
sid: string;
|
|
49
|
+
cue: number;
|
|
50
|
+
t0: number;
|
|
51
|
+
t1: number;
|
|
52
|
+
changed: number;
|
|
53
|
+
total: number;
|
|
54
|
+
}
|
|
55
|
+
export declare function gradeStillCues(rows: readonly CueChange[], min?: number): Finding[];
|
|
56
|
+
/**
|
|
57
|
+
* The settled frame's drawing must span this share of the body box (the
|
|
58
|
+
* bounding box of everything drawn, over the box's area). Round 1's scenes sat
|
|
59
|
+
* at 55-70%: a diagram in the top-left two thirds with the right third or the
|
|
60
|
+
* bottom band empty, at a size the founder called small.
|
|
61
|
+
*/
|
|
62
|
+
export declare const STAGE_FILL = 0.8;
|
|
63
|
+
/** The settled frame's largest label, px at 1080p: one thing on the stage is read first. */
|
|
64
|
+
export declare const KEY_TYPE_PX = 64;
|
|
65
|
+
/** A cue group may appear this long before its cue starts (the prompt's own sync window). */
|
|
66
|
+
export declare const EARLY_SLACK = 0.5;
|
|
67
|
+
/**
|
|
68
|
+
* At the settled frame, at most this share of a scene's parts may be ones it
|
|
69
|
+
* showed lit (>= `LIT`) earlier and has left dimmed (< `DIM`). Round 2 kept
|
|
70
|
+
* three scenes that ended on a frame of ghosts; the founder's rule is that the
|
|
71
|
+
* last cue ends with the whole scene lit. A part that was never lit — a halo, a
|
|
72
|
+
* translucent cone — is not counted: it is drawn that way, not dimmed.
|
|
73
|
+
*/
|
|
74
|
+
export declare const END_DIMMED = 0.1;
|
|
75
|
+
export declare const LIT = 0.85;
|
|
76
|
+
export declare const DIM = 0.6;
|
|
77
|
+
/** A label as the repair pass sees it: its address, the unit a nudge moves, its box in body px. */
|
|
78
|
+
export interface GeoLabel {
|
|
79
|
+
/** `tag:index` among the body's elements, in document order. */
|
|
80
|
+
a: string;
|
|
81
|
+
/** The unit a nudge would wrap (the text, or its plate group); null when it cannot move. */
|
|
82
|
+
u: string | null;
|
|
83
|
+
/** Document order (paint order). */
|
|
84
|
+
o: number;
|
|
85
|
+
/** x, y, w, h in body px. */
|
|
86
|
+
b: [number, number, number, number];
|
|
87
|
+
/** Screen px per unit of the unit's parent (a camera zoom makes it > 1). */
|
|
88
|
+
s: number;
|
|
89
|
+
fs: number;
|
|
90
|
+
}
|
|
91
|
+
/** What one frame of a bespoke scene looks like to the repair pass. */
|
|
92
|
+
export interface Geo {
|
|
93
|
+
w: number;
|
|
94
|
+
h: number;
|
|
95
|
+
labels: GeoLabel[];
|
|
96
|
+
/** Filled shapes and pictures: address, owning label unit, paint order, box. */
|
|
97
|
+
boxes: Array<{
|
|
98
|
+
a: string;
|
|
99
|
+
u: string | null;
|
|
100
|
+
o: number;
|
|
101
|
+
b: [number, number, number, number];
|
|
102
|
+
}>;
|
|
103
|
+
/** Visible stroke samples, body px, with the label unit they belong to. */
|
|
104
|
+
points: Array<[number, number, string | null]>;
|
|
105
|
+
/** Each visible part's opacity and what dims it (`tag:index`, `#id` appended when it has one). */
|
|
106
|
+
parts: Array<{
|
|
107
|
+
a: string;
|
|
108
|
+
al: number;
|
|
109
|
+
dim: string[];
|
|
110
|
+
}>;
|
|
111
|
+
}
|
|
112
|
+
/** What the page measured at one frame of one scene. */
|
|
113
|
+
export interface Layout {
|
|
114
|
+
sid: string;
|
|
115
|
+
key: string;
|
|
116
|
+
t: number;
|
|
117
|
+
crossings: string[];
|
|
118
|
+
occlusions: string[];
|
|
119
|
+
overlaps: string[];
|
|
120
|
+
small: string[];
|
|
121
|
+
off: string[];
|
|
122
|
+
/** Markers painted where their line is not (an arrowhead on an undrawn path). */
|
|
123
|
+
strays?: string[];
|
|
124
|
+
/** Bounding box of what is drawn in the body box, over the box's area. Bespoke scenes only. */
|
|
125
|
+
fill?: number;
|
|
126
|
+
/** Share of a 6x4 grid over the body box that something is drawn in. Reported, not graded. */
|
|
127
|
+
cells?: number;
|
|
128
|
+
/** The largest visible label in the body box, px. */
|
|
129
|
+
maxType?: number;
|
|
130
|
+
/** Share of the body box the settled drawing paints (`paintedShare`). Reported; the rubric probe reads it. */
|
|
131
|
+
mass?: number;
|
|
132
|
+
/** Share of the drawn labels and shapes in the body box at under 0.6 opacity. */
|
|
133
|
+
dimmed?: number;
|
|
134
|
+
/** Distinct `data-cue` numbers the scene's groups declare. */
|
|
135
|
+
groups?: number[];
|
|
136
|
+
/** The cue groups showing at this instant. */
|
|
137
|
+
revealed?: Array<{
|
|
138
|
+
id: string;
|
|
139
|
+
cue: number;
|
|
140
|
+
}>;
|
|
141
|
+
/** Scene seconds each cue starts at, so `early_reveal` can be graded off this row alone. */
|
|
142
|
+
cueStarts?: number[];
|
|
143
|
+
/** Where the camera is when it is not home (bespoke scenes). Graded at the end only. */
|
|
144
|
+
camOff?: string;
|
|
145
|
+
/** Repair geometry, when the probe asked for it. */
|
|
146
|
+
geo?: Geo;
|
|
147
|
+
/** End row: the share of parts lit earlier and left dimmed (`END_DIMMED`). */
|
|
148
|
+
litDimmed?: number;
|
|
149
|
+
/** End row: the elements dimming those parts, as `tag:index[#id]`. */
|
|
150
|
+
relight?: string[];
|
|
151
|
+
/** The shell's camera at this instant: scale, x, y, and the box's width and height. */
|
|
152
|
+
shot?: [number, number, number, number, number];
|
|
153
|
+
/** How many subjects the scene's illustration has (0 or absent: not an illustrated scene). */
|
|
154
|
+
subjects?: number;
|
|
155
|
+
/** Each visible label that names a subject: its gap to that subject (box px) and how much of it another subject covers. */
|
|
156
|
+
anchors?: Array<{
|
|
157
|
+
id: string;
|
|
158
|
+
k: number;
|
|
159
|
+
d: number;
|
|
160
|
+
o: number;
|
|
161
|
+
}>;
|
|
162
|
+
/** Labels with digits in them sitting on the illustration. */
|
|
163
|
+
onPicture?: number;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* A label that names a subject sits within this many box px of that
|
|
167
|
+
* subject's box (0 = on it). A leader line may bridge the gap; a row of plates
|
|
168
|
+
* along the top of the box, round 3's habit, is 150-400px away.
|
|
169
|
+
*/
|
|
170
|
+
export declare const ANCHOR_PX = 96;
|
|
171
|
+
/** A label may cover at most this share of itself with ANOTHER subject's box. */
|
|
172
|
+
export declare const ANCHOR_OVERLAP = 0.25;
|
|
173
|
+
/** This many numbers on the picture is a table or a chart painted over it (ja s12, round 3: nine). */
|
|
174
|
+
export declare const NUMBERS_ON_PICTURE = 5;
|
|
175
|
+
/** A shot at this scale or closer is a push-in; under `WIDE` it is the whole picture. */
|
|
176
|
+
export declare const CLOSE = 1.5;
|
|
177
|
+
export declare const WIDE = 1.1;
|
|
178
|
+
/** The camera is sampled this often (scene seconds) for `shot_variety`: no screenshot, a seek and a read. */
|
|
179
|
+
export declare const CAM_STEP = 0.5;
|
|
180
|
+
/** The shell's camera at one instant of one scene (`probeScenes` samples it every `CAM_STEP`). */
|
|
181
|
+
export interface CamSample {
|
|
182
|
+
sid: string;
|
|
183
|
+
t: number;
|
|
184
|
+
/** scale, x, y, and the box's width and height. */
|
|
185
|
+
shot: [number, number, number, number, number];
|
|
186
|
+
/** How many subjects the scene's illustration has. */
|
|
187
|
+
subjects: number;
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* The distinct shots a scene's camera HOLDS: a push-in counts when two
|
|
191
|
+
* consecutive samples (one `CAM_STEP` apart) are both at `CLOSE` or closer and
|
|
192
|
+
* aimed within `SAME_SHOT` of each other — a camera passing through on its way
|
|
193
|
+
* somewhere is not a shot. Push-ins aimed within `SAME_SHOT` of one already
|
|
194
|
+
* counted are the same shot.
|
|
195
|
+
*/
|
|
196
|
+
export declare function shotsOf(samples: readonly CamSample[]): {
|
|
197
|
+
wide: boolean;
|
|
198
|
+
close: Array<[number, number]>;
|
|
199
|
+
};
|
|
200
|
+
/**
|
|
201
|
+
* `shot_variety`: an illustrated scene is staged — it OPENS on the whole
|
|
202
|
+
* picture (every sample in the first second of its first cue is wide), and the
|
|
203
|
+
* camera HOLDS push-ins on at least `min(2, subjects)` different subjects
|
|
204
|
+
* (`shotsOf`); that it comes back is `camera_end`'s. `opens` is each scene's
|
|
205
|
+
* first cue start.
|
|
206
|
+
*/
|
|
207
|
+
export declare function gradeShots(samples: readonly CamSample[], opens?: ReadonlyMap<string, number>): Finding[];
|
|
208
|
+
export declare function gradeLayout(rows: readonly Layout[]): Finding[];
|
|
209
|
+
/**
|
|
210
|
+
* What the page did that a scene must not, pinned to the scenes it implicates.
|
|
211
|
+
* A bespoke script that throws is caught by the shell and logged with its scene
|
|
212
|
+
* id (`bespokeCall`), so it lands on that scene alone. A request, a navigation
|
|
213
|
+
* or a CSP refusal names no scene, so it fails every scene probed with it —
|
|
214
|
+
* none of them can be shown to be innocent from inside one page.
|
|
215
|
+
*/
|
|
216
|
+
/**
|
|
217
|
+
* Console errors the MACHINE raises, not the page: no scene can cause or fix
|
|
218
|
+
* them. MEASURED 2026-10-09: "The AudioContext encountered an error from the
|
|
219
|
+
* audio device or the WebAudio renderer" appeared on one probe deck in three
|
|
220
|
+
* and failed all five of its scenes — five critique calls, two fallbacks.
|
|
221
|
+
* Round 1 saw the same line once on bytes that had passed an hour earlier.
|
|
222
|
+
*/
|
|
223
|
+
export declare const ENVIRONMENT_ERRORS: RegExp[];
|
|
224
|
+
export declare function gradeErrors(errors: readonly string[], sids: readonly string[]): Finding[];
|
|
225
|
+
export interface SeekDiff {
|
|
226
|
+
sid: string;
|
|
227
|
+
key: string;
|
|
228
|
+
t: number;
|
|
229
|
+
/** Which comparison: `descending`, `scrambled`, `cold`. */
|
|
230
|
+
order: string;
|
|
231
|
+
px: number;
|
|
232
|
+
}
|
|
233
|
+
export declare function gradeSeekOrder(rows: readonly SeekDiff[], severity?: Finding["severity"], tolerance?: number): Finding[];
|
|
234
|
+
/**
|
|
235
|
+
* The share of a box's pixels that differ between two frames — with and without
|
|
236
|
+
* a scene's drawing — i.e. how much of its stage the drawing actually paints.
|
|
237
|
+
* `box` is in CSS px of a 1920-wide page; frames may be any scale of it.
|
|
238
|
+
*/
|
|
239
|
+
export declare function paintedShare(shown: Frame, hidden: Frame, box: {
|
|
240
|
+
x: number;
|
|
241
|
+
y: number;
|
|
242
|
+
w: number;
|
|
243
|
+
h: number;
|
|
244
|
+
}, delta?: number): number;
|
|
245
|
+
/**
|
|
246
|
+
* Changed pixels that are not on an EDGE of the change: a pixel counts only
|
|
247
|
+
* when it and its 8 neighbours all changed. What `seek_order` compares with.
|
|
248
|
+
*
|
|
249
|
+
* WHY. A picture's first paint can be rasterised a hair differently from a
|
|
250
|
+
* later one (MEASURED 2026-10-09: 12,698px on one illustrated scene's first
|
|
251
|
+
* frame, 2 runs in 3, identical DOM, the difference a 1-2px outline round every
|
|
252
|
+
* subject of the picture). That is the raster, not the scene's state, and it
|
|
253
|
+
* is all edges. A state leak — a term left swollen, a part left lit — changes
|
|
254
|
+
* AREAS, which survive this nearly whole (a 20x20 patch keeps 324 of 400).
|
|
255
|
+
*/
|
|
256
|
+
export declare function changedArea(a: Frame, b: Frame, delta?: number): number;
|
|
257
|
+
export declare function changedPixels(a: Frame, b: Frame, delta?: number): number;
|
|
258
|
+
/**
|
|
259
|
+
* Each scene's cues, scene-relative, from the manifest `build` wrote. Segment
|
|
260
|
+
* cues are relative to their own audio; the segment's `start` is where that
|
|
261
|
+
* audio sits on the composition clock (which is the output clock — nothing is
|
|
262
|
+
* frozen, see `framePlan`).
|
|
263
|
+
*/
|
|
264
|
+
export declare function sceneWindows(timing: Timing, sids?: ReadonlySet<string>): SceneWindow[];
|
|
265
|
+
/**
|
|
266
|
+
* The instants a scene is photographed at: just after each cue starts, 0.6s in,
|
|
267
|
+
* just before it ends, and the settled end. `sparse` keeps the first cue's start,
|
|
268
|
+
* the cue ends and the end — what `seek_order` on a deck's other scenes needs,
|
|
269
|
+
* at about a third of the screenshots.
|
|
270
|
+
*/
|
|
271
|
+
export declare function probeTimes(w: SceneWindow, sparse?: boolean): Array<{
|
|
272
|
+
key: string;
|
|
273
|
+
t: number;
|
|
274
|
+
cue?: number;
|
|
275
|
+
edge?: "a" | "s" | "z";
|
|
276
|
+
}>;
|
|
277
|
+
export interface ProbedFrame {
|
|
278
|
+
sid: string;
|
|
279
|
+
key: string;
|
|
280
|
+
/** Scene-relative seconds. */
|
|
281
|
+
t: number;
|
|
282
|
+
png: Buffer;
|
|
283
|
+
}
|
|
284
|
+
export interface Probe {
|
|
285
|
+
findings: Finding[];
|
|
286
|
+
/** Ascending-pass frames, for a contact sheet. */
|
|
287
|
+
frames: ProbedFrame[];
|
|
288
|
+
cueChanges: CueChange[];
|
|
289
|
+
layout: Layout[];
|
|
290
|
+
seek: SeekDiff[];
|
|
291
|
+
/** The shell's camera every `CAM_STEP` through each illustrated scene (`shot_variety`). */
|
|
292
|
+
cams: CamSample[];
|
|
293
|
+
}
|
|
294
|
+
export interface ProbeOptions {
|
|
295
|
+
/** Which gates. Default all three. `seek` alone is what `verify` runs on plain v2 scenes. */
|
|
296
|
+
gates?: ReadonlyArray<"motion" | "layout" | "seek">;
|
|
297
|
+
/** `seek_order` severity; `verify` passes `warning` for scenes nobody generated. */
|
|
298
|
+
seekSeverity?: Finding["severity"];
|
|
299
|
+
/** Keep the PNGs (the critique round needs them). */
|
|
300
|
+
keepFrames?: boolean;
|
|
301
|
+
/** Also compare a never-seeked page per scene. Default true; a fresh browser each. */
|
|
302
|
+
cold?: boolean;
|
|
303
|
+
/** Photograph cue ends only (see `probeTimes`). */
|
|
304
|
+
sparse?: boolean;
|
|
305
|
+
/** Measure the repair geometry at every graded frame (bespoke scenes). */
|
|
306
|
+
geometry?: boolean;
|
|
307
|
+
/** Opens a fresh page; injected so the graders' wiring is testable. */
|
|
308
|
+
open: () => Promise<DeckPage>;
|
|
309
|
+
/** The array `open` hands to `openDeck({ watch })`; read once the probe ends. */
|
|
310
|
+
errors?: readonly string[];
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Photograph `windows` and grade them. One warm page for the ordered passes, one
|
|
314
|
+
* cold page per scene for the first-seek comparison.
|
|
315
|
+
*/
|
|
316
|
+
export declare function probeScenes(windows: readonly SceneWindow[], opts: ProbeOptions): Promise<Probe>;
|
|
317
|
+
/**
|
|
318
|
+
* The end row's `litDimmed` and `relight`, from every graded frame of one
|
|
319
|
+
* scene: a part lit (>= `LIT`) at some earlier frame and dimmed (< `DIM`, but
|
|
320
|
+
* visible) at the end, and the elements above it that dim it. A part that was
|
|
321
|
+
* never lit is drawn translucent on purpose and is left alone.
|
|
322
|
+
*/
|
|
323
|
+
export declare function endState(rows: Layout[]): void;
|
|
324
|
+
/** `timing.json`, or nothing. */
|
|
325
|
+
export declare function readTimingFile(dir: string): Promise<Timing | undefined>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jokerized/decksmith",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.2",
|
|
4
4
|
"description": "Turn a source document into an animated explanation deck.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -64,6 +64,7 @@
|
|
|
64
64
|
"@fontsource-variable/space-grotesk": "5.3.0",
|
|
65
65
|
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
66
66
|
"@puppeteer/browsers": "^3.0.6",
|
|
67
|
+
"acorn": "^8.19.0",
|
|
67
68
|
"commander": "^14.0.1",
|
|
68
69
|
"fflate": "^0.8.3",
|
|
69
70
|
"gsap": "^3.14.2",
|