@m0saic/knowledge 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +71 -0
  3. package/dist/index.d.ts +8 -0
  4. package/dist/index.js +7 -0
  5. package/docs/README.md +60 -0
  6. package/docs/file-formats/m0-iteration-protocol.md +120 -0
  7. package/docs/file-formats/m0p-and-custom-field.md +195 -0
  8. package/docs/handbook/README.md +27 -0
  9. package/docs/handbook/composition-arithmetic.md +278 -0
  10. package/docs/handbook/dsl-complexity.md +75 -0
  11. package/docs/handbook/dsl-rules.md +367 -0
  12. package/docs/handbook/feasibility-precision-quantization.md +591 -0
  13. package/docs/handbook/m0-construction-methods.md +201 -0
  14. package/docs/handbook/precision-tiers.md +84 -0
  15. package/docs/m0saic-thesis.md +95 -0
  16. package/docs/runtime/README.md +17 -0
  17. package/docs/runtime/cli-usage.md +372 -0
  18. package/docs/runtime/ffmpeg-expression-limits.md +117 -0
  19. package/docs/runtime/reduce-to-one.md +96 -0
  20. package/docs/skills/README.md +40 -0
  21. package/docs/skills/axis-and-geometry.md +103 -0
  22. package/docs/skills/dsl-stdlib-method-catalog.md +7 -0
  23. package/docs/skills/identity.md +123 -0
  24. package/docs/skills/labels-and-masks.md +170 -0
  25. package/docs/skills/m0saic-string-generation.md +251 -0
  26. package/docs/skills/more-atoms-not-bigger-atoms.md +77 -0
  27. package/docs/skills/overlay-semantics.md +194 -0
  28. package/docs/skills/parse-apis.md +79 -0
  29. package/docs/skills/passthrough-semantics.md +136 -0
  30. package/docs/skills/structural-construction.md +86 -0
  31. package/docs/skills/text-in-templates.md +126 -0
  32. package/docs/skills/zero-overlay-analysis.md +87 -0
  33. package/docs/templates/README.md +65 -0
  34. package/docs/templates/capability-templates.md +72 -0
  35. package/docs/templates/construction-strategy.md +329 -0
  36. package/docs/templates/data-pipeline.md +324 -0
  37. package/docs/templates/emission-patterns.md +130 -0
  38. package/docs/templates/geometry-recipes.md +248 -0
  39. package/docs/templates/layout-contract.md +168 -0
  40. package/docs/templates/output-resolution-tree.md +202 -0
  41. package/docs/templates/patterns/case-study-lessons.md +69 -0
  42. package/docs/templates/patterns/perf-authoring-rules.md +100 -0
  43. package/docs/templates/patterns/primitive-extraction-pattern.md +103 -0
  44. package/docs/templates/philosophy-and-contract.md +310 -0
  45. package/docs/templates/recursion-nested-rendering.md +138 -0
  46. package/docs/templates/reference/grid.md +104 -0
  47. package/docs/templates/reference/json-prop-type.md +169 -0
  48. package/docs/templates/reference/mosaic-color.md +81 -0
  49. package/docs/templates/reference/mosaic-placement-props.md +103 -0
  50. package/docs/templates/reference/prop-bindings.md +203 -0
  51. package/docs/templates/reference/template-flags.md +205 -0
  52. package/docs/templates/render-lifecycle.md +117 -0
  53. package/docs/templates/rendering-model-contract.md +392 -0
  54. package/docs/templates/standalone-pack-authoring.md +233 -0
  55. package/docs/templates/theming.md +81 -0
  56. package/docs/templates/ui-controls.md +150 -0
  57. package/package.json +37 -0
