@a-company/atelier 0.36.0 → 0.38.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. package/dist/{chunk-JPZ4F4PW.js → chunk-3ARBOSWY.js} +64 -5
  2. package/dist/chunk-3ARBOSWY.js.map +1 -0
  3. package/dist/cli.js +11469 -413
  4. package/dist/cli.js.map +1 -1
  5. package/dist/{dist-M67UZGFQ.js → dist-3YQK6PI6.js} +2 -2
  6. package/dist/index.cjs +3193 -227
  7. package/dist/index.cjs.map +1 -1
  8. package/dist/index.d.cts +701 -8
  9. package/dist/index.d.ts +701 -8
  10. package/dist/index.js +7237 -72
  11. package/dist/index.js.map +1 -1
  12. package/dist/mcp.js +2898 -507
  13. package/dist/mcp.js.map +1 -1
  14. package/package.json +14 -9
  15. package/src/web/inline-app.ts +55 -4
  16. package/src/web/timeline-state-types.ts +28 -0
  17. package/src/web/timeline-view.test.ts +99 -0
  18. package/src/web/timeline-view.ts +339 -0
  19. package/src/web/workspace-app.ts +3146 -0
  20. package/templates/workspace/.claude/agents/atelier-iris.md +75 -0
  21. package/templates/workspace/.claude/agents/atelier-lux.md +67 -0
  22. package/templates/workspace/.claude/agents/atelier-quill.md +61 -0
  23. package/templates/workspace/.gitignore +30 -0
  24. package/templates/workspace/.paradigm/personas/_shared/cascade-merge.md +172 -0
  25. package/templates/workspace/CLAUDE.md +93 -0
  26. package/templates/workspace/README.md +75 -0
  27. package/templates/workspace/SETUP.md +127 -0
  28. package/templates/workspace/_brand/.atelier-brand.yaml +34 -0
  29. package/templates/workspace/_brand/DESIGN.md +56 -0
  30. package/templates/workspace/_brand/SCRIPT.md +41 -0
  31. package/templates/workspace/_brand/STORYBOARD.md +33 -0
  32. package/templates/workspace/_packs/README.md +54 -0
  33. package/templates/workspace/projects/README.md +49 -0
  34. package/templates/workspace/workspace.atelier +22 -0
  35. package/university/content/notes/N-atel-001-first-render.md +114 -0
  36. package/university/content/notes/N-atel-001-install-and-launch.md +84 -0
  37. package/university/content/notes/N-atel-001-what-is-atelier.md +51 -0
  38. package/university/content/notes/N-atel-101-easings.md +97 -0
  39. package/university/content/notes/N-atel-101-layers.md +106 -0
  40. package/university/content/notes/N-atel-101-states-and-deltas.md +94 -0
  41. package/university/content/notes/N-atel-101-the-atelier-format.md +72 -0
  42. package/university/content/notes/N-atel-201-authoring-tools.md +141 -0
  43. package/university/content/notes/N-atel-201-mcp-overview.md +86 -0
  44. package/university/content/notes/N-atel-201-patterns.md +108 -0
  45. package/university/content/notes/N-atel-201-visual-and-effects.md +125 -0
  46. package/university/content/notes/N-atel-301-composition-and-overlays.md +141 -0
  47. package/university/content/notes/N-atel-301-effects.md +136 -0
  48. package/university/content/notes/N-atel-301-images-and-video.md +126 -0
  49. package/university/content/notes/N-atel-301-shapes-and-text.md +118 -0
  50. package/university/content/notes/N-atel-401-hierarchical-states.md +71 -0
  51. package/university/content/notes/N-atel-401-motion-deep-dive.md +106 -0
  52. package/university/content/notes/N-atel-401-presets-and-templates.md +98 -0
  53. package/university/content/notes/N-atel-401-transitions.md +94 -0
  54. package/university/content/notes/N-atel-501-detected-vs-user-edited.md +76 -0
  55. package/university/content/notes/N-atel-501-layer-tag-isolation.md +62 -0
  56. package/university/content/notes/N-atel-501-silence-trim.md +98 -0
  57. package/university/content/notes/N-atel-501-transcribe-and-captions.md +98 -0
  58. package/university/content/notes/N-atel-601-carousel.md +71 -0
  59. package/university/content/notes/N-atel-601-overlay-rules.md +96 -0
  60. package/university/content/notes/N-atel-601-recipe-tools-and-apply.md +84 -0
  61. package/university/content/notes/N-atel-601-studio-recipe.md +103 -0
  62. package/university/content/notes/N-atel-701-choosing-output.md +68 -0
  63. package/university/content/notes/N-atel-701-png-and-frames.md +84 -0
  64. package/university/content/notes/N-atel-701-vector.md +85 -0
  65. package/university/content/notes/N-atel-701-video.md +88 -0
  66. package/university/content/notes/N-atel-801-editing-surface.md +69 -0
  67. package/university/content/notes/N-atel-801-live-bridge.md +84 -0
  68. package/university/content/notes/N-atel-801-studio-app.md +72 -0
  69. package/university/content/notes/N-atel-801-symbiotic-loop.md +56 -0
  70. package/university/content/paths/LP-atel-001.yaml +21 -0
  71. package/university/content/paths/LP-atel-101.yaml +22 -0
  72. package/university/content/paths/LP-atel-201.yaml +23 -0
  73. package/university/content/paths/LP-atel-301.yaml +22 -0
  74. package/university/content/paths/LP-atel-401.yaml +22 -0
  75. package/university/content/paths/LP-atel-501.yaml +22 -0
  76. package/university/content/paths/LP-atel-601.yaml +22 -0
  77. package/university/content/paths/LP-atel-701.yaml +22 -0
  78. package/university/content/paths/LP-atel-801.yaml +22 -0
  79. package/university/content/quizzes/Q-atel-001-orientation.yaml +66 -0
  80. package/university/content/quizzes/Q-atel-101-document-model.yaml +66 -0
  81. package/university/content/quizzes/Q-atel-201-mcp-authoring.yaml +66 -0
  82. package/university/content/quizzes/Q-atel-301-visual-system.yaml +66 -0
  83. package/university/content/quizzes/Q-atel-401-state-machines.yaml +66 -0
  84. package/university/content/quizzes/Q-atel-501-video-pipeline.yaml +66 -0
  85. package/university/content/quizzes/Q-atel-601-recipes.yaml +66 -0
  86. package/university/content/quizzes/Q-atel-701-export.yaml +66 -0
  87. package/university/content/quizzes/Q-atel-801-studio-loop.yaml +66 -0
  88. package/university/index.yaml +720 -0
  89. package/university/pack.yaml +21 -0
  90. package/dist/chunk-5QQESXI6.js +0 -4432
  91. package/dist/chunk-5QQESXI6.js.map +0 -1
  92. package/dist/chunk-JPZ4F4PW.js.map +0 -1
  93. package/dist/cli.cjs +0 -6313
  94. package/dist/cli.cjs.map +0 -1
  95. package/dist/cli.d.cts +0 -1
  96. package/dist/cli.d.ts +0 -1
  97. package/dist/mcp.cjs +0 -5462
  98. package/dist/mcp.cjs.map +0 -1
  99. /package/dist/{dist-M67UZGFQ.js.map → dist-3YQK6PI6.js.map} +0 -0
