retiam 2.0.1 → 2.0.3

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.
Files changed (63) hide show
  1. package/THIRD_PARTY_NOTICES.md +3 -3
  2. package/agent/AGENTS.md +70 -0
  3. package/agent/CLAUDE.md +2 -0
  4. package/agent/skill/SKILL.md +114 -0
  5. package/agent/skill/examples/AreaLesson.ts +48 -0
  6. package/agent/skill/examples/ExplainMotion.ts +32 -0
  7. package/agent/skill/examples/NarratedMotion.ts +50 -0
  8. package/agent/skill/examples/SecantToTangent.ts +142 -0
  9. package/agent/skill/references/api.md +111 -0
  10. package/agent/skill/references/explainers.md +123 -0
  11. package/agent/skill/references/quality.md +151 -0
  12. package/agent/skill/references/styles.md +58 -0
  13. package/agent/skill/references/tts.md +166 -0
  14. package/agent/templates/explainer/lesson.json +25 -0
  15. package/agent/templates/explainer/review.json +6 -0
  16. package/agent/templates/explainer/styles.json +92 -0
  17. package/bin/agent-kit.js +26 -0
  18. package/bin/cli.js +24 -4
  19. package/bin/context.js +57 -0
  20. package/bin/init.js +71 -12
  21. package/bin/lesson-project.js +30 -0
  22. package/bin/lesson.js +195 -0
  23. package/bin/lint.js +6 -6
  24. package/bin/snapshot-runtime.js +185 -0
  25. package/bin/snapshot.js +32 -225
  26. package/dist/THIRD_PARTY_NOTICES.md +3 -3
  27. package/dist/UTIF-D6vx2UEX.min.js +1 -0
  28. package/dist/{UTIF-2Ear_0Uo.js → UTIF-mkffT70R.js} +1 -1
  29. package/dist/decompress-D7LzbvPD.min.js +15 -0
  30. package/dist/{index-B2sJBy4c.js → index-CdBPrYbr.js} +3830 -3795
  31. package/dist/index-jgFyrc2D.min.js +10472 -0
  32. package/dist/libs/explainer/example.d.ts +3 -0
  33. package/dist/libs/explainer/index.d.ts +5 -0
  34. package/dist/libs/explainer/index.js +135 -0
  35. package/dist/libs/explainer/plan.d.ts +12 -0
  36. package/dist/libs/explainer/plan.js +219 -0
  37. package/dist/libs/explainer/runtime.d.ts +24 -0
  38. package/dist/libs/explainer/styles.d.ts +3 -0
  39. package/dist/libs/explainer/types.d.ts +124 -0
  40. package/dist/libs/explainer/visuals.d.ts +18 -0
  41. package/dist/libs/tts/animations/Speak.d.ts +13 -7
  42. package/dist/libs/tts/components/TTSNarrationComponent.d.ts +9 -7
  43. package/dist/libs/tts/core/TTSCache.d.ts +3 -3
  44. package/dist/libs/tts/core/TTSClient.d.ts +3 -3
  45. package/dist/libs/tts/core/types.d.ts +48 -6
  46. package/dist/libs/tts/index.d.ts +2 -1
  47. package/dist/libs/tts/index.js +203 -1333
  48. package/dist/libs/tts/node/index.d.ts +10 -0
  49. package/dist/libs/tts/node/index.js +791 -0
  50. package/dist/libs/tts/providers/ElevenLabsTTSProvider.d.ts +30 -0
  51. package/dist/libs/tts/providers/ITTSProvider.d.ts +2 -2
  52. package/dist/libs/vite/index.d.ts +28 -0
  53. package/dist/libs/vite/index.js +158 -0
  54. package/dist/main-Cc4QQbSK.min.js +5939 -0
  55. package/dist/{main-D_G4uIju.js → main-DMu_5Qy7.js} +1 -1
  56. package/dist/msEdgeV2-D3w4Xsn-.js +642 -0
  57. package/dist/opentype.module-B-1cYAb-.min.js +1 -0
  58. package/dist/retiam.es.js +1 -1
  59. package/dist/retiam.es.min.js +1 -0
  60. package/dist/src/engine/animation/Animation.d.ts +1 -0
  61. package/dist/src/engine/entities/composites/text/mathjaxInterop.d.ts +6 -0
  62. package/package.json +15 -8
  63. package/readme.md +27 -7
@@ -311,8 +311,8 @@ After dependency or asset changes, run `npm ci`, then
311
311
  for packages that omit license files, reviewed README excerpts, and asset provenance are recorded in
312
312
  `tools/third-party-sources.json` in the source repository. These internal maintenance records
313
313
  are not needed in distributions. Generation uses local files and checked-in originals;
314
- it does not execute package scripts or fetch new legal terms. Run
315
- `npm run licenses:check` to verify the committed input and file fingerprints.
316
- This command also runs before the library build.
314
+ it does not execute package scripts or fetch new legal terms. This is a
315
+ maintenance command for dependency, asset, or license changes; ordinary builds,
316
+ publishing, and package-version changes do not require regenerating the inventory.
317
317
 
