@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.
- package/README.md +449 -90
- package/dist/animate/LICENSE +21 -0
- package/dist/animate/NOTICE +32 -0
- package/dist/animate/core.js +530 -0
- package/dist/animate/cut-paper.js +206 -0
- package/dist/animate/morph.js +131 -0
- package/dist/cli.js +13464 -8876
- package/dist/ds-animate.js +1 -0
- package/dist/index.js +13245 -8657
- package/dist/mcp.js +9778 -7477
- package/dist/types/bespoke/art.d.ts +172 -6
- package/dist/types/bespoke/cache.d.ts +10 -0
- package/dist/types/bespoke/cards.d.ts +66 -0
- package/dist/types/bespoke/contract.d.ts +0 -8
- package/dist/types/bespoke/databuild.d.ts +50 -0
- package/dist/types/bespoke/depth.d.ts +127 -0
- package/dist/types/bespoke/grammar.d.ts +73 -0
- package/dist/types/bespoke/inspect.d.ts +40 -1
- package/dist/types/bespoke/pipeline.d.ts +195 -11
- package/dist/types/bespoke/probe.d.ts +2 -2
- package/dist/types/bespoke/prompt.d.ts +110 -34
- package/dist/types/bespoke/references.d.ts +13 -14
- package/dist/types/bespoke/repair.d.ts +23 -29
- package/dist/types/bespoke/scene.d.ts +55 -10
- package/dist/types/bespoke/select.d.ts +28 -21
- package/dist/types/bespoke/sheet.d.ts +66 -1
- package/dist/types/bespoke/shots.d.ts +225 -22
- package/dist/types/build/files.d.ts +7 -0
- package/dist/types/build/piece.d.ts +8 -0
- package/dist/types/emit/animate-runtime.d.ts +149 -0
- package/dist/types/emit/archetypes/claim-figure.d.ts +84 -1
- package/dist/types/emit/archetypes/equation-walk.d.ts +2 -2
- package/dist/types/emit/archetypes/hero-number.d.ts +21 -0
- package/dist/types/emit/archetypes/index.d.ts +2 -2
- package/dist/types/emit/archetypes/kinetic.d.ts +2 -0
- package/dist/types/emit/archetypes/pipeline.d.ts +14 -2
- package/dist/types/emit/archetypes/stage.d.ts +57 -0
- package/dist/types/emit/backdrop.d.ts +114 -0
- package/dist/types/emit/kit.d.ts +8 -0
- package/dist/types/emit/look.d.ts +14 -0
- package/dist/types/emit/motion.d.ts +78 -46
- package/dist/types/emit/type.d.ts +48 -5
- package/dist/types/images/providers.d.ts +6 -0
- package/dist/types/plan/codex.d.ts +4 -2
- package/dist/types/plan/direct.d.ts +1 -1
- package/dist/types/plan/duration.d.ts +1 -1
- package/dist/types/plan/prompt.d.ts +1 -1
- package/dist/types/plan/refs.d.ts +2 -2
- package/dist/types/plan/variety.d.ts +74 -0
- package/dist/types/prefs.d.ts +20 -1
- package/dist/types/render/captions.d.ts +4 -5
- package/dist/types/render/capture.d.ts +7 -0
- package/dist/types/source/fonts.d.ts +22 -0
- package/dist/types/types.d.ts +680 -4
- package/dist/types/verify/drift.d.ts +5 -0
- package/dist/types/verify/fidelity.d.ts +15 -1
- package/dist/types/verify/index.d.ts +27 -4
- package/dist/types/verify/scenes.d.ts +185 -5
- package/package.json +1 -1
- package/dist/types/bespoke/callouts.d.ts +0 -76
package/README.md
CHANGED
|
@@ -110,7 +110,9 @@ decksmith unpack talk.deck -o reopened/
|
|
|
110
110
|
from the viewer. `--subtitles burn` makes it part of the picture, and it is only
|
|
111
111
|
accepted over a deck built with `--reserve-captions` — the layout is fixed at build
|
|
112
112
|
time, so a deck that made no room cannot be given any an hour later, and the band would
|
|
113
|
-
land on the slide's own text. `render` refuses that rather than shipping it.
|
|
113
|
+
land on the slide's own text. `render` refuses that rather than shipping it. A burned
|
|
114
|
+
caption is a quiet lower third: 40px white type with a close dark halo, no box
|
|
115
|
+
(2026-10-10; it was 4% of the width — 71px at 1920 — on a 70% black bar).
|
|
114
116
|
- **drift** — renders the deck twice and compares every frame. `--identical` fails on any
|
|
115
117
|
differing byte, which is only honest for an image-free deck with no camera — the demo
|
|
116
118
|
deck is not one, and fails it on both hyperframes pins (see
|
|
@@ -281,7 +283,7 @@ without driving scene timelines or clip visibility. See
|
|
|
281
283
|
### What the gates do not check
|
|
282
284
|
|
|
283
285
|
The failure this project keeps producing is a **green gate over wrong output**, and there
|
|
284
|
-
are now
|
|
286
|
+
are now eleven documented cases. Nearly every one was caught by a human looking at the
|
|
285
287
|
artifact. Three are worth reading as patterns rather than bugs:
|
|
286
288
|
|
|
287
289
|
- A camera move that the video renderer replaced with a still, for exactly the right
|
|
@@ -840,7 +842,7 @@ invalidates a storyboard you have already edited.
|
|
|
840
842
|
| `images.provider` | `codex` | illustrate | `auto` · `codex` · `svg` — where the chain of rungs starts |
|
|
841
843
|
| `images.model` | unset | illustrate | model for the separate backend only; the Codex rung uses the account's own |
|
|
842
844
|
| `images.style` | `flat vector illustration` | illustrate | one phrase folded into every picture prompt |
|
|
843
|
-
| `images.max` | `
|
|
845
|
+
| `images.max` | `10` | illustrate | most pictures drawn through the chain; the rest are the tool's SVG |
|
|
844
846
|
|
|
845
847
|
Three layers, in increasing precedence: these defaults, then the nearest
|
|
846
848
|
`decksmith.config.json` found by walking up from the working directory, then flags.
|
|
@@ -905,17 +907,31 @@ byte for byte. See `.planning/2026-10-07-v2-layout-director.md`.
|
|
|
905
907
|
eyebrow and headline, every seam the same 0.4s dissolve, nothing moving once a slide has
|
|
906
908
|
built — with a grammar planned per deck (`src/emit/motion.ts`):
|
|
907
909
|
|
|
908
|
-
- **Entrances.**
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
910
|
+
- **Entrances.** One verb, `fade`: a part's opacity reveal at its own time and length,
|
|
911
|
+
with no travel, scale, clip or blur. There were six (`rise`, `slide`, `snap`, `focus`,
|
|
912
|
+
`wipe`, `mask`), and the founder's verdict on plates, chips, cards and labels sliding
|
|
913
|
+
and popping in was "graphic animation by animated UI elements is old-fashioned"
|
|
914
|
+
(2026-10-10). On a fallback archetype the motion is the picture's — the backdrop's
|
|
915
|
+
drift, the field, the camera — and the seams'; no panel or slab is lifted as it is
|
|
916
|
+
read, and split-compare's divider no longer sweeps a highlight.
|
|
917
|
+
- **Seams.** `dissolve`, `push`, `lift`, `zoom`, picked from how two neighbouring
|
|
912
918
|
beats relate (same family → push, a role boundary → zoom, a title → lift, into the close
|
|
913
919
|
→ dissolve), never repeated back to back, and at least three kinds in a deck of ten or
|
|
914
|
-
more beats. A beat `inside` the one before keeps the camera dive.
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
920
|
+
more beats. A beat `inside` the one before keeps the camera dive. Every seam fades the
|
|
921
|
+
outgoing eyebrow and headline out in the first 0.16s and starts the incoming ones at
|
|
922
|
+
0.3s — a bespoke scene's chrome included — so two headlines never share a frame. The
|
|
923
|
+
`wipe` (a clip sweeping the slide) was cut on 2026-10-10: at 4fps it left half a picture
|
|
924
|
+
and a lone label on an empty slide.
|
|
925
|
+
- **Emphasis while the narrator talks.** The part a sentence is about glows (light only;
|
|
926
|
+
the scale `pulse` and the `underline` went with the entrances), starting on a cue
|
|
927
|
+
boundary of that sentence inside the quiet stretch after its stop, and is back at rest
|
|
928
|
+
before the next reveal — so every frame a gate captures at a stop is unchanged.
|
|
929
|
+
|
|
930
|
+
**Type** (`TYPE_SCALE` in `src/emit/type.ts`, the founder's scale of 2026-10-10): headline
|
|
931
|
+
and title slide 56px, kicker and burned captions 40px, body, labels and notes 40-44px, a
|
|
932
|
+
display equation asked at 64px at most (its scripts then land at 44.8px), nothing under
|
|
933
|
+
40px. Every v2 pack sets it; archetypes no longer grow their type into the region (their
|
|
934
|
+
bars, boxes and figures still grow). `--design classic` keeps its own sizes.
|
|
919
935
|
|
|
920
936
|
All of it is `fromTo` tweens with no callbacks, seeded rather than random, and keeps every
|
|
921
937
|
hold, every scene window and `timing.json` exactly as `classic` writes them. In `deck.html`
|
|
@@ -924,32 +940,77 @@ already built) and, while a stop's audio plays, seeks the scene through that sto
|
|
|
924
940
|
stretch on the audio clock so the emphasis lands on the same word as in the video. Both
|
|
925
941
|
are off under `prefers-reduced-motion`.
|
|
926
942
|
|
|
927
|
-
## v2 bespoke scenes (`--bespoke`)
|
|
943
|
+
## v2 bespoke scenes (on by default under v2; `--no-bespoke`)
|
|
928
944
|
|
|
929
|
-
The archetypes
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
945
|
+
The archetypes are the same handful of layouts on every slide, and they finish building in
|
|
946
|
+
about five seconds and then hold still while the voice keeps talking. A narrated
|
|
947
|
+
`build --design v2` hands EVERY beat to Codex instead — title, numbers, comparisons and
|
|
948
|
+
closing claims as well as mechanisms: for each, a GSAP + SVG scene written for that beat,
|
|
949
|
+
keyed to its narration cues, built on its own visual device. The archetype is only the
|
|
950
|
+
fallback for a scene that fails its gates. `--no-bespoke` (or `"bespoke": {"enabled":
|
|
951
|
+
false}`) keeps every archetype and makes no call; classic never runs it and emits the bytes
|
|
952
|
+
it did. A v2 build without narration has no cues to key a scene to, and says so.
|
|
934
953
|
|
|
935
954
|
```bash
|
|
936
|
-
decksmith build storyboard.json --source source.json -o deck --design v2
|
|
937
|
-
# bespoke:
|
|
955
|
+
decksmith build storyboard.json --source source.json -o deck --design v2
|
|
956
|
+
# bespoke: devices (codex) — b01 particle-assembly, b02 fog-lift+art, b03 edge-sweep, …
|
|
938
957
|
# bespoke: b05-backbone — every gate passed and the rubric probe is clean; no critique call
|
|
939
|
-
# bespoke:
|
|
958
|
+
# bespoke: b07 (bar-compare) FALLBACK · device draining-light-bars · motion graphics — failed the gates after the critique round: error card_row: …
|
|
959
|
+
# bespoke: 12 of 14 beats drawn bespoke (86%; 2 fell back, 0 not eligible), 19 scene call(s) + 1 device call + 6 illustration(s), …
|
|
940
960
|
```
|
|
941
961
|
|
|
942
|
-
**
|
|
943
|
-
(
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
962
|
+
**Round 6: the picture moves, not UI over it.** After the founder's verdict on round 5
|
|
963
|
+
("graphic animation by animated UI elements is old-fashioned, and the fonts are too
|
|
964
|
+
large"), every bespoke scene with a picture is a FULL-FRAME SHOT: the picture covers the
|
|
965
|
+
frame behind the deck's own headline (which keeps its place and entrance, over a soft
|
|
966
|
+
scrim), and its motion is the picture's own — a camera moving through its depth, focus
|
|
967
|
+
pulled to the subject the voice names, a slow light sweep, the subjects breathing. No
|
|
968
|
+
plates, chips, label pills or leader-line callouts animate in, and nothing a scene writes
|
|
969
|
+
renders above the 56px headline (`TYPE_SCALE` in `src/emit/type.ts`); the narration and the
|
|
970
|
+
subtitles carry the words. Every beat with two cues gets a picture, data beats included
|
|
971
|
+
(their subjects ARE the quantities, drawn in proportion), up to `--bespoke-art` — and the cap
|
|
972
|
+
goes to data beats first: a drawn chart is where the gates refuse most (all five fallbacks of
|
|
973
|
+
the rebased branch's two smoke builds were drawn data beats, whose archetype grows its bars),
|
|
974
|
+
while every pictured scene passed on its first draft.
|
|
975
|
+
|
|
976
|
+
**Which beats** (`src/bespoke/select.ts`): every beat with a narration cue, except one a
|
|
977
|
+
camera dives into or out of (the dive is aimed at a part the archetype drew) and one the
|
|
978
|
+
planner marked `bespoke: false` (`plan --bespoke`) that cites a figure or table of the
|
|
979
|
+
paper. A `false` on a beat citing neither is not kept: it was how r3's b12 stayed six grey
|
|
980
|
+
row plates for 16 seconds. When `--bespoke-calls` cannot pay two
|
|
981
|
+
calls a beat, the most mechanical win (mechanism archetypes and words in en/ko/ja/zh, a cited
|
|
982
|
+
equation, weight; ties by a hash) and the rest keep their archetype.
|
|
983
|
+
|
|
984
|
+
**The device pass** (`assignDevices`, `src/bespoke/pipeline.ts`), once per deck before any
|
|
985
|
+
picture or scene is asked for: one Codex call names, for every beat from its content, the
|
|
986
|
+
visual device its scene is built on (`spike-train`, `fog-lift`, `edge-sweep`,
|
|
987
|
+
`draining-light-bars`, `track-race` …) — never a layout, and never type or a UI element
|
|
988
|
+
(`kinetic-title`, `word-cascade`, `fill-gauges`, anything named for a card, chip, label,
|
|
989
|
+
plate, tile, badge, gauge or stamp: `isUiDevice`). The founder's rule of 2026-10-10 is in
|
|
990
|
+
the prompt: the motion is the picture's (camera, parallax, light, particles, things in it
|
|
991
|
+
moving), never labels or boxes sliding or popping in, and no giant lettering. A cached
|
|
992
|
+
answer that breaks it is asked again for that beat alone — and which beats get an
|
|
993
|
+
illustration (never a data beat or a one-cue beat, at most `--bespoke-art`; the rest are pure
|
|
994
|
+
motion graphics, so the deck varies). A name the model repeats, garbles or leaves out, and
|
|
995
|
+
every name when there is no call, comes from a rule catalogue; no two beats of a deck share
|
|
996
|
+
one. With each name the pass writes an `idea` — how the scene is composed and moves — and is
|
|
997
|
+
told to vary compositions, not only names (four comparison beats drawn as "two big numbers
|
|
998
|
+
over a shape" was the failure), and to keep metaphors truthful (the heavier side of a balance
|
|
999
|
+
sinks; a length that stands for a number starts from zero). Each scene prompt gets its own
|
|
1000
|
+
device and idea only, and is told a row of cards, boxes and arrows, bullet columns or a tile
|
|
1001
|
+
grid is not a main visual. The answer is cached by the whole deck's words AND per beat: a
|
|
1002
|
+
rerun after one beat changed (or `--bespoke-calls` picked a different set) keeps every
|
|
1003
|
+
unchanged beat's device and idea — the call is told they are decided — so the scenes cached
|
|
1004
|
+
under them still hit; only the changed beat is drawn again.
|
|
1005
|
+
|
|
1006
|
+
**Per beat:** device → illustration (or none) → draft → static contract → probe deck (a real build with every
|
|
950
1007
|
`verify` gate plus the motion gates, photographed at every cue) → the rubric probe → at most
|
|
951
1008
|
one critique-and-fix call with the contact sheet and the measures attached (`codex exec -i`)
|
|
952
|
-
→ static contract → probe again → repair rounds
|
|
1009
|
+
→ static contract → probe again → repair rounds → for a beat that would otherwise fall back
|
|
1010
|
+
(its critique's scene fails and its draft did not pass), ONE second critique with that
|
|
1011
|
+
scene's own frames and findings, if the call cap and 420s of the clock allow, then its gates
|
|
1012
|
+
and repairs. r1 (2026-10-10) fell back on its three core mechanism beats, 24% of the video,
|
|
1013
|
+
each on a collision or two its critique had moved rather than removed. A draft that passed every gate and is clean
|
|
953
1014
|
by the rubric probe is kept without the critique call; one that fails only on what the
|
|
954
1015
|
repair fixes, and is otherwise clean, is repaired instead of critiqued. A beat that does not
|
|
955
1016
|
pass keeps its archetype — the deck is never failed. If the fix round breaks a draft that
|
|
@@ -981,33 +1042,99 @@ boxes are known in box px before the scene is written. It is read only from
|
|
|
981
1042
|
12 MB. `--bespoke-art <n>` (default 6; `bespoke.art`) caps pictures per deck, counted apart
|
|
982
1043
|
from `--bespoke-calls`; a beat whose picture cannot be had is drawn without one.
|
|
983
1044
|
|
|
984
|
-
|
|
985
|
-
|
|
1045
|
+
Since round 5 a picture is TWO LAYERS from one art call: a backdrop of the setting (an
|
|
1046
|
+
environment with depth, no subjects, quiet in the top fifth), then the subjects drawn with
|
|
1047
|
+
the image tool's `transparent_background` and the backdrop as a reference image, so they
|
|
1048
|
+
stand in it. The subjects are inspected laid on the pack's ground; the backdrop is read for
|
|
1049
|
+
writing too. Round 5's shell drew the backdrop behind the camera's wrapper and moved it at half
|
|
1050
|
+
the camera's zoom; round 6 cuts both pictures into depth planes instead (below). The deck's pictures are planned together in the up-front
|
|
1051
|
+
device pass — a different setting and different subjects per illustrated beat — and stock
|
|
1052
|
+
stand-ins (robots, mascots, brains, light bulbs, gears, screens) are forbidden unless the
|
|
1053
|
+
beat names one. A picture that draws one, shares a subject noun with another picture of the
|
|
1054
|
+
deck, or sits closer than `SIMILAR_MAX` to one by Vision's feature print is redrawn once,
|
|
1055
|
+
told what the deck already shows; `bespoke.json` reports the closest pair (`repetition`).
|
|
1056
|
+
|
|
1057
|
+
**Depth planes** (round 6, `src/bespoke/depth.ts`, `src/bespoke/sheet.ts`). The kept
|
|
1058
|
+
picture is cut into a multiplane image (Zhou et al. 2018; Tucker & Snavely 2020): the
|
|
1059
|
+
backdrop's depth is estimated by Depth Anything V2 Small (Apple's Core ML build, F16, run on
|
|
1060
|
+
the CPU by a Swift helper compiled once; 1.5s and 156 MB a picture, measured), the backdrop
|
|
1061
|
+
is sliced at its depth quantiles into up to three planes (none closer in depth than 0.12),
|
|
1062
|
+
what a nearer plane uncovers on the one behind is filled by push-pull at quarter
|
|
1063
|
+
resolution, and each subject is cut onto its own plane at the depth where its foot meets
|
|
1064
|
+
the backdrop's floor, held in front of the farthest band. Each plane is given a distance
|
|
1065
|
+
(the subjects' plane is 1; others 0.8-3). The shell moves every plane EXACTLY as a pinhole
|
|
1066
|
+
camera sees it at that distance (`planeFrame`: for fronto-parallel planes a homography is
|
|
1067
|
+
a scale and an offset, so it is a CSS transform per plane), enlarged just enough to keep
|
|
1068
|
+
covering the frame (`overscan`), so a push-in or a truck shows real parallax. Focus is
|
|
1069
|
+
racked by cross-fading each plane with a blurred twin (`softAt`; a CSS `filter: blur()`
|
|
1070
|
+
tween cost ~1.5s a frame in the gates' browser). Before slicing, both pictures are
|
|
1071
|
+
upscaled 2x by Real-ESRGAN's `realesr-animevideov3` model through its ncnn/Vulkan build
|
|
1072
|
+
(1.6s and 129 MB a picture, measured), so a push-in stays sharp: the cap `EFF_MIN` is now
|
|
1073
|
+
0.85 picture px per output px. BOTH MODELS ARE OPTIONAL and never in git:
|
|
1074
|
+
`DECKSMITH_DEPTH_MODEL` names the `.mlpackage` (else
|
|
1075
|
+
`~/.cache/decksmith/models/DepthAnythingV2SmallF16.mlpackage`) and `DECKSMITH_UPSCALER`
|
|
1076
|
+
the `realesrgan-ncnn-vulkan` binary with a `models/` folder beside it (else
|
|
1077
|
+
`~/.cache/decksmith/tools/realesrgan/`). Without them — off macOS, on CI — the backdrop is
|
|
1078
|
+
one plane at distance 2 and the subjects stay at 1 (round 5's parallax, in the new layout),
|
|
1079
|
+
and `bespoke.json` says which and why (`art.depth.info`). A wipe in depth LIGHTS the
|
|
1080
|
+
subjects in turn (they stand dim from the first frame) instead of clipping the picture.
|
|
1081
|
+
A picture whose subjects merged into one is staged as a zoom-out, not a follow, rack or
|
|
1082
|
+
cutaway.
|
|
1083
|
+
|
|
1084
|
+
**Data beats** without a picture (line-chart, bar-compare, data-table) get their scene asked
|
|
1085
|
+
for the chart that builds with the voice — axes draw, the marks come on at their size, the
|
|
986
1086
|
line traces, the named value lights — from the beat's own numbers, never a table painted
|
|
987
|
-
over an illustration (`data_over_picture`).
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
1087
|
+
over an illustration (`data_over_picture`). Since round 5 each data beat of a deck builds
|
|
1088
|
+
differently (`src/bespoke/databuild.ts`): a line traced with a callout, the delta
|
|
1089
|
+
highlighted and counted, or small multiples lit in turn (round 6 dropped the bar race: bars
|
|
1090
|
+
growing from zero and sliding to their rank are what `ui_motion` refuses) — declared on the
|
|
1091
|
+
chart (`data-build`) and checked statically with its marks; the camera may push in on the
|
|
1092
|
+
value the voice names (required until round 6, when a smoke deck lost a data beat to that rule
|
|
1093
|
+
alone). `verify` refuses two data scenes of a deck that build alike
|
|
1094
|
+
(`build_repeat`).
|
|
1095
|
+
|
|
1096
|
+
**The camera** of an illustrated scene is the shell's (`src/bespoke/shots.ts`), in one of
|
|
1097
|
+
seven GRAMMARS (`src/bespoke/grammar.ts`) picked per beat in the device pass from the
|
|
1098
|
+
narration's rhetorical role (compare, cause, process, reveal, quantify, define — read from
|
|
1099
|
+
the archetype and the narration's own words in en/ko/zh/ja), never the grammar of the
|
|
1100
|
+
illustrated beat before it: `tour` (establish, push in on each named subject, reveal),
|
|
1101
|
+
`follow` (push in, then track along the subjects at one scale), `rack` (A, whip to B, back to
|
|
1102
|
+
A, a two-shot), `zoom-out` (open close on the detail, pull back to the whole), `wipe` (the
|
|
1103
|
+
picture wiped on subject by subject, the camera wide), `cutaway` (hard cuts to close inserts
|
|
1104
|
+
and back) and `parallax` (a slow lateral truck at a medium scale). The scene names its
|
|
1105
|
+
`shots` — on which cue, how far into it, which subject — and the shell compiles them in the
|
|
1106
|
+
grammar; shots closer than 1.6s are dropped, and the end frame is always the whole picture.
|
|
1107
|
+
No push-in goes past the scale where a picture pixel spans more than 1/`EFF_MIN` output
|
|
1108
|
+
pixels (`sharpMax`): the image tool draws ~1.57 megapixels whatever it is asked.
|
|
1109
|
+
`shot_variety` reads the grammar back off the camera's samples (a follow must track, a rack
|
|
1110
|
+
come back or hold a two-shot, a cutaway cut, a zoom-out only pull back, a wipe wipe, a truck
|
|
1111
|
+
travel), and that the backdrop moves less than the subjects; `verify` refuses two
|
|
1112
|
+
consecutive illustrated scenes in one grammar (`grammar_repeat`). Every
|
|
995
1113
|
camera tween is a `fromTo` with explicit from-values, and no two touch. The scene's own
|
|
996
1114
|
script may not move `#sN-cam` (`script_camera`). A scene without a picture still moves the
|
|
997
|
-
wrapper itself (`scale`/`x`/`y`, arithmetic in the prompt)
|
|
1115
|
+
wrapper itself (`scale`/`x`/`y`, arithmetic in the prompt), and since round 6 its words keep
|
|
1116
|
+
their declared size under it: for every camera tween (the wrapper's `scale`, or the svg's
|
|
1117
|
+
`viewBox`) the shell adds the inverse scale on each word about its own centre over the same
|
|
1118
|
+
span (`quietWords` in `src/bespoke/scene.ts`; r1's final review measured labels at 75-80px
|
|
1119
|
+
under zoom). A scene with a moving camera is
|
|
998
1120
|
clipped to its box and marked `data-ds-clip`, so `verify` does not count what a push-in
|
|
999
1121
|
carries past the canvas edge as off-canvas.
|
|
1000
1122
|
|
|
1001
|
-
**The labels**
|
|
1123
|
+
**The labels** (rounds 4-5; gone in round 6). An illustrated scene's names were the shell's (`src/bespoke/callouts.ts`, deleted). The
|
|
1002
1124
|
scene says what each subject is called (`labels`); the shell gives each subject a zone just
|
|
1003
1125
|
above it (or across its top when there is no room), as wide as half the gap to each
|
|
1004
|
-
neighbour, and sets the name there on a plate —
|
|
1126
|
+
neighbour, and sets the name there on a plate — 64px, shrinking to 52, then two lines —
|
|
1005
1127
|
with a leader line to a dot on the subject, entering as the camera first arrives on it (or
|
|
1006
|
-
at the reveal).
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1128
|
+
at the reveal). Since round 5 the top 120px of the box is the CAPTION BAND, where the scene
|
|
1129
|
+
draws its own words; a label goes above its subject only under the band, else below it,
|
|
1130
|
+
else across its top; and a label is held at 1/sqrt(s) of the camera's zoom, so on screen it
|
|
1131
|
+
grows by sqrt(s) in a push-in and is its own size in the whole view. `label_size` holds
|
|
1132
|
+
every label to 52px as rendered at every graded frame; `label_band` refuses a label in the
|
|
1133
|
+
band or the scene's own words within 24px of one. Round 6 removed them: plates
|
|
1134
|
+
and leader lines entering as the camera arrived were exactly the "animated UI elements"
|
|
1135
|
+
the founder called old-fashioned, and every name was one the narration already says. A
|
|
1136
|
+
round-6 scene's own layer is FIXED to the frame (`#sid-fx`), holds at most two short
|
|
1137
|
+
phrases or one number, 40-56px, fading in place, and nothing aimed at a subject.
|
|
1011
1138
|
|
|
1012
1139
|
**Repair** (`src/bespoke/repair.ts`), no model call. On `text_overlap`,
|
|
1013
1140
|
`graphic_crosses_text` or `svg_text_overprint`, each colliding label's unit (the text, or
|
|
@@ -1017,6 +1144,8 @@ every visible stroke sample by 10px and every shape painted over it, inside the
|
|
|
1017
1144
|
every frame the probe measured, camera scale included; it is written as a wrapping
|
|
1018
1145
|
`<g transform="translate()">`. A label never moves farther than 0.8 of its own height
|
|
1019
1146
|
(at least 32px): a collision that needs more is left to the critique round. On
|
|
1147
|
+
`label_band` the same nudge keeps the scene's own words 26px (camera-free) clear of the
|
|
1148
|
+
shell's labels, which never move. On
|
|
1020
1149
|
`end_dimmed`, the elements dimming parts the scene had lit are tweened back to full
|
|
1021
1150
|
strength just before the end; on `camera_end`, the camera is tweened home. On `seek_order`,
|
|
1022
1151
|
two tweens on one target animating one property over overlapping time — which a timeline
|
|
@@ -1026,15 +1155,18 @@ property when both start together; `src/bespoke/untangle.ts`). A repaired scene
|
|
|
1026
1155
|
through the static contract and is gated again (at most two repair rounds).
|
|
1027
1156
|
|
|
1028
1157
|
**What the model is shown** (`src/bespoke/prompt.ts`): the beat, its cues, the paper's
|
|
1029
|
-
excerpts (fenced, untrusted), the contract, motion-design rules with numbers (
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1158
|
+
excerpts (fenced, untrusted), the contract, motion-design rules with numbers (quiet type:
|
|
1159
|
+
words 40-44px, one key number or word up to 56px, nothing bigger; NO UI MOTION — no word,
|
|
1160
|
+
plate, chip, card or bar moved, scaled or grown into place; one focus per cue; fill the
|
|
1161
|
+
box; at least three kinds of motion, one of flow, camera, counter or morph; the end frame a
|
|
1162
|
+
summary — for a scene on a depth picture, a shorter direction: the shot list is the scene), text widths measured in the pack's own font, and two of four hand-made
|
|
1033
1163
|
reference scenes (`src/bespoke/references.ts`: a routing mechanism with particle flow and
|
|
1034
1164
|
a counter, a camera zoom into one block, a traced curve whose gap morphs into the headline
|
|
1035
|
-
number, a scatter whose dots travel into a bell
|
|
1036
|
-
|
|
1037
|
-
in the pack's colours
|
|
1165
|
+
number, a scatter whose dots travel into a bell — round 6 rewrote every one of them to
|
|
1166
|
+
the quiet type scale with nothing sliding, popping or growing into place), picked for the
|
|
1167
|
+
beat's archetype and painted in the pack's colours; a scene on a depth picture is shown
|
|
1168
|
+
none (round 4's "illustrated" reference, labels and a spotlight over the picture, is
|
|
1169
|
+
deleted). `test/bespoke-references.test.ts` runs each reference, and a depth-staged scene,
|
|
1038
1170
|
through every gate and the rubric probe in a real deck, so a reference that stops passing
|
|
1039
1171
|
stops being one. Parts a cue introduces are `<g data-cue="N">` groups (semantic grouping,
|
|
1040
1172
|
after Vector Prism, arXiv 2512.14336). MorphSVG is registered for a deck only when one of
|
|
@@ -1043,19 +1175,21 @@ its scenes morphs.
|
|
|
1043
1175
|
**The rubric probe** (`rubricProbe`, free: it reads what the probe measured) sends a
|
|
1044
1176
|
passing draft to the critique round when any of these is off: the drawing paints under 12%
|
|
1045
1177
|
of its box at the end (round 1's scenes painted 4-16%), more than half its parts are still
|
|
1046
|
-
dimmed at the end, no label reaches
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
0.5% of the frame, or a warning about the scene. The critique round scores the frames against a six-line rubric
|
|
1051
|
-
(fills the stage, one focus per cue, motion explains,
|
|
1178
|
+
dimmed at the end, no label reaches 44px (not asked of an illustrated scene), a label declares over 56px, a word rendered over the 56px headline, any UI animated into place,
|
|
1179
|
+
fewer than three kinds of motion or none of flow/camera/counter/morph (not asked of a scene
|
|
1180
|
+
on a picture: the picture's camera, focus and light are its motion), an illustrated scene
|
|
1181
|
+
that holds fewer than two push-ins or does not open wide, a cue whose picture changes by
|
|
1182
|
+
under 0.5% of the frame, or a warning about the scene. The critique round scores the frames against a six-line rubric
|
|
1183
|
+
(fills the stage, one focus per cue, motion explains with no UI motion, quiet type, the pack, sync),
|
|
1052
1184
|
is told the measures and every finding with the colliding labels' coordinates, and returns
|
|
1053
1185
|
the fixed scene.
|
|
1054
1186
|
|
|
1055
|
-
**Caps**, checked before every call: `--bespoke-calls` (default
|
|
1056
|
-
`--bespoke-seconds` (default
|
|
1057
|
-
|
|
1058
|
-
|
|
1187
|
+
**Caps**, checked before every call: `--bespoke-calls` (default 40 scene calls; two per
|
|
1188
|
+
beat), `--bespoke-seconds` (default 3600 for the pass) and `bespoke.callSeconds` (default
|
|
1189
|
+
600: one call's ceiling, the per-scene timeout). A quota or rate-limit answer stops every
|
|
1190
|
+
further call. Codex runs only through the account's own CLI (`--bespoke-cli`,
|
|
1191
|
+
`--bespoke-model`), never an API key, two calls in flight across pictures, the device call
|
|
1192
|
+
and scenes alike (`bespoke.concurrency` in the config file), at
|
|
1059
1193
|
`bespoke.effort` reasoning (default `medium`; `default` keeps the account's), from the
|
|
1060
1194
|
scratch directory, and with its tools off: shell, apps, plugins, browser, image
|
|
1061
1195
|
generation (the illustration call alone keeps it), web search and every MCP server the
|
|
@@ -1074,7 +1208,10 @@ a fallback the beat did not earn (cap, quota, timeout) is not.
|
|
|
1074
1208
|
|
|
1075
1209
|
**What a generated scene may run.** Three layers. (1) A static walk before anything is
|
|
1076
1210
|
built or opened (`src/bespoke/contract.ts`, acorn): GSAP timeline calls with literal vars
|
|
1077
|
-
at explicit seconds
|
|
1211
|
+
at explicit seconds — `tl.fromTo` whose from state names every property it moves, and
|
|
1212
|
+
`tl.set`; `tl.to`, `tl.from` and keyframes are refused (`script_fromto`, AGENTS.md invariant
|
|
1213
|
+
2: a `to` starts from whatever the page holds when it first renders) — `gsap.set`, scoped
|
|
1214
|
+
`root.querySelector`, `Math` minus `random`, local
|
|
1078
1215
|
code; `window`, `document`, `fetch`, `eval`, timers, storage, navigation, callbacks and
|
|
1079
1216
|
function-valued tween vars are refused by name; CSS must be scoped to the scene with no
|
|
1080
1217
|
at-rules, `url()` or animation; markup is SVG and inline HTML with no handlers, SMIL or
|
|
@@ -1097,16 +1234,42 @@ its plate — or a shape painted over one), `seek_order` (the frame must not dep
|
|
|
1097
1234
|
was seeked before it, beyond 1500px of rasterising noise — counted on changed AREAS: a pixel
|
|
1098
1235
|
counts when it and its 8 neighbours changed, so a picture re-rasterised a hair differently
|
|
1099
1236
|
on its first paint, an outline round every subject, 12,698px raw, counts 309), `stage_fill` (the settled
|
|
1100
|
-
drawing's bounding box covers 80% of its box), `type_hierarchy` (one label of
|
|
1101
|
-
more at the end; not asked of an illustrated scene), `
|
|
1102
|
-
|
|
1103
|
-
|
|
1237
|
+
drawing's bounding box covers 80% of its box), `type_hierarchy` (one label of 44px or
|
|
1238
|
+
more at the end; not asked of an illustrated scene), `type_ceiling` (no label DECLARES more
|
|
1239
|
+
than 56px at the end; KaTeX is exempt), `type_scale` (round 6: no word the scene
|
|
1240
|
+
draws RENDERS above the 56px headline at any graded frame, its camera's zoom included;
|
|
1241
|
+
round 2's 64px key-label floor is gone), `ui_motion` (round 6: the scene's own GSAP
|
|
1242
|
+
timeline animates no label, plate, chip, card or bar into place — read off the tweens'
|
|
1243
|
+
targets and properties, so an opacity fade passes and a particle or a traced path is not
|
|
1244
|
+
UI; a bar is a rect resized, or a path or polygon grown from under 70% of its size; the
|
|
1245
|
+
shell's own counter-scale of words under a camera is not counted), `seam_blank` (round 6:
|
|
1246
|
+
the stage — everything but the headlines — painted under 0.2% of the frame for more than
|
|
1247
|
+
0.15s across the seam into a generated scene; r1's final deck showed nothing for ~0.6s at 12
|
|
1248
|
+
of 15 seams, so cue 1's group now stands from the scene's first frame), `hollow_hold` (while
|
|
1249
|
+
cue 2 onward holds, and at the end, the drawing spans under 70% of the box's height),
|
|
1250
|
+
`marks_overlap` (at those frames, alike filled dots drawn into each other, deeper than half
|
|
1251
|
+
the smaller's radius), `stray_marker` (an SVG marker painted where its line is not drawn),
|
|
1252
|
+
`early_reveal` (a `data-cue="N"` group, N of 2 or more, showing more than 0.5s before cue N),
|
|
1253
|
+
`text_clipped` (a word cut by the frame's edge while held: sampled every 0.5s, three samples
|
|
1254
|
+
in a row, under 96% of its width or 75% of its line box inside the clip), `morph_glitch` (a
|
|
1255
|
+
path tween — `attr: { d }` or morphSVG — whose shape, at any in-between sample, reaches more
|
|
1256
|
+
than 35% of its ends' size past the union of its two ends: r1 s4's wedge, an x tweened into
|
|
1257
|
+
a y between paths of different commands), `dim_text` (a word held under 3:1 contrast with
|
|
1258
|
+
what is painted behind it at a cue's end or the end; repaired without a call by lifting a
|
|
1259
|
+
literal dimming opacity on its targets to 0.6),
|
|
1260
|
+
`shot_variety` (an illustrated scene opens on the whole picture — unless its grammar is
|
|
1261
|
+
`close-open` — and the camera — sampled
|
|
1104
1262
|
every 0.5s, no screenshot — HOLDS push-ins at 1.5x or closer on at least two different
|
|
1105
|
-
subjects; round 3's 1.1-1.4x pans are not push-ins), `label_anchor` (a label that names a
|
|
1263
|
+
subjects; round 3's 1.1-1.4x pans are not push-ins), `label_anchor` (a label of the scene's that names a
|
|
1106
1264
|
subject sits within 96px of its box and covers no other subject by more than a quarter of
|
|
1107
|
-
itself
|
|
1265
|
+
itself), `data_over_picture`
|
|
1108
1266
|
(five or more numbers on the illustration),
|
|
1109
|
-
`cue_groups` (no such groups
|
|
1267
|
+
`cue_groups` (no such groups — not asked of a scene on a picture, which may add nothing —
|
|
1268
|
+
or one naming a cue the scene does not have), `card_row`
|
|
1269
|
+
(`src/bespoke/cards.ts`, read off the markup: three or more alike rectangles — rects, or
|
|
1270
|
+
divs placed in px — in a row or a column, or four in a grid, covering 12% of the box —
|
|
1271
|
+
cards, panels or tiles as the main visual; never asked of a data beat, whose bars are alike
|
|
1272
|
+
by design; such a scene goes to its critique and falls back if it is still cards), `end_dimmed`
|
|
1110
1273
|
(at the settled frame, more than 10% of the parts the scene had shown lit are left under
|
|
1111
1274
|
0.6 opacity — a part drawn translucent from the start is not counted) and `camera_end` (the
|
|
1112
1275
|
camera, or a viewBox, not home at the settled frame). Content
|
|
@@ -1127,15 +1290,25 @@ skipped by `build` when the pass already ran it on the same bytes at the same sc
|
|
|
1127
1290
|
always runs it. Set
|
|
1128
1291
|
`DECKSMITH_BESPOKE_WORK=<dir>` to keep the prompts, replies and contact sheets.
|
|
1129
1292
|
|
|
1130
|
-
**Costs** (
|
|
1131
|
-
[`.planning/2026-10-
|
|
1293
|
+
**Costs, round 6** (four HypePaper decks, twice each, see
|
|
1294
|
+
[`.planning/2026-10-10-v2-bespoke-round6.md`](.planning/2026-10-10-v2-bespoke-round6.md)):
|
|
1295
|
+
a picture takes 90-200s (two layers, redraws included) and its depth cut 5-26s; a draft on a
|
|
1296
|
+
picture ~25s, and critique calls are rare; a deck's `build` took 431-696s for 5-7 scene calls
|
|
1297
|
+
plus 5-6 pictures and 157-237k tokens. A deck carries 1.3-2.9 MB of planes (5.4-7.2 MB in
|
|
1298
|
+
all, against round 5's 4.0-5.3). Round 4's numbers, for the record
|
|
1299
|
+
([`.planning/2026-10-09-v2-bespoke-round4.md`](.planning/2026-10-09-v2-bespoke-round4.md)):
|
|
1132
1300
|
an illustration takes 60-120s, a draft 90-220s and a critique 70-120s; a deck's pass took
|
|
1133
1301
|
380-730s for 5-8 scene calls plus 3-7 pictures (redraws included) and 153-227k tokens, and
|
|
1134
1302
|
the whole `build` 466-812s on a Mac with 9-23% memory free — three builds of eight under ten
|
|
1135
1303
|
minutes. The pictures add 128-348 KB of WebP to a deck (round 3: 6-7 MB of PNG). Round 3
|
|
1136
1304
|
took 593-742s and 214-325k tokens; round 1 13-31 minutes and 540-705k. A rebuild from the
|
|
1137
|
-
cache makes 0 calls.
|
|
1138
|
-
|
|
1305
|
+
cache makes 0 calls. Those are 4-6-beat passes at five calls in flight; drawing every beat
|
|
1306
|
+
of a deck at two in flight costs several times that: MEASURED 2026-10-10 on one 14-beat ko
|
|
1307
|
+
deck, 11 of 14 drawn bespoke, 21 scene calls + 1 device call + 3 pictures, 574k tokens, a
|
|
1308
|
+
1508s pass and a 1567s `build`.
|
|
1309
|
+
None of it fits a 300-second build timeout: a host that builds v2 decks on its request path
|
|
1310
|
+
— HypePaper's build step — must pass `--no-bespoke`, or give `build` that long off the
|
|
1311
|
+
request path.
|
|
1139
1312
|
|
|
1140
1313
|
## Themes
|
|
1141
1314
|
|
|
@@ -1262,13 +1435,28 @@ decksmith illustrate storyboard.json --source source.json
|
|
|
1262
1435
|
decksmith build storyboard.json --source source.json -o out/
|
|
1263
1436
|
```
|
|
1264
1437
|
|
|
1265
|
-
With `--images`, a beat that has nothing in the inventory to show — a `claim-figure`,
|
|
1266
|
-
either side of a `split-compare` — may carry an `illustration: { prompt, caption }` in
|
|
1267
|
-
place of a `figureId`.
|
|
1438
|
+
With `--images`, a beat that has nothing in the inventory to show — a `claim-figure`, a
|
|
1439
|
+
`stage`, or either side of a `split-compare` — may carry an `illustration: { prompt, caption }` in
|
|
1440
|
+
place of a `figureId`. A `stage` is how a paper whose figures are all plots gets a
|
|
1441
|
+
full-screen picture; its brief is drawn landscape, since the format is not known until
|
|
1442
|
+
`build`, and a portrait build crops it (and warns). The prompt describes a scene, never text, labels, numbers or charts:
|
|
1268
1443
|
nothing inside a picture can be read or checked, so the picture illustrates and the beat's
|
|
1269
1444
|
`evidence` still points at the section. `plan` says how many pictures the storyboard asks
|
|
1270
1445
|
for and the command to run; `build` and `pack` refuse the file until they exist.
|
|
1271
1446
|
|
|
1447
|
+
A `pipeline`, `split-compare`, `callout`, `bar-compare`, `hero-number` or `kinetic` may
|
|
1448
|
+
also carry a `backdrop: { illustration }` — a scene the diagram is drawn over. The picture covers the
|
|
1449
|
+
frame and drifts slowly, a uniform black scrim (0.62) darkens it, and the archetype is
|
|
1450
|
+
drawn unchanged in a glass palette: dark translucent panels, light ink, tones brightened
|
|
1451
|
+
(then whitened only if they must be) just far enough to clear 4.5:1 over a pure-white
|
|
1452
|
+
pixel, and the step-back ink `dim` held below every tone at 3:1, the floor for large
|
|
1453
|
+
text. Geometry and holds are the
|
|
1454
|
+
archetype's own, so a backdrop changes colours and nothing else. With `--images` the
|
|
1455
|
+
planner is held to making most of a deck scenes (60% of beats, within `images.max`) — a
|
|
1456
|
+
stage, a backdrop, a pictured split-compare side, a hero-number or a kinetic claim; a
|
|
1457
|
+
`claim-figure` or `annotated-figure` sits on the pale ground and does not count — so
|
|
1458
|
+
a figure-less paper reads as a sequence of pictures rather than cards on one ground.
|
|
1459
|
+
|
|
1272
1460
|
`illustrate` turns each brief into a file under `assets/` beside `source.json`, registers
|
|
1273
1461
|
it as an ordinary figure, and sets the beat's `figureId`; the brief stays on the beat as
|
|
1274
1462
|
provenance. From there nothing downstream knows the picture was generated — `build`,
|
|
@@ -1326,6 +1514,124 @@ carry SMIL or CSS animation, which runs on wall-clock time under capture — a
|
|
|
1326
1514
|
nondeterministic render that every gate passes. The tool's own SVG has no text, no
|
|
1327
1515
|
animation and no external references, and the same brief always draws the same bytes.
|
|
1328
1516
|
|
|
1517
|
+
## Animated pieces
|
|
1518
|
+
|
|
1519
|
+
A **piece** is a figure that moves: a cut-paper scene written for
|
|
1520
|
+
[animate](https://github.com/cth9191/animate), drawn on a `<canvas>` by `claim-figure` or `stage`
|
|
1521
|
+
and played by one `dsAnimate` tween. It is a third figure `kind`, next to `image` and `clip`.
|
|
1522
|
+
You write it by hand. No ingest, `plan`, server or MCP path produces one.
|
|
1523
|
+
|
|
1524
|
+
**Authoring one** takes three edits. Put the scene file under `assets/` beside
|
|
1525
|
+
`source.json`, give it a figure entry, and point a `claim-figure` beat (or a `stage` beat, for the piece alone on the whole frame)
|
|
1526
|
+
at that entry:
|
|
1527
|
+
|
|
1528
|
+
```jsonc
|
|
1529
|
+
// source.json, under "figures"
|
|
1530
|
+
{ "id": "fig-garden", "kind": "piece", "src": "pieces/garden.js",
|
|
1531
|
+
"width": 1920, "height": 1080, "seconds": 6, "caption": "The moon becomes the sun" }
|
|
1532
|
+
// storyboard.json, a beat
|
|
1533
|
+
{ "id": "b2", "archetype": "claim-figure", "seconds": 9, "intent": "…",
|
|
1534
|
+
"params": { "headline": "The moon becomes the sun", "claim": "…", "figureId": "fig-garden" } }
|
|
1535
|
+
```
|
|
1536
|
+
|
|
1537
|
+
The file is an animate `scenes.js` plus `bridges.js`. It defines `ERA_LIST`, `SHOTS`,
|
|
1538
|
+
`ERA_BG`, `BRIDGES`, `pieceCam` and its scene functions, and it uses the kit's names
|
|
1539
|
+
(`cut`, `rect`, `ellipsePts`, `PAL`, `pat`, `spark`, `TT`, `EZ`, `LX`/`LY`, `UNIT` and the
|
|
1540
|
+
rest):
|
|
1541
|
+
|
|
1542
|
+
```js
|
|
1543
|
+
const ERA_BG = ['#cfe2ee'];
|
|
1544
|
+
function pieceCam(era, t) { return null; }
|
|
1545
|
+
function sceneSky() {
|
|
1546
|
+
cut(rect(-30, -30, W + 60, H + 60, 0), PAL.sky, { key: 'bg', shadow: false, tear: 0, shade: false });
|
|
1547
|
+
const k = EZ.io(seg(TT, 0, DURATION)); // a sun that crosses the sky over the whole piece
|
|
1548
|
+
cut(ellipsePts(LX(0.12 + 0.76 * k), LY(0.55 - 0.38 * Math.sin(Math.PI * k)), 110 * UNIT, 110 * UNIT, 0, 30), PAL.yellow, { key: 'sun' });
|
|
1549
|
+
}
|
|
1550
|
+
const BRIDGES = []; // or [{ tc, A: () => shape, B: () => shape }] between eras
|
|
1551
|
+
const ERA_LIST = [[0, DURATION, () => sceneSky()]];
|
|
1552
|
+
const SHOTS = [['sky', 0, 0.0, DURATION, 'sky']];
|
|
1553
|
+
```
|
|
1554
|
+
|
|
1555
|
+
Do not define `W`, `H`, `FPS`, `DURATION`, `NFRAMES`, `LOOP_T`, `SAFE`, `HAND`, `CX` or
|
|
1556
|
+
`TIMELINE`. DeckSmith sets them from the figure: its `width`, `height` and `seconds`, 30
|
|
1557
|
+
fps, and the deck's font stack. On a `stage` beat `W` and `H` are the frame's pixels
|
|
1558
|
+
instead (less any caption reserve), so lay the piece out with `LX`, `LY` and `UNIT` rather than
|
|
1559
|
+
fixed coordinates. A file that declares one of them fails to load, and
|
|
1560
|
+
`verify` reports that as a page error. `TIMELINE` carries `shots` only. animate's own demos
|
|
1561
|
+
read `TIMELINE.cues`, so write those times as numbers.
|
|
1562
|
+
|
|
1563
|
+
`build` wraps the file and the kit into one script, `assets/<src>`, inside a function, so
|
|
1564
|
+
none of the kit's names reach the page. The piece starts 1.0s into its beat and runs for
|
|
1565
|
+
its `seconds`, with a 0.3s hold after its last frame. Before the tween the canvas shows
|
|
1566
|
+
frame 0. After it, the canvas holds the last frame; it does not wrap back to frame 0. The
|
|
1567
|
+
beat must be at least `1 + seconds + 0.3` long. A shorter beat is refused by name rather
|
|
1568
|
+
than clamped. The hold is never before 2.4s, when the claim, plate and caption have all
|
|
1569
|
+
entered, so a piece shorter than 1.1s holds there instead. On a `stage` the floor is 2.1s
|
|
1570
|
+
when there are words over it and 1.1s when there are none.
|
|
1571
|
+
|
|
1572
|
+
**What is refused**, and where:
|
|
1573
|
+
|
|
1574
|
+
- **Text in a piece.** `fillText` and `strokeText` throw on every 2D canvas while the
|
|
1575
|
+
piece runs, the kit's offscreen layers included. That is a page error, so `verify` and
|
|
1576
|
+
`build` fail. `handText`, `tag`, `yearTag`, `capStrip`, `monoText` and `cat` all draw
|
|
1577
|
+
text, so none of them can be used. Labels belong in the claim, which is DOM text and
|
|
1578
|
+
is held to the 40px floor. `handwrite` draws letters as ink strokes, which no runtime
|
|
1579
|
+
trap can tell from lines, so `build` refuses a file that calls it by name
|
|
1580
|
+
(`pieces/garden.js:12 calls handwrite — …`). An alias of it is not caught.
|
|
1581
|
+
- **A last era or last shot that ends before the piece does.** morph.js draws era 0 for
|
|
1582
|
+
any time past the last era, so `[[0, 3, …]]` in a 6s piece would replay the opening
|
|
1583
|
+
scene for 3s and the whole hold. The piece throws when it mounts (`piece "fig-garden":
|
|
1584
|
+
ERA_LIST ends at 3s and the figure plays 6s — end its last entry at DURATION`), which
|
|
1585
|
+
fails `verify`. End the last entry of `ERA_LIST` and of `SHOTS` at `DURATION`.
|
|
1586
|
+
- **More than one piece per deck.** `build` stops with ``claim-figure b3: figure
|
|
1587
|
+
"fig-garden-2" is a second animate piece in this deck — b2 already draws "fig-garden"``.
|
|
1588
|
+
The vendored `morph.js` is unpatched and writes `window.renderFrame`, so two pieces
|
|
1589
|
+
would share that global.
|
|
1590
|
+
- **Styles other than cut-paper.** Only the cut-paper kit is vendored. Another style's
|
|
1591
|
+
primitive is simply undefined: a piece that calls riso's `plates()` fails `verify` with
|
|
1592
|
+
`page_error plates is not defined`. Riso and pixel read pixels back from the canvas,
|
|
1593
|
+
and that has not been measured under capture.
|
|
1594
|
+
- A piece in any archetype but `claim-figure` and `stage`, a piece with no `seconds`, a `src` that
|
|
1595
|
+
does not end in `.js` (the determinism scan reads only those), and an `id` containing
|
|
1596
|
+
anything other than letters, digits, `.`, `_` and `-`.
|
|
1597
|
+
|
|
1598
|
+
**What the gates see.** A piece that throws while it draws fails `verify`, because the
|
|
1599
|
+
runtime reports the error before rethrowing it. Before that fix, hyperframes swallowed
|
|
1600
|
+
the error and every gate passed a piece frozen on a stale frame. That is the eleventh
|
|
1601
|
+
case under "What the gates do not check". `render` still exits 0 when the page has
|
|
1602
|
+
errors, so `verify` is the gate here. `hyperframes check` sees a throw only at the times
|
|
1603
|
+
it samples, so the fidelity gate also draws every frame of each piece once, off-screen,
|
|
1604
|
+
and fails with `piece_error` on any that throws: a throw confined to one frame is caught
|
|
1605
|
+
too. `frames` refuses to write a PNG once the page has raised an error, rather than
|
|
1606
|
+
saving a stale or half-painted canvas. `hyperframes check` cannot see inside a canvas:
|
|
1607
|
+
layout and contrast cover only the DOM around it. The determinism scan reads
|
|
1608
|
+
`assets/**/*.js`, so a `Math.random`, a `setTimeout`/`setInterval`, a
|
|
1609
|
+
`requestAnimationFrame` loop or a `new Image()` in a piece is caught. A checker without WebGL
|
|
1610
|
+
measures a piece instead of refusing it, because the piece's canvas carries
|
|
1611
|
+
`data-ds-piece`.
|
|
1612
|
+
|
|
1613
|
+
Measured on 2026-10-09 (macOS, Metal, hyperframes' headless shell). The deck had a 6s
|
|
1614
|
+
two-era piece with a moon-to-sun bridge in a 9s beat:
|
|
1615
|
+
|
|
1616
|
+
- `build` and `verify` both passed.
|
|
1617
|
+
- `render -w 1` took 13.1s for 360 frames. Stills at ten instants showed the night scene,
|
|
1618
|
+
the bridge, the morning scene and then the last frame held.
|
|
1619
|
+
- `drift --workers 1` found 360/360 frames byte-identical, and a second render produced
|
|
1620
|
+
an mp4 with the same md5.
|
|
1621
|
+
- A `pack`/`unpack` round trip rebuilt the same files.
|
|
1622
|
+
|
|
1623
|
+
**Limits.** A piece is baked at 30 fps, so `render --fps 24` judders, and `--speed` plays
|
|
1624
|
+
the whole piece faster or slower than written. Each piece inlines about 60 KB of kit.
|
|
1625
|
+
Canvas raster differs between Metal and SwiftShader, so compare `drift` runs on one
|
|
1626
|
+
backend only. See `.planning/2026-10-09-animate-piece-design.md` and
|
|
1627
|
+
`.planning/2026-10-09-animate-piece-spike.md`.
|
|
1628
|
+
|
|
1629
|
+
**Attribution.** The kit (`core.js`, `morph.js`, `cut-paper.js`) is animate @7e5eb56,
|
|
1630
|
+
copyright (c) 2026 cth9191, MIT. It is vendored unmodified in `src/build/animate/`, with
|
|
1631
|
+
upstream's `LICENSE` and a `NOTICE` that names the commit and each file's sha256.
|
|
1632
|
+
`npm run build` copies all of it to `dist/animate/`. Every assembled piece script carries
|
|
1633
|
+
the MIT text in its header, so a built deck carries the attribution too.
|
|
1634
|
+
|
|
1329
1635
|
## The `.deck` container
|
|
1330
1636
|
|
|
1331
1637
|
One file holding the whole deck: the source, the storyboard, the preferences it was made
|
|
@@ -1386,7 +1692,7 @@ names the figure or equation it rests on, a later pass can ask whether the anima
|
|
|
1386
1692
|
actually asserts what the source asserts. Prior art verifies that slides *look* fine;
|
|
1387
1693
|
nothing verifies that they are *true*.
|
|
1388
1694
|
|
|
1389
|
-
## The
|
|
1695
|
+
## The sixteen archetypes
|
|
1390
1696
|
|
|
1391
1697
|
The explanatory vocabulary. These came out of hand-building a real deck
|
|
1392
1698
|
(`.planning/EXPERIMENT-002-thinksr-korean.md`), not from guessing at what might be useful.
|
|
@@ -1395,21 +1701,71 @@ The explanatory vocabulary. These came out of hand-building a real deck
|
|
|
1395
1701
|
|---|---|---|
|
|
1396
1702
|
| `title` | opening or section break | `headline`, `eyebrow?`, `sub?` |
|
|
1397
1703
|
| `claim-figure` | one assertion beside the figure that supports it | `claim`, `figureId` |
|
|
1704
|
+
| `stage` | one picture, UI, clip or piece filling the whole frame, words optional over it | `figureId` or `illustration`, `placement`, `headline`, `line?` |
|
|
1398
1705
|
| `equation-walk` | an equation explained symbol by symbol | `equationId`, `terms` (1–4) |
|
|
1399
1706
|
| `equation-morph` | one equation becoming the next, the shared terms carried across | `fromId`, `toId`, `terms` (1–4) |
|
|
1400
1707
|
| `data-table` | a results table with rows revealed in argument order | `tableId`, `highlight` |
|
|
1401
1708
|
| `line-chart` | a trend, with per-step deltas, optionally against a baseline | `points`, `deltas?`, `readout?`, `compare?` |
|
|
1402
|
-
| `callout` | 1–3 panels of prose: definitions, contrasts, takeaways | `panels`, `note?` |
|
|
1403
|
-
| `pipeline` | stages in a flow, arrowed, with an optional feedback loop | `stages` (2–6), `loop?` |
|
|
1709
|
+
| `callout` | 1–3 panels of prose: definitions, contrasts, takeaways | `panels`, `note?`, `backdrop?` |
|
|
1710
|
+
| `pipeline` | stages in a flow, arrowed, with an optional feedback loop | `stages` (2–6), `loop?`, `backdrop?` |
|
|
1404
1711
|
| `annotated-figure` | a figure cropped to the panel under discussion, with leader lines | `figureId`, `crop?`, `notes` |
|
|
1405
1712
|
| `grid` | regions of a field lit in turn: windows, patches, receptive fields | `cols`, `rows`, `regions` |
|
|
1406
|
-
| `bar-compare` | magnitudes that share a unit, grown from zero | `bars` (2–8), `unit?` |
|
|
1713
|
+
| `bar-compare` | magnitudes that share a unit, grown from zero | `bars` (2–8), `unit?`, `backdrop?` |
|
|
1407
1714
|
| `stack` | layers drawn bottom-up as offset planes | `layers` (2–7) |
|
|
1408
|
-
| `split-compare` | two things side by side, each figure or lines | `left`, `right` |
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1715
|
+
| `split-compare` | two things side by side, each figure or lines | `left`, `right`, `backdrop?` |
|
|
1716
|
+
| `hero-number` | one number as the slide's statement (v2: 56px, faded in; classic: filling the frame, rolled in like an odometer) | `value`, `unit?`, `label`, `compare?`, `headline`, `backdrop?` |
|
|
1717
|
+
| `kinetic` | a claim as type: 2–4 phrases (v2: 44-56px, faded in, the key word turns the accent; classic: each its own move, a key word struck) | `phrases` (`text`, `key?`), `headline`, `backdrop?` |
|
|
1718
|
+
|
|
1719
|
+
All but `title`, `data-table`, `callout` and `kinetic` draw rather than describe
|
|
1720
|
+
(`DIAGRAMMATIC` in `src/types.ts`), and `verify` warns when a deck leans on those four: a
|
|
1721
|
+
deck of headlines and bullet panels is what every other slide generator already makes.
|
|
1722
|
+
|
|
1723
|
+
`stage` is the slide that is not a slide: the figure covers the frame edge to edge
|
|
1724
|
+
(cover-fit, so it is cropped to the frame's shape), with no plate, border or column. Words
|
|
1725
|
+
are optional. `placement` is one of `none`, `bottom-left`, `top-left`, `center` or
|
|
1726
|
+
`right`; under anything but `none` the `headline` (72px) and an optional `line` (44px)
|
|
1727
|
+
are set in white over a black scrim. The scrim's solid part is sized from the text's
|
|
1728
|
+
measured height and width, bounded on both axes so the rest of the picture is left
|
|
1729
|
+
alone, and is 55% black, which white text clears at about 4.8:1 even over a pure white
|
|
1730
|
+
picture, so the contrast gate passes on whatever the picture is. Text is never shrunk: words
|
|
1731
|
+
that need more than three headline lines or two of `line` in their placement's column take
|
|
1732
|
+
a wider column (and `build` warns), and only words that will not fit across the whole
|
|
1733
|
+
content width are refused by name. `right` sets the words flush right; in a portrait
|
|
1734
|
+
frame every placement is full width. The picture fades and settles in first, the words after; one stop lands
|
|
1735
|
+
once both are in, or after a piece's last frame. A still or clip drifts 4% over the beat.
|
|
1736
|
+
`build` warns (it does not refuse) when a raster figure is upscaled more than 1.5x or
|
|
1737
|
+
cropped by more than 30% to fill the frame. With `--reserve-captions` the picture stops
|
|
1738
|
+
above the caption strip. The planner is told to reach for it when a visual should own the
|
|
1739
|
+
screen, and, under RULE 1's variety rule, to give consecutive stages different placements.
|
|
1740
|
+
|
|
1741
|
+
`hero-number` and `kinetic` are full-bleed too, and need no picture (in `--design v2` so
|
|
1742
|
+
is a `bar-compare`, which classic still draws on its ground). Without a
|
|
1743
|
+
`backdrop` they stand on a field of one of the pack's colours — the accent and each tone in
|
|
1744
|
+
turn, by the beat's place in the deck, so field beats near each other differ — darkened
|
|
1745
|
+
until it is no lighter
|
|
1746
|
+
than half the backdrop scrim's worst-case ground, so the same glass inks clear 4.5:1 on
|
|
1747
|
+
it; with one they go over the picture as the diagrams do. Under `--design v2` both are
|
|
1748
|
+
quiet: `hero-number` sets `value` at the headline's 56px in the accent, its label and
|
|
1749
|
+
comparison as body lines, every part faded in where it stands; `kinetic` sets its phrases
|
|
1750
|
+
at 56px down to 44px, each word fading in, and turns the `key` the accent instead of
|
|
1751
|
+
sweeping a chip behind it — the motion is the picture under them. In classic,
|
|
1752
|
+
`hero-number` sets `value` as
|
|
1753
|
+
large as the frame holds (160–560px) and rolls each digit in on a reel — a strip of
|
|
1754
|
+
0-9 clipped to one cell with `clip-path`, its `y` tweened a whole number of cells, the
|
|
1755
|
+
right-hand reels turning most and every reel landing left to right. No counter is
|
|
1756
|
+
written from a callback (invariant 11). A `compare` whose value and `value` are both
|
|
1757
|
+
plain numbers is drawn as two bars to one scale, the baseline first — unless the
|
|
1758
|
+
shorter would be over 85% of the longer, when bars read as "equal" and the baseline
|
|
1759
|
+
is a second figure followed by the signed difference ("+0.83 dB"); otherwise as a
|
|
1760
|
+
second figure. One stop, after the `headline` under it. `kinetic` sets two to four
|
|
1761
|
+
`phrases` as large as two lines each allow (64–120px), down a slight stair; each
|
|
1762
|
+
arrives word by word with its own move (rise, slide, drop or zoom, the first seeded by
|
|
1763
|
+
the beat id), then its `key` — verbatim in the phrase — is struck by a chip swept in
|
|
1764
|
+
behind it. One stop per phrase. Its moves are the content, so the scene sets
|
|
1765
|
+
`ownEntrances` and the v2 motion grammar leaves them alone. Its `headline` labels the
|
|
1766
|
+
slide and is not drawn. With `--images` both count as scenes, and the planner is held
|
|
1767
|
+
to at most 36% panel beats (pipeline, split-compare, callout, stack, data-table,
|
|
1768
|
+
backdrop or not): five of fourteen.
|
|
1413
1769
|
|
|
1414
1770
|
`line-chart`'s `compare` is the one parameter that changes what its archetype *is*. Given
|
|
1415
1771
|
`{ label, points }` the chart draws the baseline first, holds it, then reshapes the curve
|
|
@@ -1606,6 +1962,8 @@ src/prefs.ts the three-layer preference resolver
|
|
|
1606
1962
|
src/emit/kit.ts the seam between the deck shell and the archetype emitters
|
|
1607
1963
|
src/emit/archetypes/ one emitter per archetype
|
|
1608
1964
|
src/emit/themes/ one palette per file; the registry is the extension point
|
|
1965
|
+
src/emit/animate-runtime.ts the dsAnimate plugin that draws an animated piece
|
|
1966
|
+
src/build/animate/ animate's cut-paper kit, vendored unmodified (MIT; LICENSE, NOTICE)
|
|
1609
1967
|
src/emit/type.ts type specs: the v2 packs' faces and chrome scale, read by chromeHeight and chromeCss
|
|
1610
1968
|
src/emit/faces.ts measured width tables for the packs' Latin faces (generated)
|
|
1611
1969
|
src/images/ the three rungs a brief is drawn through, and the illustrate step
|
|
@@ -1636,4 +1994,5 @@ where a real fix would arrive.
|
|
|
1636
1994
|
|
|
1637
1995
|
## Licence
|
|
1638
1996
|
|
|
1639
|
-
MIT.
|
|
1997
|
+
MIT. The animate kit vendored in `src/build/animate/` is also MIT, copyright (c) 2026
|
|
1998
|
+
cth9191. Its `LICENSE` and `NOTICE` sit beside it; see "Animated pieces".
|