@jokerized/decksmith 0.1.3 → 0.2.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.
@@ -1,4 +1,5 @@
1
- import { type Beat, type Finding, type Storyboard, type Verdict } from "../types.js";
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[min(j, last)]`. Where an archetype spends its first hold on a
178
- * landing rather than a part, the real appearance is later than this and the
179
- * finding is missed rather than invented.
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`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jokerized/decksmith",
3
- "version": "0.1.3",
3
+ "version": "0.2.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.7.71",
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",