@@ -0,0 +1,22 @@
1
+ id: LP-atel-401
2
+ title: 'ATEL-401: State Machines & Motion'
3
+ description: 'From states-as-data to interactive, physically-tuned, reusable motion. Hierarchical states and their merge rule, transitions and the interaction layer, the motion deep dive (springs, expression-valued deltas, motion paths), and the preset-vs-template distinction. After this track you can build and package interactive UI motion in Atelier. ~27 minutes.'
4
+ author: atelier
5
+ created: '2026-05-20'
6
+ updated: '2026-05-20'
7
+ tags:
8
+ - course
9
+ - atel-401
10
+ ordered: true
11
+ steps:
12
+ - content: N-atel-401-hierarchical-states
13
+ required: true
14
+ - content: N-atel-401-transitions
15
+ required: true
16
+ - content: N-atel-401-motion-deep-dive
17
+ required: true
18
+ - content: N-atel-401-presets-and-templates
19
+ required: true
20
+ - content: Q-atel-401-state-machines
21
+ required: true
22
+ passRequired: true
@@ -0,0 +1,22 @@
1
+ id: LP-atel-501
2
+ title: 'ATEL-501: Video Project Pipeline'
3
+ description: 'The video pipeline — silence trimming with parametric cuts, Whisper transcription, the transcript edit family, and caption generation. Two aspects make the loop safe: detections are immutable while your overrides survive every re-run, and each pipeline touches only its own tagged layers. After this track you can drive a VideoProject end to end and re-run any stage without losing human work. ~24 minutes.'
4
+ author: atelier
5
+ created: '2026-05-20'
6
+ updated: '2026-05-20'
7
+ tags:
8
+ - course
9
+ - atel-501
10
+ ordered: true
11
+ steps:
12
+ - content: N-atel-501-silence-trim
13
+ required: true
14
+ - content: N-atel-501-transcribe-and-captions
15
+ required: true
16
+ - content: N-atel-501-detected-vs-user-edited
17
+ required: true
18
+ - content: N-atel-501-layer-tag-isolation
19
+ required: true
20
+ - content: Q-atel-501-video-pipeline
21
+ required: true
22
+ passRequired: true
@@ -0,0 +1,22 @@
1
+ id: LP-atel-601
2
+ title: 'ATEL-601: Recipes, Overlays & Carousel'
3
+ description: 'Style as a reusable preset — the Studio Recipe, anchored handle and page-number overlays, the CLI/MCP tooling and precedence, and the carousel batch driver that turns a folder of images into a numbered, branded post deck. After this track you can author a recipe, apply it through the CLI and MCP, and batch-compose a carousel. ~22 minutes.'
4
+ author: atelier
5
+ created: '2026-05-20'
6
+ updated: '2026-05-20'
7
+ tags:
8
+ - course
9
+ - atel-601
10
+ ordered: true
11
+ steps:
12
+ - content: N-atel-601-studio-recipe
13
+ required: true
14
+ - content: N-atel-601-overlay-rules
15
+ required: true
16
+ - content: N-atel-601-recipe-tools-and-apply
17
+ required: true
18
+ - content: N-atel-601-carousel
19
+ required: true
20
+ - content: Q-atel-601-recipes
21
+ required: true
22
+ passRequired: true
@@ -0,0 +1,22 @@
1
+ id: LP-atel-701
2
+ title: 'ATEL-701: Export & Delivery'
3
+ description: Getting work out of Atelier — single-frame raster (PNG), vector (SVG and Lottie), and video (MP4/GIF via FFmpeg, WebM via the studio), then a decision guide that picks a format by destination. After this track you can choose the right exporter for any target and know which paths need extra dependencies. ~25 minutes.
4
+ author: atelier
5
+ created: '2026-05-20'
6
+ updated: '2026-05-20'
7
+ tags:
8
+ - course
9
+ - atel-701
10
+ ordered: true
11
+ steps:
12
+ - content: N-atel-701-png-and-frames
13
+ required: true
14
+ - content: N-atel-701-vector
15
+ required: true
16
+ - content: N-atel-701-video
17
+ required: true
18
+ - content: N-atel-701-choosing-output
19
+ required: true
20
+ - content: Q-atel-701-export
21
+ required: true
22
+ passRequired: true
@@ -0,0 +1,22 @@
1
+ id: LP-atel-801
2
+ title: 'ATEL-801: Studio & the Live Agent Loop'
3
+ description: 'The live editing loop — `atelier studio` as a local-first browser editor, the editing surface (layer-type picker, image drop, slider commit-gating), the WebSocket bridge + MCP-over-WS transport that lets an agent mutate the open document in real time, and the symbiotic pattern where the human and agent co-edit the same single-store doc. After this track you understand how Atelier turns a chat conversation into a live, undoable canvas. ~22 minutes.'
4
+ author: atelier
5
+ created: '2026-05-20'
6
+ updated: '2026-05-20'
7
+ tags:
8
+ - course
9
+ - atel-801
10
+ ordered: true
11
+ steps:
12
+ - content: N-atel-801-studio-app
13
+ required: true
14
+ - content: N-atel-801-editing-surface
15
+ required: true
16
+ - content: N-atel-801-live-bridge
17
+ required: true
18
+ - content: N-atel-801-symbiotic-loop
19
+ required: true
20
+ - content: Q-atel-801-studio-loop
21
+ required: true
22
+ passRequired: true
@@ -0,0 +1,66 @@
1
+ id: Q-atel-001-orientation
2
+ title: 'ATEL-001: Hello Atelier — Orientation'
3
+ description: Verify the foundations from ATEL-001 — what Atelier is, how the three commands relate, and the shape of a minimal document.
4
+ author: atelier
5
+ created: '2026-05-18'
6
+ updated: '2026-05-18'
7
+ tags:
8
+ - course
9
+ - atel-001
10
+ difficulty: beginner
11
+ passThreshold: 0.7
12
+ questions:
13
+ - id: q1
14
+ question: Which of the following best describes what makes Atelier "AI-native"?
15
+ choices:
16
+ A: It uses an LLM at render time to generate frames procedurally
17
+ B: It ships with a chat interface bolted onto a traditional editor
18
+ C: Its document format and 52 MCP tools are designed for an agent to author directly, with typed mutations and schema-validated errors
19
+ D: It runs only when an AI assistant is connected, refusing programmatic CLI use
20
+ correct: C
21
+ explanation: Atelier is AI-native because the document format and the MCP tool surface were designed for an agent to author. Mutations go through typed tools (atelier_add_layer, atelier_add_state, etc.) that validate against a Zod schema with readable errors. The engine itself is deterministic — no LLM is involved at render time, and the CLI and Builder API are first-class for programmatic use.
22
+ - id: q2
23
+ question: After running `npm i -g @a-company/atelier`, which three commands are on your PATH and what is each one's primary role?
24
+ choices:
25
+ A: '`atelier` (CLI), `atelier-mcp` (MCP server for AI agents), `atelier studio` (local browser editor) — all share one engine and document store'
26
+ B: '`atelier` (only CLI) — the MCP server and studio require separate npm packages'
27
+ C: '`atelier` (CLI), `atelier-cloud` (sync to SaaS), `atelier-render` (rendering daemon)'
28
+ D: '`atelier-cli`, `atelier-server`, `atelier-gui` — three independent binaries that do not share state'
29
+ correct: A
30
+ explanation: One install provides three executables that share `@atelier/core`. The CLI is for scripts and inspection, the MCP server is what an AI agent connects to, and `atelier studio` boots a local browser editor. They all read and write the same `.atelier` files and, with the live bridge enabled, observe each other's mutations.
31
+ - id: q3
32
+ question: In a minimal `.atelier` document, what is the relationship between a layer, a state, and a delta?
33
+ choices:
34
+ A: 'A layer is a keyframe; a state is a property; a delta is a layer parented to another layer'
35
+ B: 'A layer describes what exists; a state names a duration; a delta interpolates one property of one layer over a frame range with an easing'
36
+ C: 'A layer is an export format; a state is a render preset; a delta is a diff between two documents'
37
+ D: 'They are interchangeable names for the same concept — pick whichever reads best'
38
+ correct: B
39
+ explanation: Layers describe what exists in the scene (shape/text/image/video). States are named keyframes with a duration in frames. Deltas animate one property of one layer from a `from` value to a `to` value across `startFrame`–`endFrame` with an easing. Everything else in Atelier — easings, presets, templates, interactions, refs — extends these three primitives.
40
+ - id: q4
41
+ question: 'The "detected-vs-user-edited" convention in Atelier is a design pattern that solves which problem?'
42
+ choices:
43
+ A: 'How to compress YAML files so they download faster'
44
+ B: 'How to let an AI agent and a human edit the same document repeatedly without one erasing the other'
45
+ C: 'How to detect whether a user is logged in to Atelier Cloud'
46
+ D: 'How to choose between Canvas 2D and WebGL at render time'
47
+ correct: B
48
+ explanation: 'When an Atelier pipeline (silence-trim, captions, agent mutations) generates layers, those layers carry a tag identifying their source. User edits set `userEdited` / `userAdded` / `hidden` flags. When the pipeline re-runs (re-transcribe, re-detect silence, re-propose layout), it matches existing entries by timing tolerance and preserves the user''s edits. The same idea generalizes to any source — human, agent, or pipeline — so the symbiotic loop never destroys authorship.'
49
+ - id: q5
50
+ question: What does `atelier studio` bind to by default, and why?
51
+ choices:
52
+ A: '0.0.0.0 — to let teammates connect from the same WiFi network'
53
+ B: '127.0.0.1 — to keep the editor local-only and prevent drive-by writes from any browser tab'
54
+ C: 'A randomized public IP — for cloud sync'
55
+ D: 'It does not bind to a port; it uses raw IPC'
56
+ correct: B
57
+ explanation: '`atelier studio` binds to 127.0.0.1 (loopback) so the server is only reachable from the same machine. Mutating endpoints additionally check the Origin header to reject cross-origin POSTs from random browser tabs. There is no LAN or cloud exposure by default — Atelier is a local-first tool.'
58
+ - id: q6
59
+ question: You ran `atelier preview hello.atelier --frame 15`. What did it output, and what is it equivalent to inside the engine?
60
+ choices:
61
+ A: 'A rendered PNG of frame 15 saved to disk — equivalent to `atelier export --frame 15`'
62
+ B: 'The resolved layer state at frame 15 (interpolated property values), the same as what the renderer calls `resolveFrame(doc, "default", 15)` produces'
63
+ C: 'A live video stream starting from frame 15'
64
+ D: 'Nothing — `preview` requires the studio to be running'
65
+ correct: B
66
+ explanation: '`atelier preview` calls the same `resolveFrame` function the renderer uses. It returns the interpolated layer state at that frame — every animatable property resolved through its delta and easing. Use it to inspect what the renderer would draw without actually rasterizing. For PNG output, use `atelier export --frame N --out file.png`.'
@@ -0,0 +1,66 @@
1
+ id: Q-atel-101-document-model
2
+ title: 'ATEL-101: Document Model Fundamentals'
3
+ description: Verify the document model — format shape, layer anatomy, states/deltas/resolveFrame, easings.
4
+ author: atelier
5
+ created: '2026-05-18'
6
+ updated: '2026-05-18'
7
+ tags:
8
+ - course
9
+ - atel-101
10
+ difficulty: beginner
11
+ passThreshold: 0.7
12
+ questions:
13
+ - id: q1
14
+ question: What are the five mandatory top-level keys in every `.atelier` document?
15
+ choices:
16
+ A: '`name`, `canvas`, `assets`, `variables`, `presets`'
17
+ B: '`name`, `canvas`, `layers`, `states`, plus at least one state with a `duration`'
18
+ C: '`title`, `description`, `frames`, `tracks`, `outputs`'
19
+ D: '`document`, `scene`, `keyframes`, `easings`, `version`'
20
+ correct: B
21
+ explanation: The minimum valid document has `name`, `canvas` (width/height/fps), `layers` array, and `states` with at least one named state containing a `duration`. `assets`, `variables`, `presets`, `templates`, `audio`, `meta` are all optional and added only when needed.
22
+ - id: q2
23
+ question: 'A layer has `frame: {x: 400, y: 300}`, `bounds: {width: 600, height: 80}`, and `anchorPoint: {x: 0.5, y: 0.5}`. Where on the canvas does the center of the layer sit?'
24
+ choices:
25
+ A: At canvas pixel (0, 0)
26
+ B: At canvas pixel (400, 300) — the anchor is the pivot, and `frame` positions the anchor
27
+ C: At canvas pixel (700, 380) — bottom-right of the layer
28
+ D: At canvas pixel (100, 260) — top-left of the layer
29
+ correct: B
30
+ explanation: '`anchorPoint` is normalized 0–1 inside the layer''s `bounds`. `{0.5, 0.5}` puts the anchor at the layer''s center. `frame` then positions that anchor on the canvas. So the layer''s center sits exactly at the `frame` coordinates (400, 300). The anchor is also the pivot for `rotation` and `scale`.'
31
+ - id: q3
32
+ question: Two deltas in the same state target the same layer and property, with overlapping `startFrame`/`endFrame` ranges. What happens?
33
+ choices:
34
+ A: The engine merges them by averaging the values
35
+ B: The second delta silently wins over the first
36
+ C: The schema rejects the document — the no-overlap gate blocks overlapping motion at validate time
37
+ D: Both deltas play simultaneously and the renderer additively combines them
38
+ correct: C
39
+ explanation: Atelier's `no-overlap` gate (in the document-builder) rejects overlapping deltas on the same layer+property because the result is almost always a bug. If you need composed motion, use two layers or a group. The error message names the conflicting deltas so you can fix them quickly.
40
+ - id: q4
41
+ question: 'What does `resolveFrame(doc, stateName, frameNumber)` return?'
42
+ choices:
43
+ A: A rendered PNG buffer
44
+ B: 'A ResolvedLayer array — every layer with all animatable properties interpolated through their deltas and easings to concrete values for that frame, ready for any renderer'
45
+ C: The list of deltas active at that frame
46
+ D: A diff against the previous frame
47
+ correct: B
48
+ explanation: '`resolveFrame` is the engine''s core: it returns a fully-resolved scene where every animatable property has a concrete value. Canvas 2D, SVG export, Lottie export, and the video exporter all consume this same array. Adding a new renderer is "consume ResolvedLayer[] one frame at a time."'
49
+ - id: q5
50
+ question: You want a UI element to slide in from the right and feel natural. Which easing is the right default?
51
+ choices:
52
+ A: '`linear` — constant rate is the cleanest'
53
+ B: '`ease-out` — decelerating motion is how organic things arrive (entrance animation convention across the industry)'
54
+ C: '`step(4)` — discrete jumps look modern'
55
+ D: '`ease-in` — acceleration into the final position'
56
+ correct: B
57
+ explanation: '`ease-out` (CSS cubic-bezier(0, 0, 0.2, 1)) is the de facto standard for UI entrance animation. The element decelerates as it arrives, mimicking a physical object losing momentum. `ease-in` is correct for exits, `linear` is correct for scrubbing/progress bars, and `step()` is for sprite-frame indexing or digital-readout feel. The general rule: if a physical object would do it, use spring or ease-out.'
58
+ - id: q6
59
+ question: Why do pipeline-generated layers carry tags like `silence-trim`, `caption`, or `overlay`?
60
+ choices:
61
+ A: 'Cosmetic — the tags only affect what color the layer appears as in the layer panel'
62
+ B: 'For pricing — each tag corresponds to a separately billed feature'
63
+ C: 'To enforce the layer-tag isolation invariant — re-running a pipeline drops only its own tagged layers; user-authored layers and other pipelines'' outputs are never touched'
64
+ D: 'For sorting alphabetically'
65
+ correct: C
66
+ explanation: Tags are how the pipelines (silence-trim, captions, overlays, future agent-driven pipelines) find layers they own. When `atelier captions regenerate` runs, it drops layers tagged `caption` and `caption-word` and writes fresh ones — but never touches layers tagged `silence-trim` or `overlay`, and never touches user-authored untagged layers. This is the load-bearing invariant that makes the human↔agent symbiotic loop safe.
@@ -0,0 +1,66 @@
1
+ id: Q-atel-201-mcp-authoring
2
+ title: 'ATEL-201: Authoring via MCP'
3
+ description: Verify the MCP tool surface — the single-store invariant, tool naming conventions, common authoring patterns, and the most-common agent mistakes.
4
+ author: atelier
5
+ created: '2026-05-18'
6
+ updated: '2026-05-18'
7
+ tags:
8
+ - course
9
+ - atel-201
10
+ difficulty: intermediate
11
+ passThreshold: 0.7
12
+ questions:
13
+ - id: q1
14
+ question: An AI agent calls `atelier_add_layer` while a human is editing the same document in `atelier studio`. The Studio's canvas re-renders within a frame. What architectural pattern makes this work?
15
+ choices:
16
+ A: The Studio polls the MCP server every 100ms for changes
17
+ B: 'There is one DocumentStore shared by both the MCP tool handlers and the Studio; mutations fire onChange, the WebSocket bridge broadcasts to Studio, Studio applies via applyMutation(op) with a suppress-notify flag'
18
+ C: The agent and the human edit separate copies that are merged by an operational-transform algorithm
19
+ D: 'The Studio receives a full document diff via HTTP long-poll on every keystroke'
20
+ correct: B
21
+ explanation: 'Atelier is single-author with an AI collaborator, not a multiplayer canvas. One in-memory DocumentStore is shared; mutations fire onChange; the bridge broadcasts to any connected Studio; the Studio applies via applyMutation(op) with a one-tick suppress-notify flag so the change does not echo back as a human edit. Conflict policy is last-write-wins, tagged source: "human" | "llm" so the UI can attribute changes.'
22
+ - id: q2
23
+ question: 'You are an agent. You call `atelier_add_delta` to animate `style.borderRadius` on a TextVisual layer. The tool returns an error. What is the correct response?'
24
+ choices:
25
+ A: 'Retry the same call — transient errors are common'
26
+ B: 'Read the error verbatim, recognize that borderRadius is not animatable on a text layer, choose a real text property (style.fontSize, style.color, opacity) and call again'
27
+ C: 'Edit the document file directly with `fs.writeFile` to bypass the validator'
28
+ D: 'Suppress the error and continue — the schema validation is advisory'
29
+ correct: B
30
+ explanation: 'The schema rejects animatable-property mismatches with AI-readable errors naming the exact field. The correct flow is read the error, fix the request, retry. Bypassing the validator is not possible (every tool call validates); editing files directly defeats the LLM↔Studio bridge and produces drift. The four most common agent mistakes are documented in the patterns note — this is mistake #1.'
31
+ - id: q3
32
+ question: 'Which two-tool pairing covers "I want this fade-in pattern on every title layer in three different states"?'
33
+ choices:
34
+ A: '`atelier_add_delta` called 9 times, once per (layer, state) pair — there is no other way'
35
+ B: '`atelier_define_preset` once to bundle the deltas, then `atelier_apply_preset` per layer per state with an optional offset for staggering'
36
+ C: '`atelier_instantiate_template` — templates are how repeated motion is expressed'
37
+ D: '`atelier_set_motion_path` once on each layer — motion paths handle all repetition'
38
+ correct: B
39
+ explanation: 'Presets bundle deltas; templates bundle layer subtrees. For "same motion on N layers" the right tool is `atelier_define_preset` + `atelier_apply_preset` with the `offset` parameter for staggering. Templates are for "same layer subtree" (group + children with variable substitution). One-of-a-kind motion should be hand-authored.'
40
+ - id: q4
41
+ question: How are layers in the overlay namespace (handle, page-number) protected from being destroyed when a recipe is re-applied?
42
+ choices:
43
+ A: They are stored in a separate file outside the document
44
+ B: 'They are tagged `overlay`; the `applyRecipeToOverlay` translator drops only overlay-tagged layers before re-adding, leaving user-authored layers and other pipelines'' outputs untouched'
45
+ C: A locking mechanism prevents re-application unless the user confirms
46
+ D: They are recreated identically every time; user edits are discarded by design
47
+ correct: B
48
+ explanation: 'Layer-tag isolation is the load-bearing invariant. Each pipeline owns a tag namespace (`silence-trim`, `caption`, `caption-word`, `overlay`) and re-running a pipeline drops only its own tagged layers. User-authored layers (untagged) and other pipelines'' outputs (different tag) are never touched. The same idea generalizes to agent mutations via the `source: "human" | "llm"` op tagging.'
49
+ - id: q5
50
+ question: 'Two `atelier_add_delta` calls target the same layer and the same property, with overlapping `startFrame`–`endFrame` ranges. What happens?'
51
+ choices:
52
+ A: The second call replaces the first silently
53
+ B: The values are averaged
54
+ C: 'The no-overlap gate rejects the second call with a field-level error naming both deltas; the agent must split into adjacent ranges or use two layers'
55
+ D: Both run additively at render time
56
+ correct: C
57
+ explanation: 'The no-overlap gate lives in the document builder and rejects overlapping deltas on the same layer + property. Overlapping motion is almost always a bug. The error message names both conflicting deltas so the fix is obvious — split into adjacent ranges, or compose by adding a second layer or a group.'
58
+ - id: q6
59
+ question: An agent is configuring a Claude Desktop client. The MCP config entry needed to connect to atelier-mcp is which of the following?
60
+ choices:
61
+ A: '`{ "mcpServers": { "atelier": { "command": "atelier-mcp" }}}` — that is the full config'
62
+ B: 'Requires an API token from atelier.dev'
63
+ C: 'Requires running `atelier login` first to provision credentials'
64
+ D: 'Requires manually starting `atelier-mcp serve` as a background process; the config points to its port'
65
+ correct: A
66
+ explanation: 'Atelier is local-first. The MCP config entry is one line — Claude Desktop spawns the `atelier-mcp` stdio process on demand. No tokens, no login, no separate daemon. To pin the project root, set `ATELIER_PROJECT_ROOT` in the env. To pair with `atelier studio` for live mutation, just run the studio — the bridge connection is auto-detected.'
@@ -0,0 +1,66 @@
1
+ id: Q-atel-301-visual-system
2
+ title: 'ATEL-301: Visual System'
3
+ description: Verify the visual surface — shapes, text, images/video, effects (fills/strokes/shadows/blends/clips/motion paths/tints), composition (groups/refs), and the overlay namespace.
4
+ author: atelier
5
+ created: '2026-05-18'
6
+ updated: '2026-05-18'
7
+ tags:
8
+ - course
9
+ - atel-301
10
+ difficulty: intermediate
11
+ passThreshold: 0.7
12
+ questions:
13
+ - id: q1
14
+ question: 'For text to render consistently across the browser studio and the CLI PNG export, what is the recommended pattern?'
15
+ choices:
16
+ A: Set `fontFamily` to a generic name like `sans-serif` and let each renderer pick
17
+ B: 'Ship the font as an asset (`atelier_add_asset({ kind: "font", src: "fonts/Inter.woff2" })`) and reference it by PostScript name in `fontFamily`'
18
+ C: Use only system fonts that ship with macOS, Windows, and Linux
19
+ D: Convert all text to SVG paths before rendering
20
+ correct: B
21
+ explanation: Without font assets, the studio uses browser-available fonts and the CLI PNG export uses node-canvas with whatever's installed on the machine. Renders diverge. Registering the font as an asset and referencing its PostScript name in `fontFamily` makes the renderer load the same font everywhere. The CSS-style fallback chain still works for graceful degradation.
22
+ - id: q2
23
+ question: 'You want to crop an image to show only its center 50% in a layer that sits in a 1080×1080 layout. The source image is 2000×1500. Which approach fits the "cropping vs scaling" model best?'
24
+ choices:
25
+ A: Pre-process the image with a tool like ImageMagick and ship the cropped version
26
+ B: 'Set `sourceRect: {x: 500, y: 375, width: 1000, height: 750}` to pick the center 50% from the source; the layer''s bounds and the image''s drawn region handle the rest'
27
+ C: 'Set `Layer.scale.x: 2.0` and `Layer.scale.y: 2.0` to zoom in'
28
+ D: Resize the layer's `bounds` to be smaller than the image
29
+ correct: B
30
+ explanation: '`sourceRect` is the right tool — it picks a sub-rectangle from the source image in source pixels. The center 50% of a 2000×1500 image is `{x: 500, y: 375, width: 1000, height: 750}`. Layer.scale stretches the rendered layer (lossy zoom). Layer.bounds changes the destination size (the renderer fits per objectFit). Pre-processing forfeits the ability to animate the crop later (ken-burns pan/zoom via deltas on sourceRect).'
31
+ - id: q3
32
+ question: A `motionPath` is set on a layer that also has a delta animating `frame.x`. What happens?
33
+ choices:
34
+ A: The delta wins; the motion path is ignored
35
+ B: 'The motion path wins; the frame.x delta is ignored — animate `motionProgress` instead'
36
+ C: They additively combine, producing a sum trajectory
37
+ D: 'The schema rejects the document at validate time'
38
+ correct: B
39
+ explanation: When a motion path is present, the layer's frame.x and frame.y are overridden — the path is the trajectory. Deltas on frame.x conflict with the path and are ignored at render time. To control speed along the path, animate `motionProgress` (0..1). If you want a custom trajectory, you've already chosen the motion path — express speed/timing through motionProgress + easings, not through frame deltas.
40
+ - id: q4
41
+ question: 'Why do overlay layers (handle, page-number) carry `tags: ["overlay"]` instead of being plain text layers?'
42
+ choices:
43
+ A: For sidebar grouping in the studio's layer panel only
44
+ B: 'So the `applyRecipeToOverlay` translator can drop only overlay-tagged layers when re-running — user-authored layers and other pipelines'' tagged outputs are never touched'
45
+ C: 'For pricing tier enforcement — overlay layers count toward a quota'
46
+ D: 'Because TextVisual without the tag has reduced rendering quality'
47
+ correct: B
48
+ explanation: 'The `overlay` tag is part of the layer-tag isolation invariant. Each pipeline owns a tag namespace; re-running a pipeline drops only its own tagged layers. Without the tag, re-applying a recipe would either destroy the user''s authored text layers (bad) or fail to clear stale overlay layers (also bad). With the tag, recipes are idempotent and human edits survive re-runs.'
49
+ - id: q5
50
+ question: 'A `RefVisual` layer references `./lower-third.atelier`. You edit the referenced file in another editor. What does `atelier studio` do?'
51
+ choices:
52
+ A: 'Nothing — refs are resolved once at load time'
53
+ B: The studio file-watches every referenced file; on change, it re-renders the parent composition with the updated ref
54
+ C: It throws a stale-ref error and forces a reload
55
+ D: 'It requires the user to re-import the ref manually'
56
+ correct: B
57
+ explanation: '`RefVisual` is how Atelier expresses single-source-of-truth for reusable scenes (lock-ups, lower-thirds, carousel templates). The studio file-watches every referenced `.atelier` file and re-renders parent compositions automatically. Editing the source once updates every composition that uses it.'
58
+ - id: q6
59
+ question: 'A `page_number` overlay rule has `format: "{current:02d}/{total:02d}"`. You apply the recipe to a single-frame composition (no carousel context). What happens?'
60
+ choices:
61
+ A: The page-number layer is created with content `"{current:02d}/{total:02d}"` (template not rendered)
62
+ B: 'The applyRecipeToOverlay translator skips the page-number layer (logger warns) — page-numbers don''t make sense on standalone compositions; handle still gets added if configured'
63
+ C: It throws an error halting the whole apply-recipe pipeline
64
+ D: It picks defaults (`current=1`, `total=1`) and renders `"01/01"`
65
+ correct: B
66
+ explanation: 'Page-numbers require carousel context (`currentIndex`, `totalCount`). On single-frame applies, the translator skips the page-number layer with a logger warning — by design, since `01/01` on a standalone post is misleading. The handle overlay is unconditional and still gets added. When the carousel batch driver eventually applies the same recipe per page, it threads the current/total values into the apply context and the page-number renders correctly.'
@@ -0,0 +1,66 @@
1
+ id: Q-atel-401-state-machines
2
+ title: 'ATEL-401: State Machines & Motion'
3
+ description: 'Verify hierarchical states, transitions and the interaction layer, motion (springs, expressions, motion paths), and the preset-vs-template distinction.'
4
+ author: atelier
5
+ created: '2026-05-20'
6
+ updated: '2026-05-20'
7
+ tags:
8
+ - course
9
+ - atel-401
10
+ difficulty: intermediate
11
+ passThreshold: 0.7
12
+ questions:
13
+ - id: q1
14
+ question: 'A state `hover` has `parent: default`. `default` animates `title:opacity` with a spring. `hover` adds its own `title:opacity` delta with a linear easing. What does `resolveFrame` use for the title''s opacity on `hover`?'
15
+ choices:
16
+ A: 'The child''s linear delta only — the parent''s spring delta for that layer+property is dropped entirely'
17
+ B: 'Both deltas, additively combined into one curve'
18
+ C: 'The parent''s spring, because parents take precedence over children'
19
+ D: 'A field-level merge: the child''s easing with the parent''s from/to values'
20
+ correct: A
21
+ explanation: 'Inheritance merges per layer+property group, not per field. Deltas are grouped by a `layer+property` key, and the most-derived state''s group wins outright. Because `hover` declares any `title:opacity` delta, the parent''s `title:opacity` group is dropped wholesale — there is no field-level patching. To keep the parent''s spring and add motion, the child must animate a different property or layer.'
22
+ - id: q2
23
+ question: 'You configured `atelier_configure_transition` from `default` to `hover`. Does the reverse transition (`hover` to `default`) now exist?'
24
+ choices:
25
+ A: 'Yes — configuring one direction auto-creates the symmetric reverse'
26
+ B: 'No — transitions are directional; you must configure `hover` to `default` separately'
27
+ C: 'Yes, but only if both states share a parent'
28
+ D: 'No — transitions cannot point back to a previously visited state'
29
+ correct: B
30
+ explanation: 'Transitions are directional. `default` to `hover` and `hover` to `default` are configured independently, and they usually differ in duration and easing — entrances ease-out, exits ease-in. There is no auto-symmetry.'
31
+ - id: q3
32
+ question: 'A layer has a `click` interaction whose action is `go-to-state` targeting `pressed`. No transition is configured from the current state to `pressed`. What happens on click?'
33
+ choices:
34
+ A: 'The click is ignored because the transition is missing'
35
+ B: 'It plays a default 300ms ease-in-out transition'
36
+ C: 'The runtime cuts directly to `pressed` frame 0 — the action fires, but there is no bridge to animate across'
37
+ D: 'The schema rejected the document at validate time, so the click never wired up'
38
+ correct: C
39
+ explanation: 'Interactions decide when; transitions decide how it looks getting there. A `go-to-state` action runs the configured transition for that state pair. With no transition configured, the action still fires but the runtime snaps to the target state''s frame 0. A click that appears to "do nothing" is almost always a missing transition for that pair, not a missing interaction.'
40
+ - id: q4
41
+ question: 'In a spring easing `{ type: spring, stiffness: 100, damping: 10 }`, what most directly determines whether the motion visibly overshoots and bounces?'
42
+ choices:
43
+ A: 'Damping relative to stiffness — low damping against high stiffness oscillates; high damping glides in with no overshoot'
44
+ B: 'The `mass` field alone — higher mass always adds bounce'
45
+ C: 'The delta''s frame range length'
46
+ D: 'Whether `position: start` or `position: end` is set'
47
+ correct: A
48
+ explanation: 'Bounce is governed by damping relative to stiffness. Low damping against high stiffness oscillates; raising damping at the same stiffness removes overshoot and the spring glides in. Mass adds inertia and lag, not bounce. `position` belongs to step easings, not springs.'
49
+ - id: q5
50
+ question: 'You want a layer to follow a defined curved trajectory and control its speed along that curve. Which approach matches Atelier''s model?'
51
+ choices:
52
+ A: 'Animate `frame.x` and `frame.y` with matching deltas; the engine infers the curve'
53
+ B: 'Use an expression on `frame.x` that returns the full 2D path'
54
+ C: 'Define the path as a preset and apply it to the layer'
55
+ D: 'Set a `motionPath` on the layer and animate the `motionPath.progress` property; `frame.x`/`frame.y` deltas would be overridden by the path'
56
+ correct: D
57
+ explanation: 'A motion path overrides the layer''s `frame.x`/`frame.y` — the path is the trajectory, so frame deltas conflict and are ignored at render time. You control position along the path by animating `motionPath.progress` (0 to 1); that delta''s easing shapes speed along the curve. `autoRotate` can additionally bank the layer into the tangent.'
58
+ - id: q6
59
+ question: 'What is the difference between a preset and a template in Atelier?'
60
+ choices:
61
+ A: 'A preset is a bundle of deltas applied to an existing layer; a template is a layer subtree instantiated (with variable bindings) into a new document'
62
+ B: 'A preset is for export; a template is for the studio editor'
63
+ C: 'A preset creates new layers; a template animates existing ones'
64
+ D: 'They are interchangeable names for the same reuse mechanism'
65
+ correct: A
66
+ explanation: 'Presets = delta bundles; templates = layer subtrees. `atelier_define_preset` then `atelier_apply_preset` stamp motion onto a layer that already exists (expanding `offset` windows relative to `startFrame`). `atelier_instantiate_template` resolves `{{variable}}` placeholders against bindings and creates a new document with the structure. Motion from one mechanism, structure from the other.'
@@ -0,0 +1,66 @@
1
+ id: Q-atel-501-video-pipeline
2
+ title: 'ATEL-501: Video Project Pipeline'
3
+ description: 'Verify the video pipeline — silence trimming with parametric cuts, Whisper transcription and the transcript edit family, caption generation, and the two re-run safety aspects (detected-vs-user-edited, layer-tag-isolation).'
4
+ author: atelier
5
+ created: '2026-05-20'
6
+ updated: '2026-05-20'
7
+ tags:
8
+ - course
9
+ - atel-501
10
+ difficulty: intermediate
11
+ passThreshold: 0.7
12
+ questions:
13
+ - id: q1
14
+ question: 'In a CutEntry, which two fields are the immutable AI detections, and which two are the user-tunable overrides?'
15
+ choices:
16
+ A: 'paddingPre/paddingPost are detected; rawStart/rawEnd are user-tunable'
17
+ B: 'rawStart/rawEnd are detected (immutable); paddingPre/paddingPost are the user-tunable overrides'
18
+ C: 'All four are detected; the user edits the layers directly'
19
+ D: 'All four are user-tunable; nothing is detected'
20
+ correct: B
21
+ explanation: 'rawStart and rawEnd are the detected speech boundaries from silencedetect — you do not edit them by hand. paddingPre and paddingPost are your editorial overrides; the effective cut is [rawStart - paddingPre, rawEnd + paddingPost]. Keeping them in separate fields is what lets a re-detect regenerate the raw boundaries while preserving your padding.'
22
+ - id: q2
23
+ question: 'You run `atelier trim ./proj --loosen 120`. What happens?'
24
+ choices:
25
+ A: 'The 120 is treated as seconds and added to every paddingPre/paddingPost'
26
+ B: 'Every cut padding increases by 120 milliseconds (0.12s), composing with any existing per-cut overrides; padding still floors at 0'
27
+ C: 'Only cut #120 is loosened'
28
+ D: 'The raw boundaries shift outward by 120 frames'
29
+ correct: B
30
+ explanation: '`--tighten` and `--loosen` take milliseconds. 120ms is converted to 0.12s and added to the current padding of every cut, so it composes with overrides you already set rather than clobbering them. `--tighten` subtracts; padding is floored at 0 so you cannot drive a cut negative. Per-cut targeting is done with `--cut <index>`, not `--loosen`.'
31
+ - id: q3
32
+ question: 'What does `atelier transcript delete ./proj --word 7` actually do?'
33
+ choices:
34
+ A: 'Removes word 7 from transcript.json entirely'
35
+ B: 'Deletes the caption layer at index 7'
36
+ C: 'Sets hidden=true on word 7 — it stays in the transcript but is skipped when captions are built'
37
+ D: 'Lowers word 7 confidence below the caption threshold'
38
+ correct: C
39
+ explanation: 'delete is a soft, reversible cut. It sets hidden on the word; the word remains in transcript.json (so a future re-transcribe can re-recognize it) but groupIntoPhrases skips hidden words, so it never appears in a caption. To truly remove it you would have to edit it back in — by design, nothing is destroyed.'
40
+ - id: q4
41
+ question: 'A re-transcribe must decide whether to carry your correction forward to a fresh detection. Both conditions must hold for the override to be re-attached. Which pair?'
42
+ choices:
43
+ A: 'Same word index AND same segment'
44
+ B: 'Timing within tolerance (0.3s) AND the detected string is unchanged'
45
+ C: 'Same confidence value AND same language'
46
+ D: 'Identical text AND identical start time to the millisecond'
47
+ correct: B
48
+ explanation: 'mergeTranscriptWithExisting matches a fresh detection to an old entry on timing within 0.3s AND an unchanged detected string. Timing alone is not enough — if Whisper now hears a different word at that timestamp, carrying the old fix forward would graft it onto the wrong word. Requiring the detected string to match guarantees you are editing the same finding. (Cuts use the same idea with a 0.5s tolerance on both raw boundaries.)'
49
+ - id: q5
50
+ question: 'You added a word with `atelier transcript add` that Whisper never heard. On the next `atelier transcribe`, what happens to it?'
51
+ choices:
52
+ A: 'It is dropped — there is no matching fresh detection'
53
+ B: 'It is converted into a detected word with confidence 1.0'
54
+ C: 'It blocks the re-transcribe until you remove it'
55
+ D: 'It is preserved as a userAdded orphan and re-inserted into the right segment by its timing'
56
+ correct: D
57
+ explanation: 'A userAdded word has no fresh detection to match against by definition — Whisper did not produce it. The merge collects these orphans and re-inserts each into the segment that contains its timing. Your manual additions survive a re-transcribe just like your text corrections do.'
58
+ - id: q6
59
+ question: 'You run `atelier transcribe`, which rewrites caption layers. You also have hand-authored title layers and silence-trim layers in the same document. Why are your titles safe?'
60
+ choices:
61
+ A: 'transcribe only ever appends; it never removes layers'
62
+ B: 'Layer-tag isolation — the rewrite drops only caption-tagged layers; untagged (user-authored) and silence-trim-tagged layers fall into the preserved set and pass through unchanged'
63
+ C: 'Title layers are stored in a separate file from caption layers'
64
+ D: 'The schema locks all non-caption layers during a transcribe run'
65
+ correct: B
66
+ explanation: 'rewriteCaptionLayers partitions the document: everything that is NOT caption-tagged is preserved, and only the caption set is dropped and rebuilt. Your untagged titles and the silence-trim-tagged cut layers are untouched. Each pipeline owns exactly one tag namespace and cleans up only after itself — the invariant the test suite asserts directly.'
@@ -0,0 +1,66 @@
1
+ id: Q-atel-601-recipes
2
+ title: 'ATEL-601: Recipes, Overlays & Carousel'
3
+ description: 'Verify the Studio Recipe surface — what a recipe is and how partial recipes fall back, overlay_rules (handle + page_number, anchors, format templates), the CLI/MCP tooling and precedence, and the carousel batch driver.'
4
+ author: atelier
5
+ created: '2026-05-20'
6
+ updated: '2026-05-20'
7
+ tags:
8
+ - course
9
+ - atel-601
10
+ difficulty: intermediate
11
+ passThreshold: 0.7
12
+ questions:
13
+ - id: q1
14
+ question: 'You author a recipe that sets only `caption_style.color`. What happens to the silence policy and the rest of the caption style when you apply it?'
15
+ choices:
16
+ A: 'The apply fails — a recipe must declare every Phase 1 section to be valid'
17
+ B: 'Omitted fields fall back to the code defaults (e.g. noise -30dB, font_size 84); the merge is shallow per section, so setting one caption field does not wipe the others'
18
+ C: 'Only the color is applied and all other styling is cleared to empty/zero'
19
+ D: 'The recipe silently does nothing because it is incomplete'
20
+ correct: B
21
+ explanation: 'Every section and every field is optional. Omitted fields fall back to the code defaults baked into renderRecipeWithDefaults, and the merge overlays your section over the defaults section — so setting one caption field leaves the rest of the defaults intact. Run `atelier recipe show <name> --with-defaults` to see the fully-resolved effective recipe.'
22
+ - id: q2
23
+ question: 'A recipe includes a `palette` section. You run `atelier recipe validate` on it. What is the result?'
24
+ choices:
25
+ A: 'FAIL — palette is not a recognized field'
26
+ B: 'PASS with no remarks — palette is fully implemented'
27
+ C: 'PASS, but with a forward-compat warning that palette is a reserved Phase 3 field that Phase 1 ignores'
28
+ D: 'It strips the palette section and rewrites the file'
29
+ correct: C
30
+ explanation: 'palette is one of the six reserved Phase 3 fields. They parse cleanly as opaque (unknown) values so recipes stay forward-compatible, but Phase 1 ignores their contents. validate emits a forward-compat warning (not an error) so you are not caught off-guard that the section is inert. Note overlay_rules was promoted to first-class Phase 1.5 and does NOT warn.'
31
+ - id: q3
32
+ question: 'A `page_number` rule has `format: "{current:02d}/{total:02d}"` and you apply the recipe to a single-frame composition via `atelier apply-recipe` (no carousel context). What renders?'
33
+ choices:
34
+ A: 'A page-number layer with the literal content `{current:02d}/{total:02d}` (template not substituted)'
35
+ B: 'The page-number layer is skipped with a warning; the handle is still applied if present'
36
+ C: 'It defaults to current=1, total=1 and renders `01/01`'
37
+ D: 'The whole apply-recipe pipeline throws and halts'
38
+ correct: B
39
+ explanation: 'applyRecipeToOverlay only emits the page-number layer when both currentIndex and totalCount are in its context. A single-frame apply has no carousel context, so the page-number is skipped with a one-shot warning — `01/01` on a standalone post would be misleading. The handle overlay is unconditional and still gets added. The carousel driver is what threads current/total in.'
40
+ - id: q4
41
+ question: 'In the four-corner anchor math, what does the `anchor` value `bottom-right` map to (with margin m on a canvas of width W and height H)?'
42
+ choices:
43
+ A: 'frame `{ x: m, y: m }`, anchorPoint `{ x: 0, y: 0 }`'
44
+ B: 'frame `{ x: W - m, y: H - m }`, anchorPoint `{ x: 1, y: 1 }`'
45
+ C: 'frame `{ x: W - m, y: m }`, anchorPoint `{ x: 1, y: 0 }`'
46
+ D: 'frame `{ x: W / 2, y: H / 2 }`, anchorPoint `{ x: 0.5, y: 0.5 }`'
47
+ correct: B
48
+ explanation: 'anchorToFrame pairs the frame position with a matching anchorPoint so the corner sticks. bottom-right places frame at `{ W - margin, H - margin }` and sets anchorPoint `{ 1, 1 }`, so the overlay anchors its own bottom-right corner at the canvas bottom-right-minus-margin point and grows inward regardless of text length. The anchor doubles as the rotation/scale pivot.'
49
+ - id: q5
50
+ question: 'How does `atelier_recipe_save` (MCP) differ from `atelier recipe new` (CLI), and why does it matter for an editing agent?'
51
+ choices:
52
+ A: 'They are identical; save is just the MCP alias for new'
53
+ B: 'new writes an always-valid scaffold template; save validates BEFORE writing and refuses to write an invalid recipe — so an agent persisting an edited recipe cannot land a malformed file on disk'
54
+ C: 'save writes without validating; new validates first'
55
+ D: 'new can only write YAML, save can only write JSON'
56
+ correct: B
57
+ explanation: 'recipe new scaffolds a starter template that is valid by construction and does not validate. atelier_recipe_save is how an agent persists an EDITED recipe, so it runs validateRecipe first and refuses to write if invalid, returning the issues instead. That validate-before-write guard is what keeps a malformed agent edit from ever reaching disk.'
58
+ - id: q6
59
+ question: 'The carousel driver loops over a sorted folder of N images. For image at loop position n (0-based), what index does it thread into the page-number overlay, and how is the output filename prefixed?'
60
+ choices:
61
+ A: 'index = n (0-based); filename prefixed with the raw loop counter, no padding'
62
+ B: 'index = n + 1 (1-based); filename prefixed with a zero-padded number of width max(2, digits-in-N)'
63
+ C: 'index is a random uuid; filename uses the original image name unchanged'
64
+ D: 'index = N - n (counts down); filename prefixed with the total count'
65
+ correct: B
66
+ explanation: 'The driver computes `const index = n + 1` (1-based) and passes `{ currentIndex: index, totalCount: total }` so the page-number renders i/N. The output name is zero-padded to `Math.max(2, String(total).length)` digits then carries the original stem, so files sort in deck order. Both the page-number content and the filename prefix derive from the same 1-based index, keeping what the viewer sees and how files sort in agreement.'