@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,77 @@
1
+ # More atoms, never bigger atoms — the scaling contract with ffmpeg
2
+
3
+ > The measured cliffs behind atom 1 (and the "graph-size OOM" debunk) live in
4
+ > [`../runtime/ffmpeg-expression-limits.md`](../runtime/ffmpeg-expression-limits.md).
5
+ > This is the general principle; that is the case study.
6
+
7
+ m0saic renders arbitrarily complex templates by splitting work into pieces.
8
+ That works because ffmpeg capacity scales with the NUMBER of things it is
9
+ given (commands, inputs, filters, files). It breaks when a feature grows a
10
+ single UNPARTITIONABLE ATOM with content. Chunking cannot rescue a bigger
11
+ atom — the atom is smaller than a command.
12
+
13
+ ## The four atoms (with measured walls)
14
+
15
+ 1. **One expression string.** `av_expr_parse` has a ~100 recursion budget:
16
+ ~98 flat `+` terms or ~100 nesting levels, independent of char length.
17
+ Failure is a phantom `Cannot allocate memory` (−12) or `Invalid
18
+ argument` (−22) at graph init, in <80 ms. Applies to EVERY expression:
19
+ `enable`, `geq`, crop/scale/drawtext params, `%{eif}`.
20
+ 2. **One filter's per-frame work.** `geq` interprets its expression per
21
+ pixel per plane per frame (nodes × W × H × fps). No crash — render time
22
+ explodes. Chunking does not reduce it: same pixels either way.
23
+ 3. **One frame's memory.** frames-in-flight × W × H × 4 B. Buffering
24
+ filters (`loop=size=N`, `split`, framesync queues, `tpad`) and
25
+ resolution multiply it; filter COUNT does not (12,000 chained filters /
26
+ 1.19 MB single graph verified fine).
27
+ 4. **One process's argv.** Windows caps ~32 K → `ENAMETOOLONG`.
28
+
29
+ ## The classification test (apply to any content-scaled feature)
30
+
31
+ When something scales with content (N tiles, steps, keyframes, data
32
+ points), ask: **which quantity grows?**
33
+
34
+ - Count of commands / inputs / filters / workspace files grows → MORE
35
+ ATOMS → fine; capacity, chunkable.
36
+ - A single expression, a single filter's per-pixel cost, a single frame's
37
+ resident bytes, or argv grows → BIGGER ATOM → redesign before shipping.
38
+
39
+ ## Bigger-atom anti-patterns → more-atoms replacements
40
+
41
+ | anti-pattern (bigger atom) | replacement (more atoms / bounded atom) |
42
+ |---|---|
43
+ | nested `if(cond,…,else)` per keyframe (`expr = if(...,${expr})` builders) | flat gated sum: `lt(t,t0)*(v0)+(gte·lt)*seg+…+gte(t,tN)*(vN)` |
44
+ | `.join("+")` on a content-scaled window list, emitted raw | route through the funnels (below) or `rebalanceAdditiveChains` |
45
+ | `geq` on a canvas/panel-sized stream | static SVG/PNG mask + `alphamerge`; keep geq on small intermediates |
46
+ | animated values as ever-larger exprs | pre-render to a small intermediate (value `.mov`s, text renders) |
47
+ | content-scaled string inline in argv (`-i lavfi`, drawtext text) | workspace file (`-filter_complex_script`, `-graph_file`, `textfile=`) |
48
+ | full-res lossless-alpha (qtrle/argb) intermediates on opaque nodes | alpha intermediates only where alpha flows |
49
+
50
+ ## Existing enforcement (route new work through these; do not bypass)
51
+
52
+ - `rebalanceAdditiveChains` (`@m0saic/platform` ffexpr) auto-applied at the
53
+ funnels: `quoteEnableArg` (all `:enable=`), `buildOverlayAlphaFilter`
54
+ (enable→geq alpha fold), `buildCameraFilters` (zoom/focus). >32 flat
55
+ terms → balanced tree (O(log n) depth, verified past 1024 terms); ≤32
56
+ passes byte-identical. It CANNOT fix nesting — builders must emit flat
57
+ shapes.
58
+ - Input-count split (`maxInputsPerCommand`) — more commands per node.
59
+ - Graph/text file lowering — argv atom stays constant.
60
+ - SVG rounding rasterizer (default) — rounding is a mask input, not geq.
61
+
62
+ Known bypasses to watch: hand-rolled lavfi `enable=` strings (dsl-string
63
+ caret track, dsl-canvas drawbox — currently ≤3 terms) and nested-if
64
+ builders in other templates (e.g. line-chart anim) that will hit atom 1 if
65
+ their datasets grow.
66
+
67
+ ## Review greps
68
+
69
+ `join("+")` on generated arrays; recursive expr accumulation
70
+ (`= \`if(…,${expr})\``); `geq` fed by canvas-sized labels; content
71
+ interpolation into `-i`/argv rather than a workspace file.
72
+
73
+ ---
74
+
75
+ *Per-pixel eval cost (atom 2) is the next practical wall at denser grids — a
76
+ perf ceiling, not a crash; fix it representationally (masks, pre-rendered
77
+ intermediates), not by chunking.*
@@ -0,0 +1,194 @@
1
+ ## Overlay Semantics & Paint Order
2
+
3
+ Overlays are recursive layout subtrees rendered inside an owner's rect — a first-class
4
+ compositional primitive, not just "layers". This doc covers attachment, paint order,
5
+ deferred zero-overlay paint, the overlay-body validity rule, and composition at scale.
6
+
7
+ > **Source of truth:** overlay parsing in [https://github.com/m0saic-dsl/m0/blob/main/packages/dsl/src/parse/m0StringParser.ts](https://github.com/m0saic-dsl/m0/blob/main/packages/dsl/src/parse/m0StringParser.ts);
8
+ > overlay-body validation in [https://github.com/m0saic-dsl/m0/blob/main/packages/dsl/src/validate/m0StringValidator.ts](https://github.com/m0saic-dsl/m0/blob/main/packages/dsl/src/validate/m0StringValidator.ts)
9
+ > (relaxation comment at lines 479–486); error codes in [https://github.com/m0saic-dsl/m0/blob/main/packages/dsl/src/errors/errors.ts](https://github.com/m0saic-dsl/m0/blob/main/packages/dsl/src/errors/errors.ts).
10
+ > Every accept/reject example below was runtime-verified against https://github.com/m0saic-dsl/m0/blob/main/packages/dsl/dist
11
+ > via `validateM0String` on 2026-07-27.
12
+
13
+ ## Attachment: any node can own an overlay
14
+
15
+ Form:
16
+
17
+ node{ overlay_body }
18
+
19
+ `node` may be ANY node kind — not just `1`:
20
+
21
+ - `1` (tile), `0` (passthrough), `-` (null — child position only; see Logical Owner)
22
+ - `N(...)` / `N[...]` (splits)
23
+ - a node that is itself an overlay owner (chains)
24
+
25
+ No base `1` is required for an overlay to exist. A chain of full layouts is legal:
26
+
27
+ 2(1,1){3(1,1,1){2(1,1)}} — base 2-col, overlay 3-col, overlay-on-overlay 2-col
28
+
29
+ Rules:
30
+
31
+ - At most ONE immediate overlay object per node — `1{1}{1}` rejects
32
+ (`OVERLAY_CHAIN`). Stack by nesting instead: `1{1{1}}`.
33
+ - The overlay body must validate independently as a full m0 string, recursively.
34
+ - Nesting depth is structurally unbounded — but see the render-cost caveat below.
35
+
36
+ Valid: `1{1}` · `2(1,1){1}` · `1{1{1}}` · `2(1,1){3(1,1,1){2(1,1)}}`
37
+
38
+ ## Overlay rect = owner rect
39
+
40
+ The overlay subtree is parsed against owner.x / owner.y / owner.width / owner.height.
41
+
42
+ - It does NOT inherit sibling geometry.
43
+ - It does NOT escape its parent rect.
44
+
45
+ Applies to tile, group, null, and zero overlays alike.
46
+
47
+ ## Overlays never mutate geometry
48
+
49
+ Overlay stacking, at any depth:
50
+
51
+ - does NOT change splitEven results or sibling sizes
52
+ - does NOT change structuralDepth
53
+ - does NOT alter stableKeys of the base structure
54
+
55
+ It only increments `overlayDepth` and adds paint layers. The base layout defines
56
+ geometry; overlay chains define drawing.
57
+
58
+ ## Paint order
59
+
60
+ Deterministic, defined by engine traversal.
61
+
62
+ ### Tile overlay
63
+
64
+ 1{1}
65
+
66
+ 1. Base tile
67
+ 2. Overlay subtree (immediately after owner)
68
+
69
+ ### Group overlay
70
+
71
+ 2(1,1){1}
72
+
73
+ 1. Child 0
74
+ 2. Child 1
75
+ 3. Group overlay — group overlays paint AFTER all base children.
76
+
77
+ ### Zero overlay (deferred paint)
78
+
79
+ 3(0{1},1,1)
80
+
81
+ Zero overlays do NOT paint immediately; they defer until the claimant renders:
82
+
83
+ 1. Claimant tile
84
+ 2. Deferred zero overlay
85
+
86
+ Multiple zero overlays in one run:
87
+
88
+ 3(0{1},0{1},1)
89
+
90
+ 1. Claimant
91
+ 2. Larger-area zero overlay
92
+ 3. Smaller-area zero overlay (top-most)
93
+
94
+ Deferred zero overlays are sorted by area DESCENDING → smaller overlays paint on top.
95
+
96
+ ### Claimant with its own overlay
97
+
98
+ 3(0{1},1{1},1)
99
+
100
+ 1. Claimant tile
101
+ 2. Claimant's own overlay
102
+ 3. Deferred zero overlay — always after the claimant AND the claimant's overlay.
103
+
104
+ ### Nested overlays
105
+
106
+ 1{1{1}}
107
+
108
+ 1. Base tile
109
+ 2. First overlay
110
+ 3. Nested overlay
111
+
112
+ ## Logical owner `-`
113
+
114
+ `-{X}` marks `isLogicalOwner = true`: the base does not render, but the overlay rect
115
+ exists and carry is still absorbed if it is a claimant. Use for editor semantics and
116
+ labeling containers.
117
+
118
+ Position matters: `-{...}` is valid in a child slot or inside an overlay body, NOT as
119
+ the whole-string root — `2(-{1},1)` and `1{-{1}}` accept; bare `-{1}` rejects
120
+ (`TOKEN_RULE`), as does a bare `-` root (`INVALID_EMPTY`).
121
+
122
+ ## Overlay depth vs structural depth
123
+
124
+ Two independent counters:
125
+
126
+ - `structuralDepth` — increases entering numeric containers; root = 0.
127
+ - `overlayDepth` — increases entering `{...}`; base layout = 0, first overlay = 1.
128
+
129
+ Overlay frames inherit the structuralDepth of their context and use a separate frame
130
+ namespace.
131
+
132
+ ## Overlay body node rule (`INVALID_EMPTY`)
133
+
134
+ An overlay body `{...}` must contribute at least ONE node to the graph, but does not
135
+ have to paint. Logical-owner anchors (`-{X}`) are the common shape.
136
+
137
+ Rejected — zero nodes, or a single root with nothing to act on:
138
+
139
+ 1{} — empty body; no nodes at all
140
+ 1{0} — bare passthrough at the overlay root; nothing to donate to
141
+ 1{-{}} — recursive: the inner `{}` is rejected (deepest offender reported)
142
+
143
+ Accepted — at least one node, even if nothing paints:
144
+
145
+ 1{-} — single null node; logical-owner anchor
146
+ 1{2(-,-)} — all-null split; structural nodes, no paint
147
+ 1{2(0,-)} — passthrough with a sibling to donate space to
148
+ 1{-{-}} — nested logical owners
149
+ 1{-{2(0,-)}} — nested with structural content
150
+ 1{2(-,1)} — overlay body has a source (always valid)
151
+ 1{2[1,1]{1}} — nested overlay body has a source
152
+
153
+ The legacy `ZERO_SOURCE_OVERLAY` rule (body must contain at least one leaf `1`) was
154
+ relaxed 2026-06-03 — overlay bodies no longer have to paint, only exist as nodes
155
+ ([`m0StringValidator.ts`](https://github.com/m0saic-dsl/m0/blob/main/packages/dsl/src/validate/m0StringValidator.ts):479–486).
156
+ The whole-string `NO_SOURCES` check still requires the OUTER (root) layout to produce
157
+ at least one source tile, so the renderer is never asked to draw a fully empty canvas.
158
+
159
+ Error code when rejected: `INVALID_EMPTY` (kind `SYNTAX`).
160
+
161
+ ## Composition at scale: deep overlay stacks
162
+
163
+ Overlay chains are how large rect sets are composed: each `{}` wraps a full valid
164
+ subtree, and each layer paints inside the owner's rect. A 109-overlay brand mark —
165
+ each overlay an independently calculated m0saic drawing one precise rectangle — is a
166
+ measured working example; its serialized string ran to tens of thousands of
167
+ characters, which parse/validate/walk handle without issue.
168
+
169
+ Deep stacks are editor-clean: each overlay is its own layer, filterable by
170
+ `overlayDepth`, structurally independent, with no geometry coupling.
171
+
172
+ Use deep overlays for programmatic rect emission — dictionary-based layouts, glyphs,
173
+ masks, strict layering control. Prefer a single split when it expresses intent more
174
+ clearly or raw-string readability matters.
175
+
176
+ > ⚠️ **Render-cost caveat** (measured 2026-07-02): depth is free structurally, NOT at
177
+ > render time. Each overlay level is one sequential blend pass on ffmpeg's single
178
+ > graph thread, and past ~25 nested overlay layers inline-masks silently degrade
179
+ > (circles→squares, text→tofu) — engine wall W3. Deep stacks are cheap to WRITE
180
+ > and inspect, not cheap to RENDER — collapse time-disjoint line geometry into
181
+ > lavfi tracks first. Author-facing rules:
182
+ > [`../templates/patterns/perf-authoring-rules.md`](../templates/patterns/perf-authoring-rules.md)
183
+ > (R6). Deep dives are engine-internal: `.ai/moat/runtime/ffmpeg-limitations.md`,
184
+ > `.ai/moat/runtime/render-cost-model.md` — absent in the shipped copy.
185
+
186
+ ## Invalid constructions (verified, with codes)
187
+
188
+ 1{} — INVALID_EMPTY (empty body)
189
+ 1{0} — INVALID_EMPTY (bare passthrough body)
190
+ 1{-{}} — INVALID_EMPTY (recursive)
191
+ 1{1}{1} — OVERLAY_CHAIN (two immediate overlays; nest instead)
192
+ 1{{1}} — TOKEN_RULE (no owner token for the inner overlay)
193
+ 1{2(1,0)} — PASSTHROUGH_TO_NOTHING (trailing `0` inside overlay body)
194
+ -{1} — TOKEN_RULE (`-{...}` cannot be the whole-string root)
@@ -0,0 +1,79 @@
1
+ # Choosing the right parse API (and mapping frames → sources)
2
+
3
+ > **Source of truth:** [https://github.com/m0saic-dsl/m0/blob/main/packages/dsl/src/parse/m0StringParser.ts](https://github.com/m0saic-dsl/m0/blob/main/packages/dsl/src/parse/m0StringParser.ts).
4
+ > All four parse exports re-exported from `@m0saic/dsl`. (A fifth,
5
+ > `parseM0StringToFullGraphWithTraversal`, was removed in the public-API prune —
6
+ > use `parseM0StringComplete` with `opts.trace`.)
7
+
8
+ ## The canonical entry point: `parseM0StringComplete`
9
+
10
+ `parseM0StringComplete(input, width, height, opts?)` returns (on success):
11
+
12
+ - `ok: true`
13
+ - `ir.renderFrames` — RenderFrame[] in **paint order**
14
+ - `ir.editorFrames` — EditorFrame[] (full structural graph, always present)
15
+ - `ir.traversal` — optional DFS event stream if `opts.trace`
16
+ - `precision`, `resolutionDiagnostics`, `warnings`
17
+
18
+ Frames are one level deep under **`ir`**, not top-level. On failure:
19
+ `{ ok: false, error, precision, warnings }`. Other options:
20
+ `opts.materialize: "renderOnly"` (the cheap mode `parseM0StringToLogicalFrames`
21
+ rides on) and `opts.precisionNorm` (default 100; gates `PRECISION_EXCEEDS_NORM`
22
+ warnings). Prefer Complete for tooling, editor logic, anything non-trivial.
23
+
24
+ ## The three cheaper tiers
25
+
26
+ | Tier | API | Returns | Use for |
27
+ |---|---|---|---|
28
+ | 1 | `parseM0StringToLogicalFrames(s, w, h)` | rendered leaves only, **logical order**; each has `rect`, `logicalIndex` (array index === logicalIndex), `meta` | template tile mapping — `cell-<logicalIndex>`, labels indexing, "Nth tile" logic |
29
+ | 2 | `parseM0StringToRenderFrames(s, w, h)` | renderable frames in **paint order**; each has `rect`, `paintOrder`, `logicalIndex` | engine paint plan / ffmpeg stacking |
30
+ | 3 | `parseM0StringToFullGraph(s, w, h)` | EVERYTHING as EditorFrame[] — roots, groups, leaves, passthroughs, nulls, overlay subtrees | editor UIs, structural analysis |
31
+
32
+ Key invariant (Tier 2): `paintOrder` is the compositing order; `logicalIndex` is
33
+ the logical tile index. There is **no `sourceIndex` field** — build sources by
34
+ `logicalIndex`, paint by `paintOrder`.
35
+
36
+ Common mistakes the wrong tier causes: confusing paint order with logical order,
37
+ attaching sources to the wrong index, losing overlays/group nodes in a UI.
38
+
39
+ ## Identity on every frame
40
+
41
+ All frame views carry `meta` identity: deterministic StableKey, structuralDepth,
42
+ overlayDepth (separate axes; overlay keys live in the `/ov{depth}c{k}` namespace and
43
+ never shift structural keys). The full contract: [`identity.md`](identity.md).
44
+
45
+ ## Frames → sources → children (the template bridge)
46
+
47
+ The standard pattern for turning geometry into a `MosaicDocument`:
48
+
49
+ 1. Parse the layout — `parseM0StringToLogicalFrames` for leaf mapping. The public
50
+ frames APIs already exclude null-render nodes (the parser filters
51
+ `nullRender` internally — you do NOT hand-filter holes/containers).
52
+ 2. For each frame: create a child renderable (often
53
+ `renderNestedTemplate(cellTemplateId, props, ctx)` —
54
+ https://github.com/m0saic-project/m0saic-packages/blob/main/packages/template-utils/src/render/renderNestedTemplate.ts#L35), attach it under
55
+ `children[id]`, push a `sources[]` entry referencing it.
56
+ 3. Return `{ m0: <same layout>, config.sources, children }` (the document's layout
57
+ field is `m0` — https://github.com/m0saic-project/m0saic-packages/blob/main/packages/types/src/document/document.ts#L132).
58
+
59
+ Rules that keep the bridge deterministic:
60
+
61
+ - **Child IDs come from `frame.logicalIndex`** (e.g. `cell-${logicalIndex}`) — or
62
+ `frame.stableKey` when identity must survive structural edits. Never bare loop
63
+ order. ([`identity.md`](identity.md) owns the when-to-use-which.)
64
+ - **Size and time from `ctx.target`, never `ctx.output`** — `ctx.output` is
65
+ format/codec/alpha only ([`../templates/rendering-model-contract.md`](../templates/rendering-model-contract.md) Rule 5b).
66
+ - **Internal cell templates** (`internal: true`, id `…/cell/v1`) are the reusable
67
+ building block for per-tile content — wireframe-style label/debug tiles are the
68
+ canonical example.
69
+ - Props schemas: use `definePropsSchema` with typed defaults. (There is no
70
+ `constraints.isM0saicLayout` — an older revision of this guidance invented it;
71
+ the real constraint set is `MosaicPropConstraints`,
72
+ https://github.com/m0saic-project/m0saic-packages/blob/main/packages/types/src/template/template.ts#L108-146`.)
73
+
74
+ ## Quick decision table
75
+
76
+ - Engine paint plan / ffmpeg stacking → `parseM0StringToRenderFrames`
77
+ - Template tile mapping / "Nth tile" logic → `parseM0StringToLogicalFrames`
78
+ - Full structural tree / editor UI → `parseM0StringToFullGraph`
79
+ - Traversal events, or anything multi-view → `parseM0StringComplete` (+ `opts.trace`)
@@ -0,0 +1,136 @@
1
+ ## Passthrough & Carry Semantics
2
+
3
+ `0` (passthrough) is the zero-frame donation primitive. Misused, a layout is either
4
+ invalid (`PASSTHROUGH_TO_NOTHING`) or geometrically wrong. This doc covers donation
5
+ mechanics, claimant/carry scoping, overlay behavior on zero-runs, the trailing rule,
6
+ and the weighted-split encoding.
7
+
8
+ > **Source of truth:** passthrough handling in [https://github.com/m0saic-dsl/m0/blob/main/packages/dsl/src/parse/m0StringParser.ts](https://github.com/m0saic-dsl/m0/blob/main/packages/dsl/src/parse/m0StringParser.ts)
9
+ > and validator rules in [https://github.com/m0saic-dsl/m0/blob/main/packages/dsl/src/validate/m0StringValidator.ts](https://github.com/m0saic-dsl/m0/blob/main/packages/dsl/src/validate/m0StringValidator.ts).
10
+ > Trailing-passthrough error code `PASSTHROUGH_TO_NOTHING` in [`errors/errors.ts`](https://github.com/m0saic-dsl/m0/blob/main/packages/dsl/src/errors/errors.ts).
11
+ > Validity examples below runtime-verified against https://github.com/m0saic-dsl/m0/blob/main/packages/dsl/dist on 2026-07-27.
12
+
13
+ ## What `0` means
14
+
15
+ `0`:
16
+
17
+ - Consumes a split slot
18
+ - Produces no rendered frame
19
+ - Donates its space forward to the next claimant
20
+
21
+ It does NOT render. It only contributes geometry.
22
+
23
+ > Design intent: `0` exists primarily to encode weighted / percentage splits in a
24
+ > deterministic integer-only way.
25
+
26
+ ## What is a claimant?
27
+
28
+ A claimant is the first non-zero node after a run of `0`s:
29
+
30
+ - `1` (tile)
31
+ - `-` (null)
32
+ - `N(...)` / `N[...]` (group)
33
+
34
+ When a claimant appears:
35
+
36
+ - It absorbs ALL donated space from the run
37
+ - It absorbs its own slot
38
+ - Its position snaps back to the start of the run
39
+
40
+ ## Donation run example
41
+
42
+ 3(0,0,1)
43
+
44
+ - First `0` → carry = 1 slot
45
+ - Second `0` → carry = 2 slots
46
+ - `1` appears → absorbs carry + own slot = 3 slots
47
+
48
+ Result: the tile spans the entire width of the classifier.
49
+
50
+ ## Carry is scoped per classifier
51
+
52
+ Carry does NOT leak outside the classifier body, and resets after each claimant:
53
+
54
+ 7(0{1},1,0{1},1,1,1,1)
55
+
56
+ Two separate runs:
57
+
58
+ - Run 1: `0{1}` starts carry → first `1` absorbs the run
59
+ - Run 2: new `0{1}` → fresh carry → next `1` absorbs it
60
+
61
+ ## Overlay behavior on `0`
62
+
63
+ ### `0{...}` targets the merged region so far
64
+
65
+ 4(0,0{2(1,1)},0,1)
66
+
67
+ 1. First `0` — carry = 1 slot
68
+ 2. Second `0{2(1,1)}` — carry = 2 slots; overlay canvas = merged 2-slot region
69
+ 3. Third `0` — carry = 3 slots
70
+ 4. `1` — absorbs 4 slots total
71
+
72
+ Overlay size is fixed at the moment it appears. Later carry growth does NOT resize
73
+ earlier overlays.
74
+
75
+ ### Multiple zero overlays grow monotonically
76
+
77
+ 5(0{1},0{1},0,0,1)
78
+
79
+ - First `0{1}` → overlay spans 1 slot
80
+ - Second `0{1}` → overlay spans 2 slots
81
+ - Remaining `0`s → carry grows
82
+ - `1` absorbs all 5
83
+
84
+ Each overlay anchors to the region size at its creation time. Zero-overlay prefix
85
+ geometry as a deliberate technique (progressive region staging, area-ordered
86
+ layering) has its own doc: [`zero-overlay-analysis.md`](zero-overlay-analysis.md).
87
+
88
+ ## Trailing passthrough rule (`PASSTHROUGH_TO_NOTHING`)
89
+
90
+ A trailing `0` inside a classifier is **always** invalid — even if it carries an
91
+ overlay. No claimant absorbs the donation; an overlay on `0` does not change that.
92
+
93
+ Invalid:
94
+
95
+ 2(1,0) — bare trailing passthrough
96
+ 2(1,0{1}) — trailing passthrough with overlay (still invalid)
97
+ 3(1,1,0{1}) — trailing passthrough with overlay
98
+ 0{1} — whole-string passthrough: no sibling exists to claim
99
+
100
+ Valid:
101
+
102
+ 2(0,1) — passthrough donates to claimant
103
+ 2(0{1},1) — passthrough with overlay, not trailing
104
+ 3(1,0,1) — passthrough in middle position
105
+
106
+ The rule applies recursively inside nested classifiers AND overlay bodies:
107
+ `1{2(1,0)}` rejects with `PASSTHROUGH_TO_NOTHING`.
108
+
109
+ ## Weighted split encoding
110
+
111
+ Zero-runs are how weighted splits are encoded. To encode a 60/40 split:
112
+
113
+ [60% region][40% region]
114
+
115
+ DSL representation (conceptual pattern):
116
+
117
+ 100( 59 zeros, 1, 39 zeros, 1 )
118
+
119
+ The zero-run before each `1` encodes how much space that tile absorbs. Mechanical
120
+ and deterministic.
121
+
122
+ ### The stdlib GCD-reduces this for you
123
+
124
+ Never hand-roll the 100-slot form. `weightedSplit` (and the unified `split`
125
+ transform's `weights` option) default to `mode: "optimized"`, which divides all
126
+ weights by their GCD before emitting slots:
127
+
128
+ weightedSplit([60, 40], "col") → 5(0,0,1,0,1) — not 100(...)
129
+ weightedSplit([50, 25, 25], "col") → 4(0,1,1,1)
130
+
131
+ Verified 2026-07-27: GCD reduction in
132
+ [https://github.com/m0saic-dsl/m0/blob/main/packages/dsl-stdlib/src/builders/weightedSplit.ts](https://github.com/m0saic-dsl/m0/blob/main/packages/dsl-stdlib/src/builders/weightedSplit.ts):119–122;
133
+ the `split` transform routes through the same reduction in
134
+ [`buildSplitFragment.ts`](https://github.com/m0saic-dsl/m0/blob/main/packages/dsl-stdlib/src/transforms/primitives/split/_internal/buildSplitFragment.ts)
135
+ (default `mode = "optimized"`). `mode: "literal"` keeps raw weights (legacy);
136
+ `precision` rescales weights to an exact slot budget first (largest-remainder).
@@ -0,0 +1,86 @@
1
+ # Structural construction — emitting valid canonical m0
2
+
3
+ Operational construction rules. The Handbook is canonical for grammar
4
+ ([`../handbook/dsl-rules.md`](../handbook/dsl-rules.md)) — this doc is the build-time
5
+ checklist form of the same rules; on any conflict, the handbook wins.
6
+
7
+ > **Source of truth:** [https://github.com/m0saic-dsl/m0/blob/main/packages/dsl/src/validate/m0StringValidator.ts](https://github.com/m0saic-dsl/m0/blob/main/packages/dsl/src/validate/m0StringValidator.ts)
8
+ > and [https://github.com/m0saic-dsl/m0/blob/main/packages/dsl/src/errors/errors.ts](https://github.com/m0saic-dsl/m0/blob/main/packages/dsl/src/errors/errors.ts).
9
+
10
+ ## 1. Canonical output only
11
+
12
+ Emit `1` not `F`, `0` not `>`, no whitespace; allowed characters: `0–9 ( ) [ ] { } , -`.
13
+ Never emit pretty aliases.
14
+
15
+ ## 2. Numeric containers must match exactly
16
+
17
+ `N(child1, …, childK)` requires `K === N`. No compression, no implicit slots, no
18
+ empty classifiers (`2()`, `3[]` invalid). Build children first, count them, then
19
+ prefix with N.
20
+
21
+ Valid: `2(1,1)` · `3[1,0,1]` Invalid: `2(1)` · `3(1,1)` · `2(1,1,1)` · `3()`
22
+
23
+ ## 3. First token rule
24
+
25
+ A valid string starts with `1`, or a NUMBER > 1 followed immediately by `(` or `[`.
26
+ `0`, `-`, `,1`, `{1}` are invalid openers. `1(...)` / `1[...]` is
27
+ `ILLEGAL_ONE_SPLIT` — never wrap a single child in a pseudo-container.
28
+
29
+ ## 4. NUMBER must be followed by a classifier
30
+
31
+ `2,1` and `2{1}` are invalid; `2(1,1)` and `2[1,1]` are valid.
32
+
33
+ ## 5. Overlay discipline — objects vs nesting
34
+
35
+ - **At most one immediate overlay object per node**: `1{1}`, `2(1,1){1}`, `0{1}`,
36
+ `-{1}` valid; `1{1}{1}` invalid.
37
+ - **Layer via nesting**: `1{1{1}}` — base tile, overlay subtree, overlay on the
38
+ overlay.
39
+ - **Multiple overlay layers/regions via structure inside the body**:
40
+ `1{2(1,-){2(-,1)}}` — the overlay body is a container that owns its own overlay;
41
+ holes (`-`) isolate which regions show tiles. "One overlay object per node" does
42
+ not limit overlay depth.
43
+
44
+ ## 6. No trailing passthrough
45
+
46
+ A trailing `0` inside a classifier is always invalid — **even with an overlay**
47
+ (`PASSTHROUGH_TO_NOTHING`): `2(1,0)` and `2(1,0{1})` invalid; `2(0,1)` and
48
+ `2(0{1},1)` valid. A passthrough must donate to a claimant; an overlay does not
49
+ exempt it.
50
+
51
+ ## 7. Overlay bodies — valid, but sourceless is LEGAL
52
+
53
+ Bodies validate recursively as full m0 sub-expressions. A body must contain at
54
+ least one node, but it does NOT need a leaf `1` — the legacy `ZERO_SOURCE_OVERLAY`
55
+ rule was **relaxed 2026-06-03** and is no longer raised
56
+ (`m0StringValidator.ts:485`). Canonical overlay rules:
57
+ [`overlay-semantics.md`](overlay-semantics.md).
58
+
59
+ Invalid: `1{}` (INVALID_EMPTY) · `1{0}` (INVALID_EMPTY) · `1{2(1,0)}` (trailing
60
+ passthrough in body)
61
+ Valid: `1{1}` · `1{2(1,1)}` · `1{1{1}}` · `1{2(-,1)}` · `1{-}` · `1{2(-,-)}`
62
+
63
+ ## 8. No-sources rule
64
+
65
+ The layout as a whole needs ≥1 leaf `1` (`NO_SOURCES`) — a source inside an
66
+ overlay counts: `2(-,-)` invalid, `2(-,-){1}` valid.
67
+
68
+ ## Quick reference
69
+
70
+ ```
71
+ VALID: 1 · 2(1,1) · 2(1{1},1) · 3[1,0{1},1] · 1{1{1}} · 1{2(1,-){2(-,1)}} · 1{-}
72
+ INVALID: 0 · - · 2(1) · 2(1,1,1) · 1{{1}} · 1{1}{1} · 2(1,0) · 2(1,0{1}) · 1{} · 1{0} · 2(-,-)
73
+ ```
74
+
75
+ ## Construction procedure
76
+
77
+ 1. Decide the structure shape (tree of splits + leaves).
78
+ 2. Build child arrays per split; count them; prefix the exact numeric classifier.
79
+ 3. Attach at most one overlay object per node; get extra layers via nesting or
80
+ split/hole structure inside the body.
81
+ 4. Canonicalize.
82
+ 5. Validate with `isValidM0String` / `validateM0String` from `@m0saic/dsl`
83
+ (returns `{ok: true} | {ok: false, error}` — singular `error`).
84
+ 6. On failure: discard and rebuild. Never patch a broken string by hand — fix the
85
+ generator ([`m0saic-string-generation.md`](m0saic-string-generation.md) §3 has
86
+ the per-code repair moves).