318
318
  <!-- retiam-third-party-packages: ["@babel/code-frame@7.27.1","@babel/helper-validator-identifier@7.28.5","@dimforge/rapier2d-compat@0.20.0","@dimforge/rapier3d-compat@0.20.0","@petamoriken/float16@3.9.3","@puppeteer/browsers@1.9.1","@tensorflow/tfjs-backend-cpu@4.20.0","@tensorflow/tfjs-backend-webgl@4.20.0","@tensorflow/tfjs-backend-webgpu@4.20.0","@tensorflow/tfjs-converter@4.20.0","@tensorflow/tfjs-core@4.20.0","@tensorflow/tfjs-data@4.20.0","@tensorflow/tfjs-layers@4.20.0","@tensorflow/tfjs@4.20.0","@tootallnate/quickjs-emscripten@0.23.0","@types/dom-webcodecs@0.1.18","@types/estree@1.0.7","@types/libtess@1.2.0","@types/long@4.0.2","@types/node-fetch@2.6.13","@types/node@22.7.4","@types/offscreencanvas@2019.3.0","@types/offscreencanvas@2019.7.3","@types/prismjs@1.26.6","@types/seedrandom@2.4.34","@types/wicg-file-system-access@2020.9.8","@webgpu/types@0.1.38","@webgpu/types@0.1.40","@zappar/msdf-generator@1.2.4","agent-base@6.0.2","agent-base@7.1.3","ansi-regex@5.0.1","ansi-styles@4.3.0","argparse@1.0.10","argparse@2.0.1","as-number@1.0.0","ast-types@0.13.4","asynckit@0.4.0","axios@1.20.0","b4a@1.6.7","base64-js@1.5.1","basic-ftp@5.0.5","box3d-wasm@0.2.0","buffer-crc32@0.2.13","buffer@5.7.1","buffer@6.0.3","call-bind-apply-helpers@1.0.2","callsites@3.1.0","chalk@4.1.2","chromium-bidi@0.5.8","cli-progress@3.12.0","cliui@7.0.4","cliui@8.0.1","color-convert@2.0.1","color-name@1.1.4","combined-stream@1.0.8","comlink@4.4.2","commander@9.2.0","core-js@3.29.1","cosmiconfig@9.0.0","cross-fetch@4.0.0","data-uri-to-buffer@6.0.2","debug@4.3.4","degenerator@5.0.1","delayed-stream@1.0.0","devtools-protocol@0.0.1232444","dunder-proto@1.0.1","earcut@3.0.2","emoji-regex@8.0.0","end-of-stream@1.4.4","env-paths@2.2.1","error-ex@1.3.2","es-define-property@1.0.1","es-errors@1.3.0","es-object-atoms@1.1.2","es-set-tostringtag@2.1.0","esbuild@0.25.5","esbuild@0.28.1","escalade@3.2.0","escodegen@2.1.0","esm@3.2.25","esprima@4.0.1","estraverse@5.3.0","esutils@2.0.3","events@3.3.0","extract-zip@2.0.1","extrude-polyline@1.0.6","fast-fifo@1.3.2","fd-slicer@1.1.0","fdir@6.4.5","fdir@6.5.0","follow-redirects@1.16.0","form-data@4.0.6","function-bind@1.1.2","get-caller-file@2.0.5","get-intrinsic@1.3.0","get-proto@1.0.1","get-stream@5.2.0","get-uri@6.0.4","gl-vec2@1.3.0","gopd@1.2.0","hachure-fill@0.5.2","has-flag@4.0.0","has-symbols@1.1.0","has-tostringtag@1.0.2","hasown@2.0.4","http-proxy-agent@7.0.2","https-proxy-agent@5.0.1","https-proxy-agent@7.0.6","ieee754@1.2.1","import-fresh@3.3.1","inherits@2.0.4","ip-address@9.0.5","is-arrayish@0.2.1","is-fullwidth-code-point@3.0.0","isomorphic-ws@5.0.0","jolt-physics@1.1.0","js-tokens@4.0.0","js-yaml@4.1.0","jsbn@1.1.0","json-parse-even-better-errors@2.3.1","libtess@1.2.2","lines-and-columns@1.2.4","long@4.0.0","lru-cache@7.18.3","math-intrinsics@1.1.0","mathjax-full@3.2.2","matter-js@0.20.0","mhchemparser@4.2.1","mime-db@1.52.0","mime-types@2.1.35","mitt@3.0.1","mj-context-menu@0.6.1","mkdirp-classic@0.5.3","monaco-editor@0.45.0","mp4-muxer@5.2.2","ms@2.1.2","msedge-tts@2.0.8","nanoid@3.3.11","netmask@2.0.2","node-fetch@2.6.13","node-fetch@2.7.0","oidn-web@0.3.5","once@1.4.0","opentype.js@1.3.4","pac-proxy-agent@7.2.0","pac-resolver@7.0.1","pako@1.0.11","parent-module@1.0.1","parse-json@5.2.0","path-data-parser@0.1.0","pend@1.2.0","picocolors@1.1.1","picomatch@4.0.2","picomatch@4.0.3","planck@1.5.0","points-on-curve@0.2.0","points-on-path@0.2.1","polygon-clipping@0.15.7","polyline-miter-util@1.0.1","postcss@8.5.6","prismjs@1.30.0","progress@2.0.3","proxy-agent@6.3.1","proxy-from-env@1.1.0","proxy-from-env@2.1.0","pump@3.0.2","puppeteer-core@21.11.0","puppeteer@21.11.0","readable-stream@3.6.2","regenerator-runtime@0.13.11","require-directory@2.1.1","resolve-from@4.0.0","robust-predicates@3.0.3","rollup@4.41.1","roughjs@4.6.6","safe-buffer@5.2.1","seedrandom@3.0.5","smart-buffer@4.2.0","socks-proxy-agent@8.0.5","socks@2.8.5","source-map-js@1.2.1","source-map@0.6.1","speech-rule-engine@4.0.7","splaytree@3.2.3","sprintf-js@1.0.3","sprintf-js@1.1.3","stage-js@1.0.2","stream-browserify@3.0.0","streamx@2.22.1","string-width@4.2.3","string.prototype.codepointat@0.2.1","string_decoder@1.3.0","strip-ansi@6.0.1","supports-color@7.2.0","svg-parser@2.0.4","svg-pathdata@7.1.0","tar-fs@3.0.4","tar-stream@3.1.7","text-decoder@1.2.3","through@2.3.8","tiny-inflate@1.0.3","tinyglobby@0.2.15","tr46@0.0.3","tslib@2.8.1","tsx@4.22.4","typescript@5.4.5","unbzip2-stream@1.4.3","undici-types@6.19.8","urlpattern-polyfill@10.0.0","utif@2.0.1","util-deprecate@1.0.2","vite@6.3.6","webidl-conversions@3.0.1","webm-muxer@5.1.4","wgsl_reflect@1.2.3","whatwg-url@5.0.0","wicked-good-xpath@1.3.0","woff2-encoder@2.0.0","wrap-ansi@7.0.0","wrappy@1.0.2","ws@8.16.0","ws@8.22.0","xmldom-sre@0.1.31","y18n@5.0.8","yargs-parser@20.2.9","yargs-parser@21.1.1","yargs@16.2.0","yargs@17.7.2","yauzl@2.10.0","yerror@8.0.0"] -->
