@jokerized/decksmith 0.1.4 → 0.3.0
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 +161 -26
- package/dist/cli.js +3864 -1816
- package/dist/ds-morph.js +1 -0
- package/dist/index.js +2701 -780
- package/dist/mcp.js +2509 -633
- package/dist/types/emit/archetypes/annotated-figure.d.ts +3 -2
- package/dist/types/emit/archetypes/data-table.d.ts +0 -19
- package/dist/types/emit/archetypes/equation-morph.d.ts +2 -0
- package/dist/types/emit/archetypes/equation-walk.d.ts +63 -1
- package/dist/types/emit/archetypes/index.d.ts +1 -1
- package/dist/types/emit/archetypes/pipeline.d.ts +2 -2
- package/dist/types/emit/archetypes/stack.d.ts +20 -1
- package/dist/types/emit/archetypes/title.d.ts +31 -14
- package/dist/types/emit/camera.d.ts +25 -0
- package/dist/types/emit/composition.d.ts +20 -2
- package/dist/types/emit/depth.d.ts +95 -0
- package/dist/types/emit/kit.d.ts +153 -0
- package/dist/types/emit/morph-runtime.d.ts +178 -0
- package/dist/types/emit/svg.d.ts +77 -13
- package/dist/types/emit/theme.d.ts +1 -0
- package/dist/types/images/illustrate.d.ts +34 -0
- package/dist/types/images/providers.d.ts +108 -0
- package/dist/types/index.d.ts +27 -8
- package/dist/types/mcp/prereqs.d.ts +18 -0
- package/dist/types/mcp/tools.d.ts +23 -0
- package/dist/types/plan/codex.d.ts +25 -3
- package/dist/types/plan/duration.d.ts +190 -15
- package/dist/types/plan/prompt.d.ts +7 -1
- package/dist/types/plan/refs.d.ts +41 -3
- package/dist/types/prefs.d.ts +21 -6
- package/dist/types/render/capture.d.ts +99 -0
- package/dist/types/render/render.d.ts +18 -0
- package/dist/types/server/options.d.ts +7 -0
- package/dist/types/server/pipeline.d.ts +14 -0
- package/dist/types/server/queue.d.ts +1 -1
- package/dist/types/types.d.ts +294 -4
- package/dist/types/verify/apparent.d.ts +123 -0
- package/dist/types/verify/index.d.ts +114 -6
- package/dist/types/verify/typefloor.d.ts +3 -1
- package/package.json +2 -2
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The type floor, measured on the rendered frame instead of read off the source.
|
|
3
|
+
*
|
|
4
|
+
* `typefloor.ts` scans declared sizes and says so plainly in its own header:
|
|
5
|
+
* "text shrunk by a `scale` below 1 at a hold reads as its unscaled size". That
|
|
6
|
+
* hole is not hypothetical and it is not only about `scale`. Measured in Chrome
|
|
7
|
+
* at `rotateX(30deg)` under `perspective: 1400px`, a run declaring 46px renders
|
|
8
|
+
* 31.4 apparent px near the top of the plane — invariant 5 broken by 8.6px, with
|
|
9
|
+
* `lint`, `check`, `drift` and the declared-size scan all green, because not one
|
|
10
|
+
* of them looks at how big the glyph actually came out.
|
|
11
|
+
*
|
|
12
|
+
* That matters now because Gap 2 wants depth, and every way of tilting a plane
|
|
13
|
+
* shrinks the type on the far half of it. A depth archetype built against a floor
|
|
14
|
+
* that cannot see projection would ship unreadable slides that pass. So the gate
|
|
15
|
+
* comes first and the archetype second.
|
|
16
|
+
*
|
|
17
|
+
* HOW APPARENT SIZE IS MEASURED. For an SVG text node, `getBBox()` is the
|
|
18
|
+
* untransformed box in user units and `getClientRects()` is the painted box in
|
|
19
|
+
* device px, so their ratio is the TOTAL scale between the two — the element's
|
|
20
|
+
* own transform, every ancestor transform, the 3D projection's per-point
|
|
21
|
+
* perspective divide, and the stage zoom, all of it, without having to know which
|
|
22
|
+
* of them applied. Measured against the three cases that matter:
|
|
23
|
+
*
|
|
24
|
+
* flat, declared 46 ratio 1.000 -> 46.0 apparent px
|
|
25
|
+
* tilted 30deg, near plane top 0.682 -> 31.4
|
|
26
|
+
* tilted 30deg, near plane bottom 1.126 -> 51.8 (magnified, not a fault)
|
|
27
|
+
* `scale(0.6)` at a hold 0.600 -> 27.6
|
|
28
|
+
*
|
|
29
|
+
* `getScreenCTM()` was tried first and rejected: it returns 0.673 for BOTH the
|
|
30
|
+
* top and bottom runs above, because it carries the affine part and drops the
|
|
31
|
+
* perspective divide that makes those two differ by 23 apparent px.
|
|
32
|
+
*
|
|
33
|
+
* WHY IT DIVIDES BY THE STAGE. A deck legitimately scales its whole stage —
|
|
34
|
+
* `zoomOf`/`REF_PULL` — and in portrait 40 reference px is SUPPOSED to land at 30
|
|
35
|
+
* canvas px, which `typefloor.ts` calls "the whole argument of REF_PULL and not a
|
|
36
|
+
* violation". Dividing the run's own ratio by the scene's gives apparent size
|
|
37
|
+
* back in REFERENCE px, so this gate agrees with the declared one on every deck
|
|
38
|
+
* that does nothing clever, and only diverges where something shrank one element
|
|
39
|
+
* relative to its own scene.
|
|
40
|
+
*/
|
|
41
|
+
import type { Finding } from "../types.js";
|
|
42
|
+
/** One text run, as the frame actually drew it. */
|
|
43
|
+
export interface ApparentRun {
|
|
44
|
+
/** First 40 characters, for the message. */
|
|
45
|
+
text: string;
|
|
46
|
+
/** `font-size` as declared, in px. */
|
|
47
|
+
declared: number;
|
|
48
|
+
/** Painted height over untransformed height, including the stage's own zoom. */
|
|
49
|
+
ratio: number;
|
|
50
|
+
/** Its own computed opacity, multiplied down the ancestors that carry one. */
|
|
51
|
+
opacity: number;
|
|
52
|
+
}
|
|
53
|
+
/** What `measureApparent` hands back for one instant. */
|
|
54
|
+
export interface ApparentStop {
|
|
55
|
+
sid: string;
|
|
56
|
+
t: number;
|
|
57
|
+
runs: ApparentRun[];
|
|
58
|
+
/** The scene's own painted-over-declared scale, the baseline runs divide by. */
|
|
59
|
+
stage: number;
|
|
60
|
+
/**
|
|
61
|
+
* True at a declared stop, where the frame has arrived and everything drawn is
|
|
62
|
+
* being read. False at a sampled midpoint, where a run may be part-way through
|
|
63
|
+
* its own entrance — see `SETTLED_OPACITY`.
|
|
64
|
+
*/
|
|
65
|
+
settled: boolean;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Serialised into the page: every visible SVG text run's apparent scale.
|
|
69
|
+
*
|
|
70
|
+
* Deliberately the same walk as `collectSvgTextRuns` in overprint.ts — same
|
|
71
|
+
* visibility rules, same "only inside an `<svg>`" restriction — because two gates
|
|
72
|
+
* that disagree about which text exists are two gates that cannot be reconciled
|
|
73
|
+
* when they disagree about a deck.
|
|
74
|
+
*/
|
|
75
|
+
export declare function collectApparent(sid: string): {
|
|
76
|
+
runs: ApparentRun[];
|
|
77
|
+
stage: number;
|
|
78
|
+
};
|
|
79
|
+
/**
|
|
80
|
+
* Apparent size in reference px: what the run declared, times how much the frame
|
|
81
|
+
* shrank or grew it relative to its own scene.
|
|
82
|
+
*/
|
|
83
|
+
export declare function apparentPx(run: ApparentRun, stage: number): number;
|
|
84
|
+
/**
|
|
85
|
+
* How opaque a run must be, at a sampled midpoint, to count as text being read.
|
|
86
|
+
*
|
|
87
|
+
* At a declared stop the frame has arrived and everything drawn counts. Between
|
|
88
|
+
* stops it has not: `annotated-figure` enters its labels from `scale: 0.97`, so
|
|
89
|
+
* a run caught half way through its own entrance is BOTH under the floor and
|
|
90
|
+
* fading in, and failing a build for it would be crying wolf about a frame no
|
|
91
|
+
* one reads. A run at 0.95 opacity is not entering any more; it has arrived.
|
|
92
|
+
*/
|
|
93
|
+
export declare const SETTLED_OPACITY = 0.95;
|
|
94
|
+
/**
|
|
95
|
+
* The times to sample BETWEEN the declared stops.
|
|
96
|
+
*
|
|
97
|
+
* `fidelity` measures at stops because that is where a frame has settled, and
|
|
98
|
+
* the apparent floor inherited that — which left exactly the hole it was built
|
|
99
|
+
* to close, one interval over: text scaled DOWN between two stops is invisible
|
|
100
|
+
* to a gate that only looks at the stops. A camera pulling back, or an exit that
|
|
101
|
+
* shrinks a label, would ship.
|
|
102
|
+
*
|
|
103
|
+
* Midway between consecutive stops of the SAME scene. Not across a scene
|
|
104
|
+
* boundary, where the midpoint lands in a cross-fade between two compositions
|
|
105
|
+
* and belongs to neither. Costs a seek and a DOM read each — no screenshot,
|
|
106
|
+
* which is what makes doubling the sample count affordable.
|
|
107
|
+
*/
|
|
108
|
+
export declare function midpoints(stops: readonly {
|
|
109
|
+
sid: string;
|
|
110
|
+
t: number;
|
|
111
|
+
}[]): {
|
|
112
|
+
sid: string;
|
|
113
|
+
t: number;
|
|
114
|
+
}[];
|
|
115
|
+
/**
|
|
116
|
+
* Runs the frame drew below the floor, whatever shrank them.
|
|
117
|
+
*
|
|
118
|
+
* An ERROR, not a warning, and for the same reason `typefloor` is: text the
|
|
119
|
+
* audience cannot read is not a matter of degree. The tolerance is a tenth of a
|
|
120
|
+
* pixel, which is rasteriser noise rather than a grace margin — a deck that wants
|
|
121
|
+
* 40px declares 40px and measures 40.0, as the flat control does exactly.
|
|
122
|
+
*/
|
|
123
|
+
export declare function gradeApparent(stops: readonly ApparentStop[], floor?: number): Finding[];
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import type { Prefs } from "../prefs.js";
|
|
2
|
+
import { type Beat, type Finding, type Source, type Storyboard, type Verdict } from "../types.js";
|
|
2
3
|
import { type CheckOptions } from "./check.js";
|
|
3
4
|
/**
|
|
4
5
|
* The duration budget reads the built composition, so it needs no argument the
|
|
@@ -52,9 +53,10 @@ export interface VerifyOptions extends CheckOptions {
|
|
|
52
53
|
* the beat-level gates as well. `kept` — the cut `build` emitted — is optional
|
|
53
54
|
* for the same reason, and for a second one: without it the budget gate falls
|
|
54
55
|
* back to the flat threshold, which is the right list only while the budget has
|
|
55
|
-
* cut nothing.
|
|
56
|
+
* cut nothing. `source` is optional on the same terms: it is what makes the
|
|
57
|
+
* unused-figure advisory possible, and a built directory does not carry it.
|
|
56
58
|
*/
|
|
57
|
-
export declare function verify(dir: string, opts?: VerifyOptions, storyboard?: Storyboard, kept?: readonly Beat[]): Promise<Verdict>;
|
|
59
|
+
export declare function verify(dir: string, opts?: VerifyOptions, storyboard?: Storyboard, kept?: readonly Beat[], source?: Source): Promise<Verdict>;
|
|
58
60
|
/**
|
|
59
61
|
* Every mp3 the narration island promises is actually in the deck.
|
|
60
62
|
*
|
|
@@ -144,6 +146,102 @@ export declare function scanHeadlines(storyboard: Storyboard): Finding[];
|
|
|
144
146
|
* into two holds of one beat is a rewrite only the author can perform.
|
|
145
147
|
*/
|
|
146
148
|
export declare function scanRepeatedObject(storyboard: Storyboard): Finding[];
|
|
149
|
+
/**
|
|
150
|
+
* Warn when the source carries a figure the deck never cites.
|
|
151
|
+
*
|
|
152
|
+
* NOTHING ELSE LOOKS IN THIS DIRECTION. `assertRefsResolve` checks the other one
|
|
153
|
+
* — that every id a beat writes exists in the source — so a plan that ignores
|
|
154
|
+
* the figures entirely satisfies it perfectly. Measured on the shipped demo: two
|
|
155
|
+
* of four figures cited, one of the two left out being the paper's own
|
|
156
|
+
* architecture figure, while a beat redrew that architecture as a synthetic
|
|
157
|
+
* pipeline. Every gate was green, and the owner found it by watching the video.
|
|
158
|
+
*
|
|
159
|
+
* A WARNING, NEVER AN ERROR. A source may honestly hold a figure this deck does
|
|
160
|
+
* not need — a decorative header, a figure about related work, a plot the deck
|
|
161
|
+
* replaces with bars of its own. What is wrong is not using none of them; it is
|
|
162
|
+
* not having decided.
|
|
163
|
+
*
|
|
164
|
+
* Cited counts BOTH ways a beat can be accountable to a figure: `evidence`, and
|
|
165
|
+
* the params that actually put it on screen. A beat that shows a figure without
|
|
166
|
+
* citing it in evidence is a different defect, and not this scan's to report.
|
|
167
|
+
*/
|
|
168
|
+
export declare function scanUnusedFigures(storyboard: Storyboard, source: Source): Finding[];
|
|
169
|
+
/**
|
|
170
|
+
* Warn when the plan came back with fewer beats than were asked for.
|
|
171
|
+
*
|
|
172
|
+
* `--slides` IS A FLOOR ON THE REQUEST, NOT ON THE ARTIFACT, and this is the half
|
|
173
|
+
* that MEASURES it. It has no teeth, and none are available — the door is closed
|
|
174
|
+
* three ways, so do not reopen it. Truncating to the count that came back throws
|
|
175
|
+
* away slides the author asked for. Padding to reach the number is what RULE 9
|
|
176
|
+
* forbids, and `scanRepeatedObject` above only fires on the same object drawn
|
|
177
|
+
* twice with the SAME archetype over identical part labels, so padding induced by
|
|
178
|
+
* a gate would mostly be invisible to the one scan that looks for it. Re-asking is
|
|
179
|
+
* a fresh multi-minute `codex exec` whose only possible second instruction is
|
|
180
|
+
* "return more" — a padding request with extra steps — and whose answer could only
|
|
181
|
+
* be preferred over the first by counting it.
|
|
182
|
+
*
|
|
183
|
+
* THE COUNT IS NOT DETERMINISTIC — which is a weaker argument than it first looks,
|
|
184
|
+
* and is written out here so the next reader does not have to re-derive that.
|
|
185
|
+
* Under ONE fixed configuration, the same command over the same source returned
|
|
186
|
+
* 9, 10 and 8 beats on three independent runs (the "rate + prompt floor" row of
|
|
187
|
+
* the table in .planning/HANDOFF-DURATION-CONTROL.md). So the number a gate would
|
|
188
|
+
* test is partly a draw rather than a property of the source. But all three of
|
|
189
|
+
* those are short of twelve, and NO configuration on record has produced both a
|
|
190
|
+
* twelve and a short count: the 12-beat run in the same table is a different
|
|
191
|
+
* prompt and a different character budget. So this is not evidence that a gate
|
|
192
|
+
* would flap between pass and fail on identical input — it would have failed all
|
|
193
|
+
* three of those runs alike. It is a reason to distrust the number, not a fourth
|
|
194
|
+
* reason the door is closed. The three above are that, and they stand on their
|
|
195
|
+
* own.
|
|
196
|
+
*
|
|
197
|
+
* The other half is the prompt's LENGTH block, which now says so in words — and
|
|
198
|
+
* words are not enough here for the reason `scanHeadlines` records two functions
|
|
199
|
+
* up: a real Codex run answered a sharpened RULE 8 by swapping one verb. Four of
|
|
200
|
+
* the last five plans came back short of their target (8, 9, 9 and 10 against 12)
|
|
201
|
+
* against a prompt that already asked for the number. Whether a source "genuinely
|
|
202
|
+
* will not carry twelve points" is a judgement the writer makes about their own
|
|
203
|
+
* output, so it can always be met cosmetically. A COUNT is not a judgement, which
|
|
204
|
+
* is what makes it worth MEASURING here — and measuring is the whole of what this
|
|
205
|
+
* is.
|
|
206
|
+
*
|
|
207
|
+
* THE FLOOR THE OWNER ASKED FOR IS ALREADY HELD, and it is a different floor: he
|
|
208
|
+
* asked that a short duration not be paid for in slides — a 12-slide deck under a
|
|
209
|
+
* minute, "keeping all twelve". That one holds structurally now. `slidesFor` is a
|
|
210
|
+
* DEFAULT that an explicit `--slides` overrides rather than the reverse (see
|
|
211
|
+
* `loadPrefs`), `durationPlan` raises the speaking rate before it says less, and
|
|
212
|
+
* the advisory that used to answer sixty seconds with "use nine slides" no longer
|
|
213
|
+
* fires there at all — under `FF_BEAT_SECONDS` the rate is what moves, and where
|
|
214
|
+
* it still fires it names keeping every slide beside the smaller count. A floor
|
|
215
|
+
* against the PLANNER'S judgement is a different object, and it is the one that
|
|
216
|
+
* cannot be built. What is held in its place is honesty about it: the budget is
|
|
217
|
+
* struck at the count that came back, the gap is priced in the numbers that moved,
|
|
218
|
+
* and `pack` writes `prefs.slides` and the storyboard into the same file — so a
|
|
219
|
+
* caller that really does want "N or nothing" compares those two numbers itself.
|
|
220
|
+
*
|
|
221
|
+
* REPORTED, NEVER REPAIRED, which is the same shape as `cut.dangling`. None of the
|
|
222
|
+
* three repairs above is available, so the deck is built at the count it has —
|
|
223
|
+
* `durationPlan` restrikes the whole budget there, which is what stops the
|
|
224
|
+
* shortfall becoming a video that quietly misses its duration — and the author
|
|
225
|
+
* is told what it cost, at `plan`, before a minute of TTS is spent on it.
|
|
226
|
+
*
|
|
227
|
+
* NAMING THE NUMBERS IS THE WHOLE VALUE, same argument as `INSTEAD` above: "the
|
|
228
|
+
* plan is short" changes nothing, "each surviving slide now runs 7.5s instead of
|
|
229
|
+
* 5.0s and has to carry 102 characters where the prompt budgeted 66" is a fact
|
|
230
|
+
* the author can act on — by adding the missing beat, or by accepting the deck
|
|
231
|
+
* they have.
|
|
232
|
+
*
|
|
233
|
+
* A WARNING, NEVER AN ERROR, and that is a contract rather than an oversight. A
|
|
234
|
+
* source that honestly carries eight points is a real thing, and only the author
|
|
235
|
+
* can tell that from a planner that stopped early. `build` prints every content
|
|
236
|
+
* loss and fails on none of them: a beat the emitter refused (`onBeatError`), a
|
|
237
|
+
* beat the budget cut (`reportCut`), a beat the planner never wrote (this one).
|
|
238
|
+
* A beat written and then thrown away is a bigger loss than one never written, so
|
|
239
|
+
* making the smallest of the three fatal while the larger two stay advisory would
|
|
240
|
+
* be incoherent — promoting this to `severity: "error"` is a decision about all
|
|
241
|
+
* three, not about this scan. test/verify.test.ts asserts the severity so that
|
|
242
|
+
* decision cannot be made by accident.
|
|
243
|
+
*/
|
|
244
|
+
export declare function scanBeatCount(storyboard: Storyboard, prefs: Prefs): Finding[];
|
|
147
245
|
/**
|
|
148
246
|
* How far a name may precede the thing it names before a viewer notices.
|
|
149
247
|
*
|
|
@@ -174,9 +272,19 @@ export declare const LEAD_SECONDS = 1;
|
|
|
174
272
|
* CONSERVATIVE BY CONSTRUCTION, in three ways, because a warning that cries wolf
|
|
175
273
|
* is a warning people learn to scroll past:
|
|
176
274
|
* - a part is assumed to appear at the EARLIEST hold that could be its own,
|
|
177
|
-
* `holds[
|
|
178
|
-
*
|
|
179
|
-
*
|
|
275
|
+
* `holds[j]`. Where an archetype spends its first hold on a landing rather
|
|
276
|
+
* than a part, the real appearance is later than this and the finding is
|
|
277
|
+
* missed rather than invented.
|
|
278
|
+
*
|
|
279
|
+
* THIS PARAGRAPH USED TO SAY `holds[min(j, last)]`, AND IT WAS WRONG IN THE
|
|
280
|
+
* ONE DIRECTION A DOCSTRING MUST NOT BE WRONG IN. Clamping to the last hold
|
|
281
|
+
* does not under-report, it OVER-reports: an archetype that draws five bars
|
|
282
|
+
* on two holds charges bars three through five to the final hold, so a word
|
|
283
|
+
* spoken over a bar that is already on screen is read as a word spoken 1.05
|
|
284
|
+
* to 2.25s early, against a 1.0s threshold. 86 of `bar-compare`'s 90 flagged
|
|
285
|
+
* parts were that arithmetic and not a defect — 43% of every flagged part in
|
|
286
|
+
* the committed corpus. The promise of under-reporting is exactly why nobody
|
|
287
|
+
* checked. The scene is now skipped instead of clamped, see below.
|
|
180
288
|
* - only labels the narration actually names are considered, by the same
|
|
181
289
|
* five-character prefix match `scanHeadlines` uses — tuned on exactly this
|
|
182
290
|
* problem, where the headline said "encoding" and the stage was `Encoder`.
|
|
@@ -28,7 +28,9 @@
|
|
|
28
28
|
*
|
|
29
29
|
* WHAT IT DOES NOT SEE, stated because a gate that overclaims is the thing this
|
|
30
30
|
* file was written to stop. It reads declared sizes, so text shrunk by a
|
|
31
|
-
* `scale` below 1 at a hold reads as its unscaled size
|
|
31
|
+
* `scale` below 1 at a hold reads as its unscaled size — `apparent.ts` is the
|
|
32
|
+
* gate that closes exactly that, by measuring the painted glyph instead, and it
|
|
33
|
+
* catches a 3D projection the same way; and it says nothing about
|
|
32
34
|
* whether the text FITS — `container_overflow` and `text_occluded` in
|
|
33
35
|
* `hyperframes check` are the gates for that, and `check.ts` already grades them
|
|
34
36
|
* up to errors.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jokerized/decksmith",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Turn a source document into an animated explanation deck.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -63,7 +63,7 @@
|
|
|
63
63
|
"commander": "^14.0.1",
|
|
64
64
|
"fflate": "^0.8.3",
|
|
65
65
|
"gsap": "^3.14.2",
|
|
66
|
-
"hyperframes": "0.
|
|
66
|
+
"hyperframes": "0.8.27",
|
|
67
67
|
"katex": "^0.16.11",
|
|
68
68
|
"puppeteer-core": "^25.3.0",
|
|
69
69
|
"remark-gfm": "^4.0.1",
|