@@ -0,0 +1,372 @@
1
+ # m0saic CLI usage
2
+
3
+ > Regenerated 2026-07-29 from the CLI source (not published) — re-verify with
4
+ > `m0saic --help` / `grep -c '.command(' the CLI source (not published) (14 as of
5
+ > 2026-07-29, **2 of them dev-gated** — `browse-templates` and `momo`; a 15th
6
+ > command, `telemetry`, registers via a helper — see below). So: 12 public
7
+ > commands + 2 dev-gated + `telemetry`.
8
+ >
9
+ > **2026-09-16:** `doctor` (added after this snapshot, dev-gated at 0.1.0) is now
10
+ > **public** — see its row; `browse-templates` and `momo` remain the only two
11
+ > `if (DEV_MODE)` commands. Public set is locked by `PUBLIC_COMMANDS` in
12
+ > the CLI source (not published) (15 incl. `doctor`, `hello-world`).
13
+
14
+ Source of truth: [the CLI source (not published)](the CLI source (not published))
15
+ (~5,866 lines). Inline-DSL arg parsing: [the CLI source (not published)](the CLI source (not published)).
16
+ Root `COMMANDS.md` (monorepo document, not published) documents the inline-DSL shorthand + test tiers.
17
+
18
+ ## Entry routing — inline-DSL mode is first-class
19
+
20
+ Before commander ever runs, `index.ts:5915-5920` inspects `process.argv[2]`:
21
+ `isInlineM0()` (`inlineDsl.ts:36-41`) treats ANY first arg that is not a flag and
22
+ not in `KNOWN_SUBCOMMANDS` as an inline m0 string and dispatches to
23
+ `handleInlineDsl` instead of `program.parseAsync`.
24
+
25
+ ```bash
26
+ m0saic "2(1,1)" # static wireframe → output.png (default mode "wire")
27
+ m0saic "2(1,1)" --anim # animated wireframe → output.mp4
28
+ m0saic "2(1,1)" --wire # explicit static (same as default)
29
+ m0saic layout.m0 # .m0 / .m0c paths also accepted (index.ts:5787-5805)
30
+ ```
31
+
32
+ - Args after the string parse via `parseInlineArgs` (`inlineDsl.ts:123`): `-w/-h`
33
+ (default 1920×1080), `-o`, `--fps`, `--durationMs`, `--props`, `--save-mosaic`,
34
+ `--save-m0`, `--prefer-pretty-m0`/`--prefer-canonical-m0`, `--disable-ui`,
35
+ `--format`, `--alpha`, `--validate-only`, `--report`, `--toolchain`, plus the
36
+ dev-gated dump flags (below).
37
+ - The string is validated with `validateM0String` before render (index.ts:5808);
38
+ whitespace is stripped. Always shell-quote the DSL (parens/brackets).
39
+ - Output extension is coerced to match mode (`resolveInlineOutput`,
40
+ `inlineDsl.ts:86` — `--anim -o out.png` → `out.mp4`).
41
+ - Mode maps onto the wireframe handlers: `wire` → `handleWireframe`, `anim` →
42
+ `handleAnimatedWireframe` (index.ts:5846-5850).
43
+
44
+ ## Command table
45
+
46
+ 14 `.command(` registrations in `index.ts` + `telemetry` registered by
47
+ `registerTelemetryCommand(program)` (index.ts:5784 → `utils/telemetry.ts:372`).
48
+ Two of the 14 sit inside `if (DEV_MODE) { … }` and are absent from the public
49
+ help — see the DEV-GATED rows below.
50
+
51
+ | Command (index.ts line) | What it does |
52
+ |---|---|
53
+ | `make <input>` (4143) | Primary render: template id / `.mosaic` / `.mosaicx` → video or image. Full flag surface below. |
54
+ | `hello-world` | The first render — a thin alias for `make @m0saic/hello-world/v1` (the brand card; output defaults to `hello-world.mp4`). With `--template-repo <path...>` / `--community-repo <path>` it renders the loaded repo's **front door** instead: the first repo (load order) whose `repo.helloWorld` names a template that registered, announced as `Front door: <id> — <repo>'s hello-world`; no repos, or none that name one → the core card with a one-line why. Takes `-o -w -h --fps --durationMs --props --quiet --verbose` (no engine surface). Picker: the CLI source (not published). |
55
+ | `make-wireframe` (4372) | Static wireframe PNG from `--m0 <string>` or `--mfile <path>`. |
56
+ | `make-wireframe-animated` (4448) | Animated wireframe MP4 from `--m0`/`--mfile`; extra `--disable-ui`. |
57
+ | `flatten <input>` (4531) | Inline all `type:mosaic` children of a `.mosaic`/template into one flat JSON doc — no render. |
58
+ | `resolve <input>` (4642) | `.mosaicx` recipe → resolved `.mosaic` provenance doc, no ffmpeg; `--flatten` also inlines children. |
59
+ | `list-templates` (4751) | Print registered template ids (`--json`); annotates primitive/internal/deprecated. |
60
+ | `doctor <repoDir>` | **Public since 0.2.0** (dev-gated at 0.1.0; founder ruling 2026-09-16, BURN-DOWN D5 → publish). Run the template conventions over a template repo folder — the same checks its build gate (`tools/check-registry.mjs`) runs, from outside (agents, reviewers, forks, CI on a fork): definition-time + render-time conventions (the 13 throw-posture ones incl. `latticeSmooth`) and, when the repo commits `layout-fingerprints/` or `.layout.m0` sidecars, the fingerprint diff. `--json` (one object: `ok, repo, mode, loadDiagnostics, rendered, skipped, errors, warnings, notes, fingerprints`), `--sweep` (also render on the standard 1080p canvases → `canvasEnvelope`), `--entry <path>`, `--src <dir>`. Loads through `loadTemplateRepoFromPath` under the external origin scope, so conventions RECORD rather than throw and one bad template never hides the rest. **Runs the repo's template module bodies in-process** — prints a trust warning to stderr (not under `--json`). Exit 1 on any error-severity finding or a fatal load diagnostic. It is the pack-publishing contract for community/starter authors ("the publish requirement for packs"). Impl: the CLI source (not published); locked public by `__tests__/cli.surface.test.js`. |
61
+ | `browse-templates` (4794) | **DEV-GATED** (whole command inside `if (DEV_MODE)`, 4792): interactive TTY picker (template → variant → confirm → delegates to `make`). It drives the E2E variant matrix and writes to `test-output/` — a development harness, not a user surface. |
62
+ | `momo <message…>` (5042) | **DEV-GATED** (whole command inside `if (DEV_MODE)`, 5040): relay English geometry instruction to Momo in a running Mosaic Desktop over the loopback bridge; exit 0 applied / 2 not-applied / 1 couldn't-run. |
63
+ | `open [file]` (5345) | Open a file in Mosaic Desktop, or `--template` / `--make` to open the Make page via the bridge. See below. |
64
+ | `setup` (5496) | Download + install the pinned ffmpeg toolchain (`--gpl` default / `--lgpl` / `--yes` / `--platform-key`). Idempotent: detects installed golden slot or a PATH ffmpeg with libx264. **The only path that downloads** — renders never do. TTY asks `Download the pinned GPL ffmpeg build now?`; non-TTY / `CI` need explicit approval — `--yes` (or the `--gpl` / `--lgpl` repair flags), otherwise `No terminal to ask for approval on.` + exit 1, nothing downloaded. Ends `✓ ffmpeg is ready. Now run your m0saic command again.` |
65
+ | `activate <key>` (5612) | Validate + store a license key at `~/m0saic/license.json` (`M0SAIC_PRODUCT_KEY` env wins over the file). |
66
+ | `license` (5659) | Show tier/holder/expiry; `--remove` returns to free tier. |
67
+ | `update` (5711) | Check npm for a newer release; offers `npm i -g m0saic@latest`. |
68
+ | `versions` (5750) | Print package versions + ffmpeg baseline/runtime + resolved toolchain (`--json`). |
69
+ | `telemetry` (via helper) | Subcommands `status` (default) · `set-mode <standard\|local-only\|ghost>` · `list` · `preview` · `flush` · `clear`. Since 0.2.0 PUBLISHED builds transmit (Standard mode): day summary + today-so-far rollups after renders via a detached worker, to `https://m0saic.io/api/telemetry`. Workspace builds are dormant. Gates for harnesses: `M0SAIC_TELEMETRY=ghost` (nothing recorded), `M0SAIC_TELEMETRY_ENDPOINT=off` (nothing sent), `CI` (no send unless the endpoint is set by env). Full reference: `TELEMETRY.md` at the repo root. |
70
+
71
+ > **`decode-watermark` is GONE** (removed 2026-07-28). Recovering a forensic
72
+ > watermark is now an ordinary template — `@m0saic/forensic/watermark/verify/v1`,
73
+ > run through `make` — because a CLI can't grow a bespoke command per template
74
+ > family. The old `--ffmpeg <path>` override it carried is now the template's
75
+ > `ffmpegPath` prop.
76
+
77
+ ## `make` input routing (index.ts:4348-4367)
78
+
79
+ 1. Extension `.mosaic` → `handleMosaicFile` — parse JSON renderable, resolve
80
+ relative media against the file's dir, plan + render.
81
+ 2. Extension `.mosaicx` → `handleMosaicxFile` (3419) — the **resolve-then-render
82
+ branch**: absolutize asset paths → apply the doc's `runner` block as option
83
+ defaults (explicit flags win) → sibling `.m0v` auto-discovery → route
84
+ `--inputs`/`--input-dir` into the single `template_invocation`'s
85
+ `props.sourceIds` (ambiguous multi-invocation → error; zero inputs on an
86
+ input-requiring template → renders a usage-card and exits non-zero) →
87
+ `resolveMosaicx` → write resolved doc to a tmp `.mosaic` → delegate to
88
+ `handleMosaicFile` (3631) so the post-resolve render path is shared.
89
+ **Two roots accepted** (2026-08-14): `mosaicx_document` and
90
+ `mosaicx_pipeline` (a root-level template chain — see
91
+ `templates/data-pipeline.md`). The header echoes which one it read
92
+ (`kind: mosaicx_pipeline`). A chain root has no top-level `sources`, so the
93
+ `--inputs` routing and the usage-card gap detection above are inert for it;
94
+ `runner` applies to both. Same for `resolve <input>`.
95
+ **Duration is an ASK on this path** (2026-09-05): `--durationMs ??
96
+ wrapper.durationMs` reaches the template as `userIntent.durationMs` via
97
+ `mosaicxUserIntent` (`mosaicxRunner.ts:405`) on both `make` and `resolve`, so a
98
+ self-timing template FITS its walk to the length (as Make does) instead of
99
+ being trimmed to it. Mint ledger wrappers mirroring the template's natural
100
+ length unless the variant is deliberately pinned.
101
+ 3. No extension + starts with `@` → `handleTemplateMake` — template id, merge
102
+ `--props` over `defaultProps`, render.
103
+ 4. Anything else → error, exit 1.
104
+
105
+ ## Exit codes (2026-09-15)
106
+
107
+ | Code | Meaning | File at `-o`? |
108
+ |---|---|---|
109
+ | `0` | Success — the output is the render asked for. | Yes |
110
+ | `1` | Failure — plan build threw, ffmpeg exited non-zero, validation failed, zero commands. | No / unusable |
111
+ | `2` | `momo` ran but did not apply a change. | n/a |
112
+ | `3` | **RENDER DEGRADED** — the renderable carried `engine.renderStatus: "error"`, so the output is an **error mosaic**. | **Yes**, valid media |
113
+
114
+ **Why 3 exists.** A template that hits bad input does not crash: it returns
115
+ `makeErrorMosaic(...)`, a readable card stamped `engine.renderStatus: "error"`.
116
+ That card renders, so the CLI used to write it and exit 0 — a caller whose only
117
+ success test was "did a file appear at the expected path?" reported success on a
118
+ picture of an error. 3 is deliberately not 1: exit 1 means "there is no output,
119
+ retry or clean up", which is false here. The engine still never aborts on the
120
+ marker — it renders, writes the file, and *then* the CLI reports.
121
+
122
+ **Detection** (`src/utils/collectRenderErrors.ts`) walks the WHOLE renderable
123
+ tree for the marker: document level, source level, nested `children`
124
+ (recursively), and pipeline `steps` (dispatching per step, so a nested pipeline
125
+ is walked too). **One error mosaic anywhere in the tree degrades the whole run** —
126
+ a batch where 4 of 5 steps are fine still exits 3, because one deliverable is an
127
+ error card. `errors[].path` in the sidecar (`children.hero.sources[0]`,
128
+ `steps[1].sources[0]`) says which.
129
+
130
+ > ⚠️ Historical: the walker read `doc.config?.sources`, but
131
+ > `MosaicDocument.sources` was hoisted out of `config` long ago — so every
132
+ > source-level marker (exactly where `makeErrorMosaic` puts it) was invisible and
133
+ > every error-mosaic render exited 0. `mosaicxRunner` hand-promoted its error to
134
+ > doc level to work around it; both the bug and the workaround are gone.
135
+
136
+ **Classification** (`src/utils/renderOutcome.ts` — the single decision point; all
137
+ 12 report call sites in `index.ts` route through it):
138
+
139
+ - **Hard errors beat engine errors.** A run with both is 1, not 3.
140
+ - A **zero-command plan** counts its engine errors as hard — there is no output
141
+ to call degraded — so that path stays 1.
142
+ - Free vs paid tier is **orthogonal**. The marker is read off the document, not
143
+ the file; the free-tier QR wrap runs before classification, so on free tier the
144
+ error card ships stamped and still exits 3. A stamp failure is swallowed as a
145
+ warning and never changes the exit code.
146
+
147
+ With `--report` the same verdict is machine-readable, so a caller that parses the
148
+ sidecar never needs the exit code and a caller that reads exit codes never needs
149
+ the JSON:
150
+
151
+ ```json
152
+ { "ok": false, "exitCode": 3, "renderStatus": "degraded",
153
+ "renderErrorCodes": ["ENGINE_ERROR"],
154
+ "errors": [{ "code": "ENGINE_ERROR", "message": "Quote Card: quote must not be empty", "path": "sources[0]" }] }
155
+ ```
156
+
157
+ ## `make` flags
158
+
159
+ **Core** — `-w/--width` and `-h/--height` are `requiredOption`s. Then:
160
+ `-o/--output` (defaults `out.mp4` / `out.png`, index.ts:172-173), `--fps` (1–120),
161
+ `--durationMs` (>0), `--format`/`--output-kind` (`video|image`),
162
+ `--alpha`/`--no-alpha`, `--props <jsonOrPath>` (inline JSON **or** `@path/to/json`
163
+ — `utils/readJsonArg.ts:18` strips the `@` and reads the file),
164
+ `--validate-only` (exit 0/1/3, no render — 3 = the renderable IS an error
165
+ mosaic; prints `Validation DEGRADED`), `--report` (`.output.json` /
166
+ `.validate.json` sidecars, both carrying `renderStatus` + `renderErrorCodes`),
167
+ `--save-mosaic [path]`, `--save-m0 [path]`,
168
+ `--keep-temp`, `--quiet`, `--verbose`, `--prefer-pretty-m0` /
169
+ `--prefer-canonical-m0` (printed-m0 form only; note: NOT `--prefer-*-m0saic`),
170
+ `--background-color <color>`, `--template-repo <path...>` +
171
+ `--template-repo-entry <file>` (external template repos), `--community-repo <path>`
172
+ (ONE checkout of the official community repo — see the community gate below).
173
+
174
+ **Encode surface** (index.ts:4231-4338, mirrors the Make page's Advanced panel;
175
+ precedence: flags > `.m0v` preset > doc-on-disk > engine default):
176
+ `--target <preset>` (`web-mp4|web-webm|alpha-mov|image-png|image-jpeg|animated-gif|audio-mp3|audio-wav`),
177
+ `--container`, `--video-codec`, `--audio-codec`, `--pixel-format`, `--bitrate`
178
+ (CRF wins on conflict), `--crf`, `--encoder-preset`, `--encoder-profile`,
179
+ `--encoder-level`, `--encoder-options <k=v,…>`, `--gop-size`, `--audio-bitrate`,
180
+ `--audio-sample-rate`, `--audio-channel-layout`, `--no-audio` (`-an`),
181
+ `--color-space`/`--color-range`/`--color-primaries`/`--color-transfer`
182
+ (metadata-only), `--metadata-title`/`-description`/`-author`/`-copyright`/`-comment`.
183
+
184
+ **Batching / multi-output**: `--inputs <files...>` (populates `sourceIds`; wins
185
+ over `--input-dir`), `--input-dir <path>` (+ `--recursive` for depth-first walk),
186
+ `--output-pattern <pattern>` (tokens `{{base}} {{ext}} {{stepName}} {{index}}
187
+ {{i}} {{date}} {{batch}} {{label}}`; collisions get `-1,-2,…`), `--batch <name>`.
188
+
189
+ **`.m0v`**: `--m0v <path>` loads a Mosaic Vocabulary file two ways — named
190
+ outputs onto `ctx.userIntent.outputs` (template-consultative) AND a post-render
191
+ merge onto the renderable **by index** when entry counts match
192
+ (index.ts:4218-4221; `BaseRenderOptions` docstring 541-557).
193
+
194
+ **Toolchain**: `--toolchain <name>` is a **global program option**
195
+ (index.ts:526), not make-specific — a named entry from `m0saic.local.json`, with
196
+ implicit `gpl`/`lgpl` golden slots merged in. Resolution precedence: `--toolchain`
197
+ flag > `M0SAIC_TOOLCHAIN` env > config `defaultToolchain` > PATH/golden fallback
198
+ (`utils/toolchainConfig.ts:129`). There is **no** `--ffmpeg` anywhere on the CLI
199
+ any more (it left with `decode-watermark`) — the equivalent is the verify
200
+ template's `ffmpegPath` prop; `--platform-key` only on `setup`.
201
+
202
+ **No-ffmpeg gate** (`utils/ensureFfmpeg.ts` via `ensureRenderToolchain()`, index.ts
203
+ ~404, first thing in every render handler): resolved ffmpeg fails to probe → the
204
+ 4-line `ffmpeg isn't detected.` message (points at `setup`) and **exit 1** — TTY,
205
+ piped and CI alike; no prompt, no inline download, no auto-install (founder
206
+ ruling 2026-09-16; supersedes cli-v1-publish.md Phase 2). `setup` is the only
207
+ downloader, and it too never fetches without approval (TTY prompt, or `--yes`).
208
+ Spell every `setup` hint with `cliSetupCommand()` (`utils/invocation.ts`):
209
+ `npx m0saic setup` under npx, `m0saic setup` installed.
210
+
211
+ **Perf**: `--perf [path]` writes a `.perf.json` sidecar — per-ffmpeg-command
212
+ timing rolled up by mosaic node (names the bottleneck panel/template).
213
+
214
+ **DEV-GATED** (each wrapped in `.hideHelp(!DEV_MODE)`; `DEV_MODE =
215
+ process.env.M0SAIC_DEV === "1"` at index.ts:58, compile-stripped to `false` in
216
+ the published artifact): `--dev` (commands + timings), `--print-commands`,
217
+ `--save-plan [path]`, `--save-commands [path]`. Same gating inside inline-DSL
218
+ mode (`inlineDsl.ts:148-154`). Also dev-gated: the whole `momo` command, the
219
+ whole `browse-templates` command, and `open`'s
220
+ `--agent-note`/`--agent-question`. `--save-mosaic` and `--save-m0` are NOT gated,
221
+ and neither is `doctor` (public since 0.2.0).
222
+
223
+ > Note: a dev-gated command's NAME still appears in `KNOWN_SUBCOMMANDS`
224
+ > (`inlineDsl.ts:11-29`), outside the gate — the inline-DSL router has to know
225
+ > `momo` is a subcommand and not an m0 string. So grepping a shipped tarball for
226
+ > `momo` / `browse-templates` finds hits; that is the router table, not the
227
+ > gated implementation.
228
+
229
+ ## External template repos and the community gate (2026-09-16)
230
+
231
+ **Three reserved identities, exact-match, nothing else is checked:** template
232
+ namespaces `@m0saic/` and `@m0saic-dev/`, repo id `@m0saic-community`
233
+ (`index.ts:907-909`; registry compare is case-insensitive, `templateRegistry.ts:112`).
234
+ Every other namespace / repo id is open. `--template-repo` never grants anything.
235
+
236
+ **The grant.** `--community-repo <path>` says "this IS the official repo"; the
237
+ `@m0saic-dev/` grant comes only from `verifyCommunityRelease()` (`@m0saic/product`
238
+ `communityRelease.ts`) passing on the checkout's `release.json` against
239
+ `LICENSE_PUBLIC_KEYS` (`publicKeys.ts` — the licence k1 family; rotation = append
240
+ `k2`, ship, sign with `M0SAIC_SIGNING_KID=k2`; older CLIs report `unknown-kid`).
241
+ The gate runs BEFORE the entry module is imported. All gate/notice lines go to
242
+ stderr (survive `--quiet`, keep `--json` stdout clean):
243
+
244
+ - ok → `✓ community repo verified: <tag> (signed, <kid>)`
245
+ - else → `⚠ <path> is not a verified release of the official community repo
246
+ (<reason>). Loaded as an ordinary template repo — its @m0saic-dev/ ids are
247
+ refused. Get the official checkout: the link on m0saic.io/community` → the
248
+ reserved ids are refused in ONE collapsed line, `⚠ [template-repo]
249
+ TEMPLATE_ID_RESERVED_NAMESPACE: refused <N> ids in reserved namespace
250
+ (@m0saic-dev/…): <id1>, <id2>, <id3>, …` (other rejection codes stay one line
251
+ each) → the existing `❌ Refusing to use template repo` block → **exit 1**.
252
+
253
+ **`release.json` v1:** `{ schemaVersion: 1, tag, publishedAt, treeSha256,
254
+ signature: { kid, alg: "ed25519", sig: <base64url> } }` (`--unsigned` omits
255
+ `signature`; the legacy `{ tag, publishedAt }` reads as `missing-signature`).
256
+ Tree = every file under `dist/**` + `template-manifest.json` + `package.json`,
257
+ sorted POSIX paths, one `"<path>\0<sha256>\n"` line each; `*.json` hashed on
258
+ `JSON.stringify(JSON.parse(text))` (BOM stripped; CRLF / re-indent survive, a
259
+ key reorder does not), other files raw bytes; no symlink is ever followed.
260
+ Payload `"m0saic-community-release/v1\n<tag>\n<publishedAt>\n<treeSha256>\n"`
261
+ (`publishedAt` must be ISO 8601, else `malformed`). Ladder:
262
+ `missing-release → malformed → missing-signature → unknown-kid → bad-signature
263
+ → symlink-in-tree / foreign-modules → tree-mismatch → ok` — signature before
264
+ tree, so `tree-mismatch` = authentic release with edited dist/manifests,
265
+ `bad-signature` = forged `release.json`; the two structural refusals
266
+ (`symlink-in-tree`: ANY symlink under `dist/**`, `dist` itself or a signed
267
+ root file; `foreign-modules`: a `node_modules` dir at the root or under
268
+ `dist/`) sit between them and carry the offending path in the notice —
269
+ `(symlink-in-tree: dist/node_modules)`, `(foreign-modules: node_modules)`.
270
+ `publishedAt` is signed but there is still no anti-rollback (an older
271
+ authentic release verifies).
272
+
273
+ **A checkout never supplies `@m0saic/*` to itself.** The loader
274
+ (`@m0saic/platform` `template-repos/hostFirstResolution.ts`, installed by
275
+ `loadTemplateRepoFromPath` before the entry import) resolves every
276
+ `@m0saic/<pkg>[/<subpath>]` require whose parent file lies under a loaded
277
+ repo root from the HOST's own `node_modules` walk — the CLI's vendored copies,
278
+ the desktop's bundle, the workspace under dev — never from the checkout. So a
279
+ clone with nothing installed loads anywhere (0.2.0 is the first CLI that
280
+ can), and a `node_modules/@m0saic/template-utils` planted beside a tree is
281
+ never evaluated, via `--community-repo` or `--template-repo` alike. Every
282
+ other specifier (`sharp`, `@twemoji/svg`, relative paths) resolves as before,
283
+ from the checkout. CommonJS only — an `.mjs` entry's static `import`s use the
284
+ ESM resolver, which has no hook.
285
+
286
+ **Minting:** `scripts/sync-community-templates.mjs --tag <tag> --out <dir>` signs
287
+ with `M0SAIC_SIGNING_KEY_FILE` (REQUIRED — no default path; production laptop
288
+ only) under `M0SAIC_SIGNING_KID` (default `k1`), self-verifies against the
289
+ embedded keys, writes `.gitattributes` (`* -text`). Unset or missing key =
290
+ refused, never a silent unsigned release; `--unsigned` = loud banner, a snapshot
291
+ no shipped CLI trusts.
292
+
293
+ **DEV seam (workspace builds only, inside `if (DEV_MODE)`):** reasons
294
+ `missing-release` / `missing-signature` are still granted — `✓ community repo:
295
+ <path> (dev build: unsigned community checkout trusted)` (the in-repo
296
+ https://github.com/m0saic-project/m0saic-community-templates/blob/main has no `release.json`) — and
297
+ `M0SAIC_COMMUNITY_TRUST_KEY_PEM` (+ `M0SAIC_COMMUNITY_TRUST_KEY_KID`, default `k1`)
298
+ adds a public key for tests. Tampered tree, forged release or unknown kid are
299
+ refused even in dev. The tarball folds `process.env.M0SAIC_DEV` to `"0"` and the
300
+ audit bans the token, so none of this ships.
301
+
302
+ **Lookalikes are a display concern** — one stderr line for UNSIGNED repos, never a
303
+ refusal: `⚠ [template-repo] <subject> is not an official m0saic namespace — only
304
+ @m0saic/ and @m0saic-dev/ are, and they are signed.` Subject: loaded publisher
305
+ namespaces starting with `@m0saic` that are not exactly the two reserved ones →
306
+ else a repoId matching `/m0saic|mosaic/i` → else `"<displayName>" (<repoId>)`.
307
+ Never for repoId exactly `@m0saic-community`: via `--template-repo` that gets
308
+ `⚠ [template-repo] repo id "@m0saic-community" is reserved for the official
309
+ community repo and this checkout is unverified — loading it under ordinary
310
+ rules.` A `--community-repo` checkout that fails the gate gets ONLY the
311
+ not-verified notice.
312
+
313
+ **Desktop:** `installVersion()` runs `validateCommunityTree` → `verifyCommunityTree`
314
+ (inline-requires `@m0saic/product`) → refuses `tag-mismatch`; failure =
315
+ `{ ok:false, error: "downloaded tree rejected: release signature not verified
316
+ (<reason>): <detail>", reason }` on the existing IPC path; `installed.json` gains
317
+ `signedBy` + `treeSha256`; seam `installVersion({ releasePublicKeys })`. The
318
+ seed/installed load path (`loadCommunityRepo`) does not re-verify; consent gate
319
+ unchanged.
320
+
321
+ Help wording for `make` / `hello-world` / `list-templates` is ONE string,
322
+ test-locked (`__tests__/cli.flags.test.js`): "…Grants the @m0saic-dev/ namespace
323
+ when its release signature verifies (unsigned or modified checkouts load as
324
+ ordinary repos); the community checkout loads only this way".
325
+
326
+ ### Hardening (rounds 2 + 3, 2026-09-16)
327
+
328
+ **Host-first `@m0saic/*` resolution.** https://github.com/m0saic-project/m0saic-packages/blob/main/packages/platform/src/template-repos/hostFirstResolution.ts (wired in `loadTemplateRepoFromPath.ts` via `registerHostFirstRepoRoot(repoRoot)` before the native `import(file://…)` of the entry): ONE wrapper around Node's real `Module._resolveFilename` (found by walking the prototype chain — under Jest `node:module` exports a subclass copy), installed once per process, persistent so render-time lazy requires are covered. A request matching `/^@m0saic\/[^/\\]+(?:\/.*)?$/` whose requesting module lies under a registered root (realpath'd) is resolved from the HOST's own `node_modules` walk — never the checkout's. Everything else (relative paths, non-`@m0saic` deps, host code) is untouched. CJS only. Consequences: a community clone loads from ANY directory with no `node_modules` (before this, `--community-repo` only worked inside the monorepo); a planted `node_modules/@m0saic/*` beside a verified tree is never evaluated, via either flag; a starter's `file:` link to `@m0saic/template-utils` resolves to the same physical package — no drift.
329
+
330
+ **Fail-closed tree verification** (`inspectReleaseTree` in `communityRelease.ts`; desktop mirror `inspectCommunityTree` in the Mosaic Desktop / Web app source (not published), run by `installVersion`). A release tree is plain files and directories, nothing else. Refused, in this order after the signature check and before the hash compare: `symlink-in-tree` (ANY symlink — file or dir — under `dist/**`, `dist` itself, a signed root file, or ANY entry at the checkout root); `special-file-in-tree` (FIFO / socket / device anywhere in the same places — a FIFO named `dist/publishers.js` shadows the signed `dist/publishers/` dir and blocks or executes whoever opens it); `foreign-modules` (a `node_modules` dir at the root or under `dist/`, matched case-insensitively). `verifyCommunityRelease` lstats `release.json` before opening it (a FIFO planted there used to hang the verifier). The hasher THROWS `ReleaseTreeError` on a special entry rather than skipping it; `signCommunityRelease` refuses to sign such a tree. The signed payload is `"m0saic-community-release/v1\n<tag>\n<publishedAt>\n<treeSha256>\n"`. The CLI notice carries the detail for these reasons — `(foreign-modules: node_modules)`; for `special-file-in-tree` the CLI does NOT load the folder at all (an ordinary load provably hangs): notice + `❌ Refusing to use template repo` + exit 1.
331
+
332
+ **Community deps.** The `@m0saic-dev/community-m` pack (the only `sharp` / `@twemoji/svg` user) was deleted as defunct on 2026-09-16; both left the community `package.json` and BOTH allowlists (`dep-allowlist.json` ↔ `TEMPLATE_REPO_DEP_ALLOWLIST`, lockstep-tested). Community templates may import only what the hosts ship: `@m0saic/{types,template-utils,dsl-stdlib,platform,dsl}` + `node:path`. A native or third-party runtime dep is a founder decision. `sharp` survives only as a root devDependency for core/templates tests.
333
+
334
+ **Accepted residual.** A third-party `--template-repo` checkout with its own `node_modules` runs its own copies of non-`@m0saic` deps — third-party code, with the notice printed; it cannot obtain a reserved identity that way (adversary-verified). DEV builds (`M0SAIC_DEV=1`, compile-stripped from the tarball) still trust an unsigned checkout for `missing-release` / `missing-signature` only; a tampered or forged tree is refused even in dev.
335
+
336
+ ## Wireframe commands
337
+
338
+ `make-wireframe` / `make-wireframe-animated` take the layout as `--m0 <string>`
339
+ (inline DSL — the flag is `--m0`, not `--m0saic`) or `--mfile <path>`, plus the
340
+ same core/save/report/`--m0v`/`--output-pattern` surface as `make` (no encode
341
+ surface, no `--inputs`). Animated adds `--disable-ui` (index.ts:4504).
342
+
343
+ ## `m0saic open` — pointer only
344
+
345
+ The canonical protocol (candidate file naming, agent block, iteration ritual,
346
+ `--template` hot-reload loop) lives in
347
+ [https://github.com/m0saic-project/m0saic-sandbox/blob/main/packages/sandbox/agent.md](https://github.com/m0saic-project/m0saic-sandbox/blob/main/packages/sandbox/agent.md) — read that,
348
+ not this. Shape only: `open <file>` accepts `.m0 .m0c .m0p .m0v .mosaic
349
+ .mosaicx` and routes to Mosaic Desktop (auto-detects a running dev electron on
350
+ macOS; `--app <path>` override); `open --template <id> [--props/--props-file]`
351
+ opens the Make page via the loopback bridge, hot-reloading fresh
352
+ https://github.com/m0saic-project/m0saic-packages/blob/main/packages/templates/dist first (`--no-reload` to skip); `open --make
353
+ <file.mosaicx>` opens an authored `.mosaicx` in Make and adopts it for the
354
+ agent loop.
355
+
356
+ ## Failure modes
357
+
358
+ - `spawnSync m0saic EACCES` — the CLI isn't built/linked on this machine:
359
+ `cd the CLI source (not published) && npm run build && npm link`.
360
+ - No ffmpeg at all → `ffmpeg isn't detected.` (4 lines), exit 1, nothing
361
+ downloaded (`ensureRenderToolchain()`, index.ts ~404); run `m0saic setup`
362
+ (`npx m0saic setup` under npx), then re-run. A broken golden slot prints
363
+ `❌ Toolchain "gpl" (golden) is broken … Re-install it: m0saic setup --gpl`.
364
+ - Free tier re-encodes every deliverable (QR stamp wrap) — codecs/durations
365
+ change, breaking probe-based assertions. Check `npm run tier:status` first;
366
+ full rule in the maintainers' agent contract §7.7 (not published).
367
+ - Non-zero ffmpeg exits print a core-computed failure classification after the
368
+ exit-code line (`printFfmpegFailure`, index.ts:98) — read it before rerunning
369
+ with `--verbose`.
370
+ - Invalid m0 strings fail fast at validation (`validateM0String`), not inside
371
+ ffmpeg; `SPLIT_EXCEEDS_AXIS` means the split count exceeds the pixel axis —
372
+ see `docs/handbook/feasibility-precision-quantization.md`.
@@ -0,0 +1,117 @@
1
+ # FFmpeg Expression Limits — the parse-depth cliff (a phantom "OOM")
2
+
3
+ **Source of truth:** `rebalanceAdditiveChains` in
4
+ [https://github.com/m0saic-project/m0saic-packages/blob/main/packages/platform/src/ffexpr/balance.ts](https://github.com/m0saic-project/m0saic-packages/blob/main/packages/platform/src/ffexpr/balance.ts)
5
+ (`DEFAULT_MAX_FLAT_ADDITIVE_TERMS = 32`); the funnels that apply it —
6
+ `quoteEnableArg` in [https://github.com/m0saic-project/m0saic-packages/blob/main/packages/platform/src/ffexpr/ffmpeg.ts](https://github.com/m0saic-project/m0saic-packages/blob/main/packages/platform/src/ffexpr/ffmpeg.ts),
7
+ `buildOverlayAlphaFilter` + `buildCameraFilters` in `@m0saic/core`.
8
+ Verified against BtbN ffmpeg `N-124278-gcc3ca17127-20260430` (the pinned GPL build).
9
+
10
+ > Companion mental model: [`../skills/more-atoms-not-bigger-atoms.md`](../skills/more-atoms-not-bigger-atoms.md).
11
+ > This doc is the measured case study; that one is the general principle.
12
+
13
+ ---
14
+
15
+ ## The symptom (and why it lies)
16
+
17
+ FFmpeg's expression parser (`av_expr_parse`, `libavutil/eval.c`) has a **fixed
18
+ recursion budget (~100)**. When an emitted expression exceeds it, filtergraph
19
+ init fails with:
20
+
21
+ - **`Error initializing filters / Error : Cannot allocate memory` (−12)** — a
22
+ **phantom OOM**: deterministic, ~60 ms, completely independent of free RAM.
23
+ This is the additive-chain shape.
24
+ - or **`Missing ')' or too many args` → `Invalid argument` (−22) /
25
+ `Error reinitializing filters`** — the deeply-nested shape (e.g. a `crop`
26
+ focus expr).
27
+
28
+ The word "memory" is a lie. It is NOT commit pressure, graph size, filter
29
+ count, or a platform difference — all of which this failure repeatedly (and
30
+ expensively) misled debugging toward. It is one expression too deep.
31
+
32
+ ## The cliff (measured, term-count-driven — NOT chars)
33
+
34
+ - A **flat `a+b+c+…` chain fails at exactly 99 terms** (98 parses, 99 fails).
35
+ Character length is irrelevant: a 3.8 K-char / 98-term expr parses; a
36
+ 2.3 K-char / 99-term expr fails.
37
+ - **Genuine nesting** (nested `if(cond,…,else)` chains) fails at ~100 levels.
38
+ - Because ffmpeg spends one recursion level **per additive term at the same
39
+ nesting level**, both shapes share the same ~100 budget.
40
+
41
+ ## The two shapes → the two fixes
42
+
43
+ 1. **Flat additive chains** (`enable` window sums, the `overlay.enable`→`geq`
44
+ alpha fold, camera zoom/focus). Fixable by REGROUPING the same terms into a
45
+ **balanced binary tree** — `((a+b)+(c+d))…` — which keeps parse depth
46
+ O(log N) and is safe past 1024 terms. This is `rebalanceAdditiveChains`.
47
+ 2. **Deep nesting** (one nested `if()` level per keyframe). Rebalancing CANNOT
48
+ help — the builder must **emit a flat shape instead**. dsl-tutorial's camera
49
+ `focusExpr` was rewritten from nested `if(lt(t,…),…, else)` (depth 109 at
50
+ 10×10) to a **flat sum of disjoint window-gated smoothstep segments**
51
+ (`lt` head + `gte·lt` segments + `gte` tail).
52
+
53
+ ## Authoring rule
54
+
55
+ **No flat additive chain longer than ~32 terms, and no piecewise-over-time
56
+ expression built as a nested if-else chain, should ever reach a filtergraph.**
57
+ Build piecewise-in-time values as **flat gated sums**; long chains are
58
+ auto-rebalanced at the engine funnels below.
59
+
60
+ ## Engine enforcement (shipped 2026-07-02 — route new work through these)
61
+
62
+ `rebalanceAdditiveChains` (`@m0saic/platform` `ffexpr/balance.ts`, threshold
63
+ `DEFAULT_MAX_FLAT_ADDITIVE_TERMS = 32`) is applied automatically at every
64
+ expression funnel:
65
+
66
+ - `quoteEnableArg` — ALL `:enable='…'` sites (overlay, drawtext,
67
+ `compileEnableArg`).
68
+ - `buildOverlayAlphaFilter` — the `overlay.enable` → `geq` alpha fold.
69
+ - `buildCameraFilters` — zoom/focus chains.
70
+
71
+ Chains **≤ 32 terms pass through byte-identical** (goldens unaffected); only
72
+ longer chains are regrouped. Rebalancing fixes PARSE; it does not change eval
73
+ cost (see below).
74
+
75
+ **Known bypasses to watch:** hand-rolled lavfi strings that build `enable=`
76
+ outside the funnels — dsl-string caret-track `drawbox`, dsl-canvas `drawbox`
77
+ (both currently ≤3-term chains, safe) — and any template that still builds a
78
+ nested-`if` expression (e.g. a line-chart animation) which will hit the cliff
79
+ if its dataset grows. Keep per-filter chains small, or call
80
+ `rebalanceAdditiveChains` before joining.
81
+
82
+ ## The "graph-SIZE OOM" was this cliff wearing a trench coat (debunked)
83
+
84
+ A separate ≈130 K-chars / 9 000-filters "graph-size OOM" mode was once believed
85
+ to exist, and a `splitByGraphBudget` chunker was calibrated against it. **It does
86
+ not reproduce.** Direct verification:
87
+
88
+ - The exact 142 K canvas part it was calibrated on renders standalone at full
89
+ duration; so do a **12 000-chained-drawbox / 1.19 MB single graph** and a
90
+ 9 000-filter graph.
91
+ - Every recorded failure sidecar in `smokebatch/` (smoke9–29) is a command
92
+ whose EXPRESSIONS cross the parse cliff (flat ≥99 or nesting ≥109), failing in
93
+ <80 ms with −12/−22. **No sidecar ever names a size-heavy command.**
94
+
95
+ Graph chars merely *correlated* with expression bulk in that era's graphs. The
96
+ graph-size chunker was reverted. **Do not re-add a filtergraph-size split
97
+ chasing a "size OOM" — it is the parse cliff.** (The input-count split,
98
+ `maxInputsPerCommand`, is a separate and real argv/process-arg constraint and
99
+ stays.)
100
+
101
+ ## Note: the macOS "grind" is a different wall
102
+
103
+ The same dense graphs that fail fast on Windows (parse cliff) instead ran for
104
+ 20+ min on macOS. That was NOT the parse cliff (Mac's ffmpeg parses flat chains
105
+ fine) — it was **per-pixel `geq` eval cost** (O(terms) per pixel per plane per
106
+ frame). Rebalancing does not reduce it; density caps like `PULSE_TERM_CAP` and
107
+ representational fixes (SVG masks, pre-rendered intermediates) do. That's a
108
+ perf ceiling, not a crash — see the "more atoms" skill, atom 2.
109
+
110
+ There is also a *third*, mechanically distinct macOS wall on
111
+ conversion-/input-dense graphs (many `scale`/`format` nodes + many still
112
+ inputs, e.g. dsl-tutorial grids): **swscale thread-cap exhaustion**, where
113
+ per-conversion thread pools blow past `kern.num_taskthreads` and frames freeze
114
+ at t=0 or grind at ~1 fps. That one is neither parse depth nor `geq` eval cost —
115
+ that wall is engine-internal: `.ai/moat/runtime/macos-swscale-thread-cap.md`
116
+ (absent in the shipped copy; symptom summary: dense still-image canvases freeze
117
+ at t=0 / grind ~1 fps on macOS — the engine's threadGuard mitigates it).
@@ -0,0 +1,96 @@
1
+ # "Push complexity down" — reduce to 1, then bake the constant
2
+
3
+ Two moves, applied in order. Move 1 is structural (isolate complexity); move 2
4
+ is temporal (stop recomputing a constant). They compose: nest, prove the knobs,
5
+ then bake. The user's vocabulary for this is literally **"reduce to 1"**, **"push
6
+ complexity down"**, and **"bake that result to a flat video file."**
7
+
8
+ ## Move 1 — Reduce to 1 (nested source)
9
+
10
+ When a subtree is too complex or too precise, render it as its own **nested
11
+ source** (child mosaic document). In the parent it collapses to a single `F` —
12
+ one cell, one source ref. High-level layout stays simple; the complexity is
13
+ isolated one level down. It still renders every frame; you've only separated
14
+ concerns by precision tier (e.g. a px-baked, quantization-sensitive scatter vs.
15
+ resolution-independent chrome).
16
+
17
+ Mechanism: wrap the subtree as a child doc (`children[...]` + a
18
+ `{type:"mosaic", ref}` source) so the engine renders it into its own
19
+ framebuffer and the parent sees one `F`. Same primitive as nested-mosaic
20
+ clipping.
21
+
22
+ ## Move 2 — Bake the constant (flat asset)
23
+
24
+ When a subtree is **identical on every render** — a fixed background, a
25
+ decorative field, a logo sting with no data dependency — don't render it at
26
+ runtime at all. Pre-render it **once** and reference the result as a flat asset.
27
+
28
+ How:
29
+ 1. Author a one-off **`internal: true`** bake template whose only job is to
30
+ render that subtree full-canvas, opaque, at each target aspect (e.g.
31
+ `hero/ffmpeg-pulse/scatter-bake/v1`).
32
+ 2. `m0saic make` it once per aspect → small `.mp4` / `.png` into the pack's
33
+ `_shared/assets/` (mirror to `dist` via `build:templates`).
34
+ 3. In the real template, replace the nested subtree with one
35
+ `{type:"media", mediaType:"video"|"image", assetId, placement:{fit:"cover"}}`
36
+ source, picking the asset by aspect (`scatter-${variantKey}.mp4`).
37
+ 4. Update the unit test: assert the `assets.<name>` ref + the `mediaType`, and
38
+ DROP the now-stale "child carries N tiles / animates" assertions.
39
+
40
+ Determinism still holds: a baked asset is a committed fixture, byte-identical
41
+ every run. Animation is fine to bake — the reveal/motion lives in the video.
42
+
43
+ ## When to bake (the trigger)
44
+
45
+ > "When you notice DSL count is high **and** render is long, and the result is
46
+ > intended to be the same every time — bake it to a flat file."
47
+
48
+ All three must hold:
49
+ - **High DSL / long render** — the subtree dominates `dsl-complexity.md` metrics
50
+ or wall-clock.
51
+ - **Constant across renders** — no prop/data dependency; same pixels every time.
52
+ (If it varies with data, you cannot bake it — keep it live or reduce to 1.)
53
+ - **Knobs are locked** — bake LAST, after the look is signed off. Baking freezes
54
+ the subtree; re-tuning means re-baking.
55
+
56
+ ## When NOT to bake
57
+
58
+ - The subtree depends on props/data (varies per render) → it's not constant.
59
+ - The look isn't locked yet → premature; you'll re-bake every iteration.
60
+ - The subtree is cheap → baking adds an asset + a build step for no win.
61
+
62
+ ## Measured payoff (FFmpeg-pulse title beat, 2026-06-23)
63
+
64
+ The title beat's signature background is a ~225-rect scatter with an L→R reveal —
65
+ the same on every render. Baking it to a flat per-aspect `.mp4`:
66
+
67
+ | Metric | Live scatter (inlined) | Baked scatter (1 video src) | Change |
68
+ |---|---|---|---|
69
+ | Wall-clock render (1920×1080, 9 s) | ~5 min | 66 s | ~4.5× faster, ~78% less |
70
+ | Flattened `.mosaic` size | ~163 K chars | 3,838 chars | ~98% smaller |
71
+ | Flattened source count | 238 | 14 | ~94% fewer |
72
+ | Baked asset size | — | ~98–155 KB / aspect | one-time, reused |
73
+
74
+ One-time bake cost ~3 m 21 s/aspect, amortized over every future render (it pays
75
+ for itself after ~1 render). The residual 66 s is the **chrome-overlay floor** —
76
+ the variable, prop-driven work that legitimately stays live. The bake removed the
77
+ *constant* cost and left only the *variable* cost; that's the whole point.
78
+
79
+ ## Trade-offs
80
+
81
+ - **Inline:** one flat doc, simplest pipeline; fat parent m0, mixed precision
82
+ tiers, depth accrues toward the ~25-layer overlay mask ceiling (see
83
+ `feasibility-precision-quantization.md` and the overlay-depth limit), full cost
84
+ every render.
85
+ - **Reduced to 1 (nested):** clean parent, isolated complexity, dodges the mask
86
+ ceiling; still full render cost (the subtree still renders).
87
+ - **Baked (flat asset):** near-zero per-render cost for that subtree, tiny
88
+ parent; but adds a committed asset + a one-off bake template + a build step, and
89
+ the subtree is frozen (re-tune ⇒ re-bake).
90
+
91
+ ## Adjacent
92
+
93
+ `dsl-complexity.md` (metrics → *when*), `feasibility-precision-quantization.md`
94
+ (precision tiers + the mask ceiling), nested-mosaic clipping (the move-1
95
+ mechanism). The shipped reference for a constant-subtree bake is
96
+ https://github.com/m0saic-project/m0saic-packages/blob/main/packages/templates/src/m0saic/hero/ffmpeg-pulse/scatter-bake/v1.
@@ -0,0 +1,40 @@
1
+ # skills/ — DSL & engine mental models
2
+
3
+ Operational skills layered on the handbook. **The handbook is canonical** — a doc
4
+ here that restates grammar is a checklist form of the same rules, and on any
5
+ conflict [`../handbook/`](../handbook/README.md) wins.
6
+
7
+ ## Grammar & construction (emitting valid m0)
8
+
9
+ - [`structural-construction.md`](structural-construction.md) — the build-time
10
+ rule checklist (count-exactness, overlay discipline, trailing-passthrough).
11
+ - [`m0saic-string-generation.md`](m0saic-string-generation.md) — programmatic
12
+ generation: builders first, unified transforms, validate, repair loop.
13
+ - [`dsl-stdlib-method-catalog.md`](dsl-stdlib-method-catalog.md) — pointer to
14
+ the full stdlib catalog (colocated with the package).
15
+
16
+ ## Geometry & semantics (what the parser does)
17
+
18
+ - [`axis-and-geometry.md`](axis-and-geometry.md) — split axes, `splitEven`
19
+ remainders, rect propagation.
20
+ - [`passthrough-semantics.md`](passthrough-semantics.md) — `0` donation runs,
21
+ claimants, carry scoping.
22
+ - [`overlay-semantics.md`](overlay-semantics.md) — **the overlay anchor**:
23
+ attachment, bodies, deferred paint order.
24
+ - [`zero-overlay-analysis.md`](zero-overlay-analysis.md) — the `0{}` phantom
25
+ operator (rarely needed; read overlay-semantics first).
26
+
27
+ ## Identity & parsing
28
+
29
+ - [`identity.md`](identity.md) — StableKey, the five axes, selection policies.
30
+ - [`parse-apis.md`](parse-apis.md) — which parse API to use + the
31
+ frames→sources bridge.
32
+
33
+ ## Authoring surfaces
34
+
35
+ - [`labels-and-masks.md`](labels-and-masks.md) — labels/masks across
36
+ dictionary → generator → editor → disk.
37
+ - [`text-in-templates.md`](text-in-templates.md) — text sources, rasterizer
38
+ choice, sizing traps.
39
+ - [`more-atoms-not-bigger-atoms.md`](more-atoms-not-bigger-atoms.md) — the
40
+ ffmpeg scaling contract (capacity grows with atom COUNT).