@@ -0,0 +1,70 @@
1
+ # Retiam animation project
2
+
3
+ Read [.agents/skills/retiam/SKILL.md](.agents/skills/retiam/SKILL.md) when creating,
4
+ editing, reviewing, or rendering scenes. It links to the API notes, an executable
5
+ example, and the visual review criteria. This project uses Retiam's TypeScript
6
+ and WebGPU APIs.
7
+
8
+ - Author default-exported `Scene` subclasses in `src/scenes/` with `async run()`.
9
+ - Read the existing scene and `package.json` first. Honor the requested duration,
10
+ aspect ratio, and technical project conventions. A requested visual redesign
11
+ takes precedence over the previous scene's layout.
12
+ - The default creative direction is a **3Blue1Brown-inspired mathematical
13
+ explainer**, matching the Dijkstra reference: dark navy, vivid stable object
14
+ colors, smooth path drawing and value transfers, readable LaTeX, conversational
15
+ questions, thinking pauses, and one attention target at a time. Keep the
16
+ implementation in Retiam TypeScript/WebGPU.
17
+ - Educational projects include `lesson/lesson.json`. Read it before creating an
18
+ explainer, then replace the square-area reference with the user's topic,
19
+ audience, objective, sources, narration and visible events. Use the packaged
20
+ skill's `references/explainers.md` for the production workflow and public API.
21
+ - Style is independent of subject and pedagogy. Honor an explicit prompt style
22
+ over the saved profile, and persist it in the lesson: mathematical (default),
23
+ excalidraw, minimal, cinematic, or custom overrides extending a profile. Read
24
+ `references/styles.md` for the selected look. A biology video need not contain
25
+ code or a table; choose representations that explain its mechanism.
26
+ - For a new video, read the skill's `references/quality.md` before planning.
27
+ Explain through evolving geometry, simulations, and visible cause and effect.
28
+ Avoid a default layout of persistent text panels, chapter slides, page counters,
29
+ and repeated fade-ins. Plan what changes on screen and where attention moves.
30
+ The small setup examples demonstrate APIs; `examples/SecantToTangent.ts` shows
31
+ a more substantial visual explanation. Follow a slide/deck layout only when requested.
32
+ - Use Retiam's built-in libraries for their supported features. For generated
33
+ narration, use `retiam/tts`; read the skill's `references/tts.md` and
34
+ `examples/NarratedMotion.ts`. Use another speech generator only when the user
35
+ requests it. ElevenLabs is a native provider: pass its key to provider.init(),
36
+ direct expression with model-compatible controls, and keep captions clean.
37
+ Follow the TTS reference; no ElevenLabs Vite setup is needed. Diagnose failures
38
+ instead of silently installing Python
39
+ TTS tools or replacing the narration pipeline.
40
+ - Use `retiam/explainer` for prepared speech, named cues, explicit silent pauses
41
+ and synchronized beats. `npm run lesson:check` validates the contract;
42
+ `npm run lesson:prepare` caches enabled native narration. Prepare audio before
43
+ locking timings. Scene handlers must animate or schedule state changes, since
44
+ they are built before playback. Capture prediction pauses as well as reveals.
45
+ - Include narration for an explainer unless the brief requests a silent video.
46
+ The sample disables narration only for an offline first preview; enable it and
47
+ prepare the requested lesson's speech as part of producing the video.
48
+ - Separate automated checks from observed visual and factual review. Record
49
+ evidence in `lesson/review.json`; a valid plan or passing lint does not establish
50
+ teaching quality. Keep approved audio files so visual revisions can reuse them.
51
+ - Use the installed version's declarations as the API authority. Run
52
+ `npx retiam context --json` to locate its guidance, examples, and declarations.
53
+ The local skill is supplied by the generator; the installed engine may be newer.
54
+ - Type-check with `npm run check`, then lint each changed scene with
55
+ `npx retiam lint src/scenes/YourScene.ts --json`. Lint is a heuristic check,
56
+ not proof of runtime or visual correctness. Review its warnings.
57
+ - Capture representative moments with
58
+ `npx retiam snapshot src/scenes/YourScene.ts --times 0,1,3 --json`.
59
+ Choose times from the actual scene, open the resulting PNGs, and fix clipping,
60
+ overlap, readability, and pacing before final rendering.
61
+ - Preview with `npm run dev`. Projects with Studio discover scenes automatically;
62
+ the standalone page uses the scene imported by `src/main.ts`.
63
+ - Render a requested video with `npx retiam render src/scenes/YourScene.ts`.
64
+ Check `config.js` or `retiam.config.js` and command help for output settings.
65
+ - Report the scene and output paths, checks performed, and any unavailable visual
66
+ or render checks. If WebGPU/Chrome or FFmpeg is missing, report that limitation;
67
+ a passing type check does not establish visual quality.
68
+
69
+ Use the project's package manager and lockfile. Keep generated outputs in
70
+ `outputs/`. Change Studio, build tooling, and dependencies when the task needs it.
@@ -0,0 +1,2 @@
1
+ Read and follow [AGENTS.md](AGENTS.md) for this Retiam project. The scene authoring
2
+ skill is [.agents/skills/retiam/SKILL.md](.agents/skills/retiam/SKILL.md).
@@ -0,0 +1,114 @@
1
+ ---
2
+ name: retiam
3
+ description: Create, direct, review, and render Retiam TypeScript animations and narrated explainers, with selectable visual styles and version-matched WebGPU APIs.
4
+ ---
5
+
6
+ # Retiam scene authoring
7
+
8
+ The default creative direction is a **3Blue1Brown-inspired mathematical
9
+ explainer**. Lead with visual intuition: ask a concrete question, construct an
10
+ example, reveal why it works, then connect the geometry to notation and a wider
11
+ principle. Use a clean dark stage, readable mathematics, a small purposeful
12
+ palette, continuous transformations, and carefully timed explanation. Honor an
13
+ explicitly different style or palette in the user's brief. Implement this with
14
+ Retiam's public TypeScript/WebGPU APIs and native libraries.
15
+
16
+ Start from the user's intended explanation or visual result. Read the existing
17
+ scene and identify its subject, audience, duration, aspect ratio, and style.
18
+ For a new animation, read [references/quality.md](references/quality.md) first.
19
+ Sketch the visible state change, the motion revealing it, and the attention target
20
+ for each beat. A narration paragraph with a title is not a visual beat. Scale this
21
+ planning to the task; a small edit does not need a new storyboard.
22
+
23
+ ## Find the API before authoring
24
+
25
+ Read [references/api.md](references/api.md) for scene lifecycle, units, styling,
26
+ camera framing, and common mistakes. Use
27
+ [examples/ExplainMotion.ts](examples/ExplainMotion.ts) only for a small API smoke
28
+ test. For a new explainer, study [examples/SecantToTangent.ts](examples/SecantToTangent.ts):
29
+ one diagram evolves through comparison, convergence, a mathematical transition,
30
+ and a moving generalization. Adapt its directing principles to the subject; do
31
+ not reuse its mathematics or visual layout for unrelated briefs. Copy an example
32
+ into `src/scenes/` to run it in Studio.
33
+
34
+ For an educational video, read [references/explainers.md](references/explainers.md).
35
+ Fresh projects include a lesson contract, native narration commands, cue timing,
36
+ and review evidence. The default style matches the Dijkstra reference: navy,
37
+ vivid stable colors, continuous drawing and transfers, conversational prediction
38
+ and discovery. For Excalidraw, minimal, cinematic or custom styling, read only the
39
+ relevant profile in [references/styles.md](references/styles.md). An explicit
40
+ user style overrides the saved profile; retain the teaching workflow.
41
+ `AreaLesson.ts` in the examples is a complete small construction using
42
+ `retiam/explainer`; adapt its mechanism to the actual subject.
43
+
44
+ Run `npx retiam context --json` to find documentation and TypeScript declarations
45
+ belonging to the engine actually executing this command. Follow exports and
46
+ constructor signatures there before using an unfamiliar feature. When an API
47
+ example differs from the installed version, follow the installed declarations
48
+ and release-specific setup requirements. Use the project brief and local directing
49
+ guide for creative decisions. Prefer public imports from `retiam` and `retiam/*`.
50
+
51
+ Choose Retiam's matching sublibrary before introducing an external implementation:
52
+ `retiam/tts` for generated narration and subtitles, `retiam/physics` for rigid-body
53
+ simulation, `retiam/charts` for charts, `retiam/graphs` for graphs and automata,
54
+ `retiam/shapes` for procedural shapes, `retiam/highlights` for attention effects,
55
+ and `retiam/rough` for sketch styling.
56
+
57
+ For voiceover, speech, or narration, read [references/tts.md](references/tts.md)
58
+ and [examples/NarratedMotion.ts](examples/NarratedMotion.ts) before authoring.
59
+ That example isolates speech integration; it is not a finished video's visual standard.
60
+ Use `retiam/tts` for synthesis, timing, subtitles, and the scene audio bus. Edge
61
+ is the default; when the user chooses ElevenLabs, initialize its native provider
62
+ with the caller's key directly, without Vite configuration. Direct the chosen
63
+ voice for natural questions, discoveries and resolution; keep delivery markup
64
+ out of captions and prepare audio before locking cues. The TTS reference covers
65
+ model-specific expression and prepared assets. Diagnose the selected provider's
66
+ actual failure instead of silently installing Python tools or switching providers.
67
+
68
+ ## Author for the requested result
69
+
70
+ Use a default-exported class extending `Scene` with `async run()` and add visible
71
+ entities to the scene. Await timeline operations; use Retiam animation groups
72
+ for intentional concurrency. Durations and waits use milliseconds; snapshot
73
+ timestamps use seconds. Animate with the engine timeline, not wall-clock timers.
74
+
75
+ Build the explanation around objects that change: paths being explored, a shape
76
+ being decomposed, a parameter altering a field, or a construction reaching its
77
+ limit. Keep object identity and color meaning through transformations. Put short
78
+ labels near the relationship they explain; use mathematical notation for formulas.
79
+ Use framing, emphasis, and deliberate pauses to direct attention. Use camera moves
80
+ when they reveal a useful scale or relationship. Follow the default direction in
81
+ `references/quality.md` while honoring the brief. The construction and its timing
82
+ carry the explanation; extra glow, constant zooming, and decorative effects do
83
+ not establish this style.
84
+
85
+ Unless the user asks for slides or a dashboard, avoid a persistent title/body
86
+ template, a large explanatory sidebar, page counters, and screen-wide fade-out /
87
+ fade-in chapter changes. A table can be useful when the changing entries are the
88
+ subject; show it when needed and keep the visual mechanism primary. Narration
89
+ should explain why the visible event matters, not replace missing visual action.
90
+
91
+ ## Close the feedback loop
92
+
93
+ 1. Run the project's type check (`npm run check`) and
94
+ `npx retiam lint src/scenes/YourScene.ts --json`. Fix errors and assess warnings.
95
+ The linter does not replace TypeScript, execution, or image review.
96
+ 2. Snapshot the beginning, a transition, the main explanatory moment, and the
97
+ resolved state using `npx retiam snapshot src/scenes/YourScene.ts --times ... --json`.
98
+ Use timestamps from the actual storyboard. Open the returned image paths.
99
+ 3. Fix composition issues visible in those images. Watch playback in Studio
100
+ (`npm run dev`) to assess motion and pacing; still images cannot establish those.
101
+ Review against the presentation-pattern checks in `references/quality.md`.
102
+ If the middle of a beat is essentially a static slide with narration, revise
103
+ the visual mechanism before calling the animation finished. Intentional reading
104
+ holds and the resolved final frame are useful exceptions.
105
+ 4. Render when requested with `npx retiam render src/scenes/YourScene.ts`.
106
+ Return artifact paths and the verification performed. State missing checks
107
+ explicitly when the environment cannot run them.
108
+
109
+ Snapshot requires headless Chrome with WebGPU; video rendering also requires
110
+ FFmpeg. `--json` returns one JSON result on stdout and diagnostics on stderr.
111
+ Snapshot runs the specified scene directly and advances its timeline at a fixed
112
+ step. Wall-clock time, unseeded randomness, live network data, and external media
113
+ can still make user scenes nondeterministic; use local assets and seeded data
114
+ when repeatable output matters.
@@ -0,0 +1,48 @@
1
+ import { Scene, CameraModel, CENTER, Rect, Text, Tex, CustomAnimation, Rate, hex } from 'retiam';
2
+ import { createExampleLesson, prepareLesson, resolveStyle, styleGeometry, drawGeometry, type LessonPlan, type PreparedSpeech } from 'retiam/explainer';
3
+ // lesson-config:start
4
+ const plan: LessonPlan = createExampleLesson();
5
+ const prepared: PreparedSpeech | undefined = undefined;
6
+ // lesson-config:end
7
+ /** One construction evolves through a prediction, growth, decomposition and notation. */
8
+ export default class AreaLesson extends Scene {
9
+ async run() {
10
+ const style = resolveStyle(plan.style);
11
+ this.clearColor(hex(style.background));
12
+ this.camera.model = CameraModel.ORTHOGRAPHIC;
13
+ this.camera.position.set([0, 0, 20]);
14
+ this.camera.orthoScale = 10;
15
+ const side = 2, factor = 2, total = factor * factor;
16
+ const cells = Array.from({ length: total }, (_, i) => {
17
+ const cell = styleGeometry(new Rect(side, side, { position: [-1 + (i % factor) * side, -0.8 + Math.floor(i / factor) * side, 0] }), style, i ? 'secondary' : 'primary', true);
18
+ if (i)
19
+ cell.setOpacity(0);
20
+ else
21
+ cell.setT2(0);
22
+ this.add(cell);
23
+ return cell;
24
+ });
25
+ const outline = styleGeometry(new Rect(side * factor, side * factor, { position: [0, 0.2, 0], opacity: 0 }), style, 'ink');
26
+ this.add(outline);
27
+ const question = new Text('What happens when we double a side?', { font: style.font, fontSize: 0.48, anchor: CENTER, fill: hex(style.ink), position: [0, 3.5, 0] });
28
+ const sideLabel = new Tex('s', { fontSize: 0.45, anchor: CENTER, fill: hex(style.primary), position: [-1, -2.25, 0] });
29
+ const doubled = new Tex(`${factor}s`, { fontSize: 0.45, anchor: CENTER, fill: hex(style.secondary), position: [0, -2.25, 0], opacity: 0 });
30
+ const equation = new Tex(`A=(${factor}s)^2=${total}s^2`, { fontSize: 0.65, anchor: CENTER, fill: hex(style.ink), position: [0, -3.25, 0], opacity: 0 });
31
+ const takeaway = new Text('Two growing directions, multiplied together.', { font: style.font, fontSize: 0.33, anchor: CENTER, fill: hex(style.muted), position: [0, -4.15, 0], opacity: 0 });
32
+ this.add(question, sideLabel, doubled, equation, takeaway);
33
+ const numbers = cells.map((cell, i) => { const p = cell.transform.position; const number = new Tex(String(i + 1), { fontSize: 0.48, anchor: CENTER, fill: hex(style.ink), position: [p.x, p.y, 0], opacity: 0 }); this.add(number); return number; });
34
+ const lesson = await prepareLesson(this, plan, { prepared });
35
+ await lesson.run({
36
+ predict: b => [drawGeometry(cells[0], style, b.cue('show'), Math.min(1800, b.durationMs - 500))],
37
+ grow: b => [new CustomAnimation(t => {
38
+ outline.setOpacity(t);
39
+ outline.transform.setScale([0.5 + 0.5 * t, 0.5 + 0.5 * t, 1]);
40
+ outline.transform.position.set([-1 + t, -0.8 + t, 0]);
41
+ sideLabel.setOpacity(1 - t);
42
+ doubled.setOpacity(t);
43
+ }, { delay: b.cue('expand'), duration: Math.min(2200, b.durationMs - 600), rate_func: Rate.smooth() })],
44
+ count: b => [new CustomAnimation(t => numbers[0].setOpacity(t), { delay: b.cue('reveal'), duration: 600 }), ...cells.slice(1).map((cell, i) => new CustomAnimation(t => { cell.setOpacity(t); numbers[i + 1].setOpacity(t); }, { delay: b.cue('reveal') + 300 + i * 850, duration: 800, rate_func: Rate.smooth() }))],
45
+ connect: b => [new CustomAnimation(t => { question.setOpacity(1 - t); equation.setOpacity(t); }, { delay: b.cue('equation'), duration: 1100 }), new CustomAnimation(t => takeaway.setOpacity(t), { delay: b.cue('equation') + 1600, duration: 900 })],
46
+ });
47
+ }
48
+ }
@@ -0,0 +1,32 @@
1
+ import {
2
+ CENTER, Circle, Concurrent, Create, CustomAnimation, Line, Scene, Text, Write, hex,
3
+ } from 'retiam';
4
+
5
+ /** A 7-second example: set the stage, move a marker, then hold the result. */
6
+ export default class ExplainMotion extends Scene {
7
+ async run() {
8
+ this.clearColor(hex('#080C14'));
9
+ this.camera.position.set([0, 0, 14]);
10
+
11
+ const title = new Text('Position changes over time', {
12
+ fontSize: 0.6, anchor: CENTER, fill: hex('#F8FAFC'), position: [0, 2, 0],
13
+ });
14
+ const path = new Line([[-3, 0, 0], [3, 0, 0]], {
15
+ stroke: hex('#64748B'), strokeWidth: 0.04,
16
+ });
17
+ const marker = new Circle(0.18, { fill: hex('#38BDF8'), position: [-3, 0, 0] });
18
+ this.add(title, path, marker);
19
+ // Include the whole path so the moving marker stays within the shot.
20
+ this.camera.frame([title, path, marker], { padding: 0.3 });
21
+
22
+ await this.play(new Concurrent([
23
+ new Write(title, { duration: 1000 }),
24
+ new Create(path, { duration: 1000 }),
25
+ ]));
26
+ await this.wait(500);
27
+ await this.play(new CustomAnimation(progress => {
28
+ marker.transform.position.set([-3 + 6 * progress, 0, 0]);
29
+ }, { duration: 3500 }));
30
+ await this.wait(2000);
31
+ }
32
+ }
@@ -0,0 +1,50 @@
1
+ import {
2
+ CENTER, Circle, Concurrent, Create, CustomAnimation, Line, Scene, Text, Write, hex,
3
+ } from 'retiam';
4
+ import { ElevenLabsTTSProvider, Speak, Subtitles, TTS } from 'retiam/tts';
5
+
6
+ // Optional application setup before starting the scene. Credentials come from
7
+ // the caller; browser-supplied keys are visible in that browser. No Vite changes.
8
+ export async function initializeElevenLabs(apiKey: string, voiceId: string) {
9
+ const provider = new ElevenLabsTTSProvider();
10
+ await provider.init({ apiKey, voiceId, modelId: 'eleven_v3', voiceSettings: { stability: 0.5 } });
11
+ TTS.setProvider(provider);
12
+ }
13
+
14
+ export default class NarratedMotion extends Scene {
15
+ async run() {
16
+ this.clearColor(hex('#080C14'));
17
+ this.camera.position.set([0, 0, 14]);
18
+ const title = new Text('Follow the motion', {
19
+ fontSize: 0.6, anchor: CENTER, fill: hex('#F8FAFC'), position: [0, 2, 0],
20
+ });
21
+ const path = new Line([[-3, 0, 0], [3, 0, 0]], {
22
+ stroke: hex('#64748B'), strokeWidth: 0.04,
23
+ });
24
+ const marker = new Circle(0.18, { fill: hex('#38BDF8'), position: [-3, 0, 0] });
25
+ const subtitles = new Subtitles({ fontSize: 0.3, bottomOffset: -1.7, maxWidth: 8 });
26
+ this.add(title, path, marker, subtitles);
27
+ this.camera.frame([[-4.5, -2.5, 0], [4.5, 3, 0]], { padding: 0.25 });
28
+
29
+ const narration = new Speak('Watch the marker move from left to right.', {
30
+ subtitles, entity: marker,
31
+ pulseAmplitude: 0, volume: 1,
32
+ });
33
+ // Headless tools must wait for synthesis, not a promise requiring timeline steps.
34
+ const loading = narration.prepareAudio();
35
+ this.composer?.pendingTasks.push(loading);
36
+ await loading;
37
+
38
+ await this.play(new Concurrent([
39
+ new Write(title, { duration: 800 }),
40
+ new Create(path, { duration: 800 }),
41
+ ], { duration: 800 }));
42
+ await this.play(new Concurrent([
43
+ narration,
44
+ new CustomAnimation(progress => {
45
+ marker.transform.position.set([-3 + 6 * progress, 0, 0]);
46
+ }, { duration: narration.duration }),
47
+ ], { duration: narration.duration }));
48
+ await this.wait(800);
49
+ }
50
+ }
@@ -0,0 +1,142 @@
1
+ import {
2
+ CameraModel, CENTER, Circle, Concurrent, Create, CustomAnimation, FadeIn, FadeOut,
3
+ Line, Rate, Scene, SDFText, Tex, Text, TransformMatchingTex, Write, hex,
4
+ } from 'retiam';
5
+
6
+ /** 21.5 seconds of one evolving construction; see references/quality.md for its beat map. */
7
+ export default class SecantToTangent extends Scene {
8
+ async run() {
9
+ const cyan = hex('#5DD9F5');
10
+ const amber = hex('#FFCB77');
11
+ const ink = hex('#EDF5FF');
12
+ const muted = hex('#8497AD');
13
+ this.clearColor(hex('#07101B'));
14
+ this.camera.model = CameraModel.ORTHOGRAPHIC;
15
+ this.camera.position.set([0, 0, 20]);
16
+ this.camera.orthoScale = 10.8;
17
+
18
+ const point = (x: number, y = x * x): [number, number, number] => [-2 + x * 1.2, -3 + y * 1.2, 0];
19
+ const curve = new Line(Array.from({ length: 181 }, (_, i) => point(-1.8 + i * 4 / 180)), {
20
+ stroke: cyan, strokeWidth: 0.055,
21
+ });
22
+ const axes = new Line([point(-2, 0), point(2.45, 0)], { stroke: muted, strokeWidth: 0.018 });
23
+ const vertical = new Line([point(0, -0.3), point(0, 4.9)], { stroke: muted, strokeWidth: 0.018 });
24
+ this.add(axes, vertical, curve);
25
+ for (const y of [1, 2, 3, 4]) {
26
+ const guide = new Line([point(-1.8, y), point(2.3, y)], {
27
+ stroke: hex('#26364A'), strokeWidth: 0.009, opacity: 0.55,
28
+ });
29
+ this.add(guide);
30
+ }
31
+
32
+ const question = new Text('How can one point have a slope?', {
33
+ fontSize: 0.56, anchor: CENTER, fill: ink, position: [0, 4.15, 0],
34
+ });
35
+ const functionLabel = new Tex('f(x)=x^2', { fontSize: 0.48, anchor: CENTER, fill: cyan, position: [-4.1, 2.2, 0] });
36
+ const p = new Circle(0.095, { fill: cyan });
37
+ const q = new Circle(0.095, { fill: amber });
38
+ const pLabel = new Text('P', { fontSize: 0.28, anchor: CENTER, fill: cyan });
39
+ const qLabel = new Text('Q', { fontSize: 0.28, anchor: CENTER, fill: amber });
40
+ const secant = new Line([point(0), point(1)], { stroke: amber, strokeWidth: 0.036 });
41
+ const dx = new Line([point(0), point(1)], { stroke: amber, strokeWidth: 0.025 });
42
+ const dy = new Line([point(0), point(1)], { stroke: cyan, strokeWidth: 0.025 });
43
+ const dxLabel = new Tex('\\Delta x=h', { fontSize: 0.27, anchor: CENTER, fill: amber });
44
+ const dyLabel = new Tex('\\Delta y', { fontSize: 0.27, anchor: CENTER, fill: cyan });
45
+ const formula = new Tex('m(a,h)=2a+h', {
46
+ fontSize: 0.65, anchor: CENTER, fill: ink, position: [4, 1.5, 0],
47
+ });
48
+ const readoutTitle = new SDFText('gap between the points', {
49
+ fontSize: 0.27, anchor: CENTER, fill: muted, position: [4, -0.35, 0],
50
+ });
51
+ const readout = new SDFText('1.40', {
52
+ fontSize: 0.85, anchor: CENTER, fill: amber, position: [4, -1.2, 0],
53
+ });
54
+ this.add(question, functionLabel, secant, dx, dy, p, q, pLabel, qLabel, dxLabel, dyLabel, formula, readoutTitle, readout);
55
+
56
+ const a = 0.6;
57
+ const setGap = (h: number) => {
58
+ const b = a + h;
59
+ const from = point(a);
60
+ const to = point(b);
61
+ const slope = a + b;
62
+ p.transform.position.set(from);
63
+ q.transform.position.set(to);
64
+ pLabel.transform.position.set([from[0] - 0.26, from[1] + 0.23, 0]);
65
+ qLabel.transform.position.set([to[0] + 0.25, to[1] + 0.23, 0]);
66
+ secant.setVertices([point(a - 0.55, a * a - slope * 0.55), point(b + 0.32, b * b + slope * 0.32)]);
67
+ dx.setVertices([from, point(b, a * a)]);
68
+ dy.setVertices([point(b, a * a), to]);
69
+ dxLabel.transform.position.set([(from[0] + to[0]) / 2, from[1] - 0.36, 0]);
70
+ dyLabel.transform.position.set([to[0] + 0.42, (from[1] + to[1]) / 2, 0]);
71
+ readout.setText(h.toFixed(2));
72
+ };
73
+ setGap(1.4);
74
+
75
+ // 0–3.1 s: establish the curve, then reveal the two-point measurement.
76
+ for (const entity of [p, q, pLabel, qLabel, secant, dx, dy, dxLabel, dyLabel, formula, readoutTitle, readout]) entity.setOpacity(0);
77
+ await this.play(new Concurrent([
78
+ new Create(curve), new Write(question), new Write(functionLabel),
79
+ ], { duration: 1400 }));
80
+ for (const entity of [p, q, pLabel, qLabel, secant, dx, dy, dxLabel, dyLabel, formula, readoutTitle, readout]) entity.setOpacity(1);
81
+ await this.play(new Concurrent([
82
+ new FadeIn(p), new FadeIn(q), new FadeIn(pLabel), new FadeIn(qLabel),
83
+ new Create(secant), new Create(dx), new Create(dy), new Write(formula),
84
+ new FadeIn(dxLabel), new FadeIn(dyLabel), new FadeIn(readoutTitle), new FadeIn(readout),
85
+ ], { duration: 900 }));
86
+ await this.wait(800);
87
+
88
+ // 3.1–6.3 s: the point follows the function; the line and its legs follow the data.
89
+ await this.play(new CustomAnimation(t => setGap(1.4 - 0.85 * t), {
90
+ duration: 3200, rate_func: Rate.smooth(),
91
+ }));
92
+
93
+ // 6.3–11.1 s: remove spent context, move closer, then expose the limit.
94
+ await this.play(new Concurrent([
95
+ new FadeOut(question), new FadeOut(functionLabel), new FadeOut(pLabel), new FadeOut(qLabel),
96
+ new CustomAnimation(t => {
97
+ this.camera.position.set([-0.55 * t, -0.55 * t, 20]);
98
+ this.camera.orthoScale = 10.8 - 1.6 * t;
99
+ }),
100
+ ], { duration: 800 }));
101
+ await this.play(new CustomAnimation(t => {
102
+ const h = 0.55 - 0.515 * t;
103
+ setGap(h);
104
+ for (const entity of [dx, dy, dxLabel, dyLabel]) entity.setOpacity(1 - t);
105
+ q.setOpacity(1 - 0.8 * t);
106
+ }, { duration: 3400, rate_func: Rate.smooth() }));
107
+ await this.wait(600);
108
+
109
+ // 11.1–13.1 s: preserve the construction while the secant resolves to a tangent.
110
+ const tangent = new Line([point(a - 0.78, a * a - 2 * a * 0.78), point(a + 0.78, a * a + 2 * a * 0.78)], {
111
+ stroke: amber, strokeWidth: 0.065,
112
+ });
113
+ this.add(tangent);
114
+ const derivative = new Tex("f'(a)=2a", { fontSize: 0.85, anchor: CENTER, fill: amber });
115
+ derivative.transform.position.set(formula.transform.position);
116
+ await this.play(new Concurrent([
117
+ new Create(tangent), new FadeOut(secant), new FadeOut(q),
118
+ new FadeOut(readoutTitle), new FadeOut(readout),
119
+ new CustomAnimation(t => setGap(0.035 * (1 - t))),
120
+ new TransformMatchingTex(formula, derivative, { morphUnmatched: false, keyMap: { m: 'f' } }),
121
+ ], { duration: 1200 }));
122
+ this.remove(secant, q, pLabel, qLabel, dx, dy, dxLabel, dyLabel, functionLabel);
123
+ readoutTitle.setText('instantaneous slope').setOpacity(1);
124
+ readout.setText('1.20').setOpacity(1);
125
+ await this.play(new Concurrent([new FadeIn(readoutTitle), new FadeIn(readout)], { duration: 800 }));
126
+
127
+ const setTangent = (x: number) => {
128
+ p.transform.position.set(point(x));
129
+ tangent.setVertices([point(x - 0.78, x * x - 2 * x * 0.78), point(x + 0.78, x * x + 2 * x * 0.78)]);
130
+ readout.setText((2 * x).toFixed(2));
131
+ };
132
+ // 13.1–19.5 s: generalize across negative, zero, and positive slopes.
133
+ await this.play(new CustomAnimation(t => {
134
+ setTangent(0.6 - 1.75 * t);
135
+ this.camera.position.set([-0.55 * (1 - t), -0.55 * (1 - t), 20]);
136
+ this.camera.orthoScale = 9.2 + 1.6 * t;
137
+ }, { duration: 2300, rate_func: Rate.smooth() }));
138
+ await this.play(new CustomAnimation(t => setTangent(-1.15 + 2.65 * t), { duration: 3200, rate_func: Rate.smooth() }));
139
+ await this.play(new CustomAnimation(t => setTangent(1.5 - 0.4 * t), { duration: 900, rate_func: Rate.smooth() }));
140
+ await this.wait(2000);
141
+ }
142
+ }
@@ -0,0 +1,111 @@
1
+ # Retiam API notes
2
+
3
+ These notes accompany the package release. The installed `.d.ts` files are the
4
+ authority for exact signatures. `npx retiam context --json` locates them without
5
+ requiring an engine source checkout or online documentation.
6
+
7
+ ## Scene lifecycle and timing
8
+
9
+ ```ts
10
+ import { Scene, Circle, Create, hex } from 'retiam';
11
+
12
+ export default class MyScene extends Scene {
13
+ async run() {
14
+ this.clearColor(hex('#080C14'));
15
+ this.camera.position.set([0, 0, 14]);
16
+ const circle = new Circle(1, { stroke: hex('#38BDF8'), strokeWidth: 0.05 });
17
+ this.add(circle);
18
+ this.camera.frame(circle, { padding: 0.25 });
19
+ await this.play(new Create(circle, { duration: 1000 }));
20
+ await this.wait(800);
21
+ }
22
+ }
23
+ ```
24
+
25
+ `run()` schedules the animation. `scene.start(canvas)` mounts a standalone scene;
26
+ Studio and the snapshot CLI mount it themselves. Do not bootstrap another canvas
27
+ inside `run()`. Scene files default-export the class, not an instance.
28
+
29
+ `this.play(...)`, `this.wait(...)`, animation `duration`, and render CLI `-d` are
30
+ in **milliseconds**. Snapshot `--times` is in **seconds**.
31
+ For parallel animations, await `this.play(new Concurrent([animationA, animationB]))`.
32
+ Use `Sequential` or chained awaits for successive actions. `CustomAnimation`
33
+ accepts `(progress) => { ... }` and `{ duration, rate_func }`; progress is eased
34
+ from 0 to 1. `scene.always` callbacks receive `(time, dt)`, both in milliseconds.
35
+ Prefer absolute values derived from progress over accumulating per-frame offsets.
36
+
37
+ ## Geometry, style, and layout
38
+
39
+ - Coordinates, radii, `strokeWidth`, and `fontSize` use world units, not CSS pixels.
40
+ The visible range depends on projection, camera, and aspect ratio. Explicitly
41
+ set text size and frame the content; text defaults may be much larger than a
42
+ small mathematical scene needs.
43
+ - `new Circle(radius, options)`, `new Rect(width, height, options)`,
44
+ `new Line([[x1, y1, z1], [x2, y2, z2]], options)`.
45
+ - `new Dot(position?, { radius, ...options })`; the first argument is a position.
46
+ - `new Text('Label', { fontSize: 0.5, anchor: CENTER, fill: hex('#F8FAFC') })`
47
+ creates vector text. `SDFText` is another text implementation. Use `Tex` for
48
+ LaTeX and inspect `TexOptions` for mathematical text configuration.
49
+ - Entity options use **`strokeWidth`** and **`zIndex`**, not `stroke_width` or
50
+ `z_index`. Use `fill`, `stroke`, and `opacity` for 2D paint. Use material and
51
+ shading APIs for 3D objects.
52
+ - Prefer automatic scene ordering for flat diagrams. Explicit `zIndex` values
53
+ affect GPU depth; inspect the output rather than assuming CSS-style layer
54
+ numbers will work with every projection.
55
+ - Position with `entity.transform.position.set([x, y, z])`; use `nextTo`, `alignTo`,
56
+ `VBox`, or `HBox` for related objects. Add objects or their parent group to the
57
+ scene. Layout alone is not scene registration.
58
+ - `this.camera.frame(entityOrArray, { padding: 0.25 })` fits content to the viewport.
59
+ Frame the planned motion envelope as well as the starting geometry. Camera
60
+ projection uses `camera.model` and `CameraModel`, not `projectionType` strings.
61
+ - The root package exports `Box`, not `Box3D`. Check constructor options for mesh
62
+ resolution; do not assume signatures from Three.js or Python Manim.
63
+
64
+ ## Import map
65
+
66
+ | Import | Use |
67
+ | --- | --- |
68
+ | `retiam` | Scene, entities, text, cameras, animation, layout, materials, math |
69
+ | `retiam/shapes` | Additional procedural 2D/3D shapes |
70
+ | `retiam/highlights` | Circumscribe, Underline, and attention animations |
71
+ | `retiam/charts` | Chart entities |
72
+ | `retiam/graphs` | Graph and automata entities |
73
+ | `retiam/physics` | Physics worlds, bodies, joints, backend adapters |
74
+ | `retiam/tts` | Speech, narration, and subtitles |
75
+ | `retiam/tts/node` | Renderer-free speech synthesis for Node preparation scripts |
76
+ | `retiam/rough` | Rough styling utilities |
77
+ | `retiam/vite` | Node-side Vite middleware for Retiam TTS; import in Vite config |
78
+
79
+ Follow the installed sublibrary's index declarations to its options and examples
80
+ before using it. Optional features can have their own asset or environment needs.
81
+
82
+ For narration, follow [tts.md](tts.md) and the checked
83
+ [NarratedMotion.ts](../examples/NarratedMotion.ts) example. ElevenLabs initializes
84
+ directly with a key on `ElevenLabsTTSProvider`, without Vite changes. The Edge TTS
85
+ route is already enabled by new project Vite configurations. Existing projects can add
86
+ `msEdgeTTSPlugin` from `retiam/vite`; see the TTS guide for the configuration and
87
+ failure checks. Never import the Vite plugin in browser scene code.
88
+
89
+ ## Commands and failure interpretation
90
+
91
+ ```bash
92
+ npm run check
93
+ npx retiam lint src/scenes/YourScene.ts --json
94
+ npx retiam snapshot src/scenes/YourScene.ts --times 0,1.5,3 --json
95
+ npx retiam render src/scenes/YourScene.ts -w 1920 -h 1080 -f 60 -o outputs/video
96
+ ```
97
+
98
+ Lint exits nonzero on errors; warnings need review even when `valid` is true.
99
+ It checks common AST patterns, not complete program semantics. A successful
100
+ snapshot contains the requested scene path and absolute PNG paths. Failure
101
+ returns `status: "error"` and a nonzero exit code. Keep stdout as JSON when
102
+ automating; stderr may contain browser or build diagnostics.
103
+
104
+ Render `-o` is an **output directory**, not a video filename. Check `--help` for
105
+ supported options; snapshot produces PNGs and has no `--format` option. Render
106
+ reads `config.js`, with `retiam.config.js` taking precedence; CLI flags override
107
+ file settings. Snapshot dimensions are controlled by its own width/height flags.
108
+
109
+ Studio discovers default-exported scenes under `src/scenes/`; reload after adding
110
+ a file. The standalone page at `/` follows `src/main.ts` imports. Snapshot mounts
111
+ the named scene directly, independent of either entrypoint.