@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,591 @@
1
+ # Feasibility, Precision, Quantization & GCD — the geometry math
2
+
3
+ > The four properties that decide whether a layout **renders**, renders
4
+ > **correctly**, and looks **balanced**. All four are **absolute, deterministic
5
+ > math** — every outcome is computable *before* rendering a pixel. Don't guess
6
+ > and eyeball; check the numbers. Companion to [`dsl-rules.md`](dsl-rules.md)
7
+ > and the [thesis](../m0saic-thesis.md).
8
+
9
+ A split lays `N` cells across a pixel axis of length `T`. Everything below falls
10
+ out of that one fact and integer pixels.
11
+
12
+ ---
13
+
14
+ ## 1. FEASIBILITY — *will it render at all?*
15
+
16
+ The **parse floor.** Below it the layout **errors** (a split produces a 0-size
17
+ frame). A hard boundary, not a quality issue.
18
+
19
+ - **API:** `computeFeasibility(m0)` → `{ minWidthPx, minHeightPx }`
20
+ (`@m0saic/dsl`). Exact minimum integer dims with no 0-size frame — accounts for
21
+ nested same-axis splits, passthrough carry chains, and overlay constraints (not
22
+ just `maxSplit`). Authoritative; no repeated-parse probing.
23
+ - **Calibration** (exact vs the structural `maxSplit`): `960(1,1,…,1)` needs
24
+ ≥960px width (flat — exact = maxSplit); `2(5(1,1,1,1,1),5(1,1,1,1,1))` needs
25
+ 10px (nested same-axis — exact > maxSplit); `10(0,0,0,0,0,0,0,0,0,1)` needs
26
+ 1px (passthroughs donate — exact < maxSplit).
27
+ - **Error:** rendering below the floor raises `SPLIT_EXCEEDS_AXIS` ("split
28
+ produced a 0-size frame").
29
+ - **Diagnostic:** after parsing at a concrete size, `M0ResolutionDiagnostics`
30
+ (`tightestWidthPx`/`tightestHeightPx` + the offending `stableKey`) tells you the
31
+ smallest frame actually produced — 1–2px means you're sitting on the floor.
32
+
33
+ **What to do:** ensure target dims ≥ `computeFeasibility(m0)`. If not, the layout
34
+ is *infeasible* — reduce cell count, decompose the split, or raise the canvas.
35
+ There is no "looks slightly off" here; it's render-or-error.
36
+
37
+ ---
38
+
39
+ ## 2. PRECISION — *will it look right?*
40
+
41
+ The **appearance guarantee** — separate from feasibility. A layout can be
42
+ feasible (parses, no 0-size *frame*) yet **not look right**, because below the
43
+ precision floor a split's cells (incl. passthrough/donation cells) can't each get
44
+ ≥1px, so the layout squashes / spreads.
45
+
46
+ - **API:** `getComplexityMetricsFast(m0)` (the only aggregate export — the validating
47
+ `getComplexityMetrics` was removed) bundles `precision` (`maxSplitX`/`maxSplitY`/
48
+ `maxSplitAny` — the largest classifier count on each axis, O(n) scan) with
49
+ frame/passthrough/null counts and `precisionCost = maxSplitAny`.
50
+ (`computePrecisionFromString` is an internal dsl helper, not public API.)
51
+ - **Warning surface:** when `maxSplitAny` exceeds the norm (`opts.precisionNorm` on
52
+ `parseM0StringComplete`, default 100), the parse emits a `PRECISION_EXCEEDS_NORM`
53
+ **warning** (https://github.com/m0saic-dsl/m0/blob/main/packages/dsl/src/warnings/warnings.ts). Not invalid — a signal the
54
+ layout is highly granular: possibly expensive, size-sensitive, hard to reason
55
+ about. Raise the norm deliberately for intentionally fine layouts.
56
+ - **Relationship — two INDEPENDENT floors that cross both ways.** Feasibility
57
+ (no 0-size *frame*) and precision (every split cell ≥1px, incl. passthroughs)
58
+ are *not* ordered — neither is a floor on the other. Measured across the sandbox
59
+ corpus: gutter grids and `weightedSplit([3,5,2])` have **precision > feasibility**
60
+ (render small, only look right bigger); deeply-nested same-axis layouts have
61
+ **feasibility > precision**. In practice take the **per-axis max of both** — the
62
+ smallest canvas that *renders AND looks right*. `evaluateM0`/`compareM0`
63
+ (`@m0saic/dsl-stdlib`) expose `feasible`, `meetsPrecision`, and that
64
+ `recommendedMin` directly. Even above precision, exact balance is a further
65
+ question — quantization (§3).
66
+
67
+ **What to do:** treat "feasible" as "won't error," never as "looks correct."
68
+ After feasibility, ask the quantization question.
69
+
70
+ ---
71
+
72
+ ## 3. QUANTIZATION — *is it balanced, or does it spread?*
73
+
74
+ When `N` cells don't divide the axis `T` evenly, the `T - N·floor(T/N)` leftover
75
+ pixels must go *somewhere*. The engine distributes them **outside-in**, so cell
76
+ sizes differ and any position computed from them **shifts** — a chart's middle
77
+ gridline drifts, a grid's center column is a pixel thin. This **visual
78
+ imbalance** is:
79
+
80
+ - **Deterministic** — same string + same dims → identical pixels, every time.
81
+ - **Not a bug, not an error** — the defined consequence of integer pixels.
82
+ - **Predictable** — `T % totalWeight === 0` ⇒ perfectly uniform; otherwise
83
+ spread, growing with cell count. Measured: a 499-cell split rendered uniform
84
+ at **1996px** (=4×499, gaps `[400,400,400]`) but spread at **1920px** (gaps
85
+ `[400,324,400]`) — same string, both deterministic; the 1920 case is feasible
86
+ but sub-precision.
87
+
88
+ **Outside-in remainder distribution (the exact rule).** Each slot starts at
89
+ `base = floor(total/N)`; the remainder `rem = total − base·N` is added one pixel
90
+ at a time to indices in the order `0, N−1, 1, N−2, 2, N−3, …` (edges first,
91
+ center last — NOT first-N). Example: `4(1,1,1,1)` at axis 103 → base 25, rem 3,
92
+ order `0, 3, 1, 2` → sizes `[26, 26, 25, 26]`. Intentional and stable;
93
+ generators that predict per-slot pixel geometry must reproduce it exactly.
94
+ Source: https://github.com/m0saic-dsl/m0/blob/main/packages/dsl/src/parse/m0StringParser.ts ("outside-in remainder
95
+ distribution"); locked by https://github.com/m0saic-dsl/m0/blob/main/packages/dsl/src/parse/outside-in-remainder.test.ts.
96
+
97
+ **You know in advance when something will look unbalanced — don't ship it blind.
98
+ You can't change the math; you massage the values:**
99
+
100
+ ### ★ The general fix: `placeRects` at real pixel positions
101
+
102
+ Spread comes from letting a **weight basis** decide positions — a basis that
103
+ doesn't divide the axis. The general escape: stop using a weight basis at all —
104
+ compute the wanted positions as **absolute integer pixels**, then `placeRects` a
105
+ rect at each. Why it's exact: placeRects builds bands whose weights ARE those
106
+ pixel sizes and **sum to the axis length**, so each weight maps to an integer
107
+ pixel count — no remainder to spread. The pattern: **trivial even split → read
108
+ the clean edge positions → `placeRects` exact rects there.** Two rules keep it
109
+ exact:
110
+
111
+ 1. **Round each boundary independently** from its fraction — `round(i/N · axisPx)`
112
+ — never sum rounded widths (that accumulates error). Independent rounding
113
+ bounds error to ≤0.5px per line, no drift.
114
+ 2. **Compute at the target resolution.** The px are baked — the deliberate trade
115
+ for exactness. Feed it the slot's real dims (`ctx.target` when nested).
116
+
117
+ > **Caveat — this is a HEAD move (§3c).** It bakes a canvas-scale basis, so it's
118
+ > exact only where the element owns its canvas and won't be nested. Doing it
119
+ > inside a *nestable* primitive is the "absolute → fine ratio" anti-pattern —
120
+ > see the grid/v1 callout in §3c.
121
+
122
+ (The resolution-DEPENDENT vs -INDEPENDENT trade this makes, and when each side
123
+ wins, is §3a — the builder choice.)
124
+
125
+ ### When you're staying weight-based
126
+
127
+ - **GCD-collapse the weights** (§4) — fewer cells → larger px-per-weight →
128
+ smaller relative remainder → less visible spread. Always do this.
129
+ - **Render at quantization-free dimensions** — pick dims where the split divides
130
+ evenly:
131
+
132
+ | helper (`@m0saic/dsl-stdlib`) | what it gives |
133
+ |---|---|
134
+ | `safeCanvas({rows, cols, …})` | **largest zero-distortion canvas** for a grid (largest multiple of its `totalX`/`totalY` that fits) |
135
+ | `snapGridFit({rootW, rootH, rows, cols})` | snaps a known grid into the largest quantization-free inner rect, letterboxing the remainder |
136
+ | `snapGridEnumerate(range)` / `snapGridFind(range)` | sweep `(rows,cols)`, **rank** quantization-free candidates by `fitScore = coverage × tileCount` |
137
+ | `aspectSafeGrid(…)` | landscape↔portrait grid pair, both quantization-free, same cell count, matched cell aspect (not a naïve transpose) |
138
+
139
+ - **Keep px-per-weight ≥ ~4** — the `grid` builder's `MIN_PX_PER_WEIGHT = 4` rule:
140
+ at ≥4px/weight the remainder is ≤25% of a cell, so spread is visually negligible
141
+ even at high counts. Auto-scaled cell weights enforce this.
142
+ - **Hairlines: ≥ 2.5px per weight unit, never a 1-unit band in a ~1px/unit split.**
143
+ A 1-unit rule inside a 13-unit band at 480×270 (~1px/unit) quantized to a
144
+ 0-size frame → `SPLIT_EXCEEDS_AXIS` → the CLI refused the whole render, and the
145
+ nested flatten collapsed the layout wherever child slot and parent cell differed
146
+ by ±1px (gate 33, dsl-tutorial, 2026-09-05). Rule: derive the band's unit count
147
+ from its px — `units = floor(bandH / 2.5)`, the hairline takes 1 unit, and below
148
+ 3 units drop the rule (the label-keyed layout contract reports the collapse as
149
+ `missing-label`; see `templates/layout-contract.md`).
150
+ - **Accept the spread and derive dependents from the same weights** — when you
151
+ can't control the canvas, compute overlays/animated layers from the *same*
152
+ quantized geometry (never recompute from ideal fractions) so everything tracks
153
+ the same spread and stays mutually aligned (line-chart `frame.ts` derives the
154
+ plot rect from the frame weights; the data layer follows it).
155
+
156
+ **What NOT to do:** don't fight spread with `size`/`fit` expressions or pixel
157
+ nudges — off-thesis (see construction-strategy). Fix the *numbers*.
158
+
159
+ ---
160
+
161
+ ## 3a. Two builders — `placeRects` vs `weightedSplit` (when to use which)
162
+
163
+ Both emit weighted splits; the difference is what decides the weights.
164
+
165
+ - **`weightedSplit(weights, axis)`** — proportional, **resolution-INDEPENDENT**,
166
+ GCD-reduced (+ Hamilton's-method `precision` scaling). One string, re-parses
167
+ correctly at any size. **May quantize** (§3). Shines: portable/authored
168
+ layouts reused across sizes, the editor, readable proportional regions.
169
+ - **`placeRects({rootW,rootH,rects})`** — absolute integer-pixel rects,
170
+ **resolution-DEPENDENT**, **exact at that canvas** (band weights are pixels that
171
+ sum to the axis → no remainder). Shines: render-time known-target geometry, exact
172
+ positions (gridlines, ticks), arbitrary 2D rect sets, overlay packing.
173
+
174
+ **Decision rule:** default to `weightedSplit` (+ GCD-collapse §4 / `safeCanvas`).
175
+ Reach for **forced-absolute `placeRects` only when you want a *specific exact*
176
+ result** and accept px baked for one resolution. Portability is the axis: the
177
+ Layout editor re-parses the same string on every canvas resize, and the
178
+ rendering-model "one m0 → one geometry" contract relies on resolution
179
+ independence — so weight-based is for layouts that travel (authored layouts, the
180
+ editor, the published stdlib builders); absolute is for a template rendering to
181
+ a target it owns (a chart, a screencap grid). Slot nuance: a template nesting a
182
+ genuinely resolution-dependent primitive should pass it real rendered px as a
183
+ `slot` when it must register with SIBLING geometry at exact pixels (line-chart's
184
+ gridlines ↔ data plot rect) or line thickness must be exact for the cell —
185
+ though a slot only *mitigates* non-nestability; the structural fix is a ratio
186
+ primitive needing no slot (§3c). And placeRects still emits a *weighted-band*
187
+ split, so an EVEN division stays uniform even scaled into another cell
188
+ (bar-graph nests the grid with bare `ctx`; gaps stay uniform).
189
+
190
+ **DSL cost (measured).** placeRects costs more DSL *only* when you force
191
+ exactness on proportions that don't divide the axis cleanly:
192
+
193
+ | layout @1000px | weightedSplit | placeRects |
194
+ |---|---|---|
195
+ | cols 1:2:1 (clean) | 4 tok, exact | **4 tok, exact** (GCD-collapses identical) |
196
+ | cols 1:1:1 (333.3 ea) | 3 tok, 0.6px spread | **1000 tok, exact** (≈250×) |
197
+ | cols 382:618 | 500 tok, exact | 500 tok, exact (equal) |
198
+ | thin-line grid ×5 | ~720 tok, 0.5px | ~720 tok, exact (equal) |
199
+
200
+ The "placeRects tax" is really *the inherent cost of placing coprime pixels
201
+ exactly* — clean proportions GCD-collapse to the same short string; fine ratios
202
+ and thin-line grids cost the same either way (there placeRects is a free win).
203
+ **Runtime vs authoring** decides whether the cost matters: runtime-regenerated
204
+ geometry (a chart, the grid primitive) is rebuilt every render → DSL cost
205
+ irrelevant → take exact placeRects; persisted/reused geometry (a saved
206
+ dictionary layout) → keep the cheap portable weightedSplit when its spread is
207
+ imperceptible, pay placeRects only when exactness is genuinely needed.
208
+
209
+ **Thin-line grids are special:** the interleaved `[gap,line,gap,…]` weightedSplit
210
+ basis can drift 0.5→**67px** while placeRects stays ~0px — the
211
+ weightedSplit-when-imperceptible win is for **region** layouts, not thin lines.
212
+ But mind the mode trap: a per-pixel placeRects grid is exact only at the
213
+ **head** — nested, its canvas-scale basis silently drops (the grid/v1 callout,
214
+ §3c). A **nestable** thin-line grid decouples the lines from the basis instead —
215
+ fixed-px strips at proportional overlay offsets (`primitives/grid/v2`, ladder
216
+ rung 2).
217
+
218
+ ### Don't guess — measure (`@m0saic/dsl-stdlib`)
219
+
220
+ - `quantizationSpread(m0, w, h)` → max px any frame drifts from ideal (`0` = exact);
221
+ `isQuantizationImperceptible(m0, w, h, maxPx)` is the yes/no.
222
+ - `evaluateM0(m0, canvas)` → bundle: `dslLength`, `feasible`, `meetsPrecision`,
223
+ `recommendedMin`, frame/passthrough/null counts, `maxSpreadPx`.
224
+ - `compareM0(a, b, canvas|canvas[])` → per-metric diff (better-when lower/higher/
225
+ context), `comparable` (same frameCount), and — the one unambiguous case —
226
+ `geometricallyEqual` + `recommendation`: if both render identical frames (via
227
+ `areM0StringsFrameEqual`), keep the **shorter** string.
228
+
229
+ A generator can emit an m0 several ways and `compareM0` them to pick the best.
230
+ Compare at ≥ `recommendedMin` (the floors cross both ways — §1–§2). The
231
+ dsl-stdlib grid family (`grid`, `safeCanvas`, `snapGrid*`, `aspectSafeGrid`) is
232
+ **not** redundant with placeRects — it's the resolution-independent /
233
+ design-time side (aspect-matching, ranking, gutters, portability). Keep it.
234
+
235
+ ---
236
+
237
+ ## 3b. Escape hatch: `placeRect` for resolution-safe absolute positioning
238
+
239
+ When an element MUST occupy an exact pixel rect at ANY canvas resolution, place it
240
+ with `placeRect` (`@m0saic/dsl-stdlib`) — **NOT** a margin/inset helper built from
241
+ splits (`insetNode` and the 3×3-split positioners). `placeRect({rootW, rootH, rectW,
242
+ rectH, x, y})` emits the rect at exact pixels with `-` null tiles for the margins;
243
+ null tiles never claim space, so the rect is byte-exact regardless of whether the
244
+ canvas divides evenly.
245
+
246
+ **Why margin-based placement is the trap.** `insetNode(node, top, right, bottom,
247
+ left)` makes the margins REAL split cells, so on a canvas that doesn't divide
248
+ evenly the engine spreads the quantization remainder INTO the placed rect —
249
+ shrinking + shifting it (§3). Invisible at clean resolutions, brutal at others →
250
+ a flaky "the geometry itself doesn't align" bug that only shows at some sizes.
251
+ Measured: the alpine stat-card's icon glyph (intended 26px square) came out
252
+ **33×23, shoved past its chip's right edge** at the 290×288 desktop rail, and its
253
+ value band (intended 59px) was **crushed to 38px** so the 55px digits clipped —
254
+ while 480×480 / 360×184 / 224×224 (which divide cleanly) rendered fine.
255
+ Desktop-only = the classic quantization tell.
256
+
257
+ **When to reach for it:** small rects (icons, chips, badges), fixed-font text
258
+ bands that must not clip, anything multi-resolution-critical. When two rects must
259
+ stay registered (a glyph centred in its chip), place BOTH via placeRect so they
260
+ share exact coordinates — mixing placeRect (exact) with insetNode (quantized)
261
+ makes them drift apart. **Cost:** higher DSL token count (the null-tile padding)
262
+ — pay it. For a whole light primitive that must stay small, keep proportional
263
+ splits + GCD-collapse; placeRect is for the elements that must be exact, not the
264
+ entire tree ([`precision-tiers.md`](precision-tiers.md)). **Verify:** parse
265
+ (`parseM0StringComplete`) and assert each rect's render-frame width/height ==
266
+ intended and center-offset == 0 across the target resolutions.
267
+
268
+ ### `placeRect` vs `placeRects` — pick by count
269
+
270
+ - **`placeRect`** (singular): ONE exact rect with `-` null margins — one-off
271
+ exact elements.
272
+ - **`placeRects`** (plural): MANY rects, **packing non-overlapping ones onto as
273
+ few overlay layers as possible** (one compact m0, N frames per layer;
274
+ overlapping rects spill via `importance`-bucketed greedy first-fit). Reach for
275
+ it whenever you place more than a couple of rects.
276
+
277
+ Map sources to the packed result: build `pieces = [{rect, source}]`, call
278
+ `placeRects({rootW, rootH, rects: pieces.map(p => p.rect)})`, then per `layer`
279
+ read `layer.rectIndices`, sort by `(y, x)` (band-emission order), and push
280
+ `pieces[idx].source` — sources line up with frames. Use `rect.importance`
281
+ (ascending = base→top) for z-order: surface < tiles < text/glyphs.
282
+
283
+ **Why plural matters (measured).** The commit-feed as N separate `placeRect`
284
+ overlay layers was **208K-char m0 / 68 layers**; the same rects via one
285
+ `placeRects` dropped to **114K / a handful of layers**. The stat-card's 8
286
+ per-element layers → **~16K → 4K**. Fewer layers = the real perf win (fewer
287
+ filtergraph overlay ops) and keeps you clear of the ~25-layer overlay mask-drop
288
+ cliff (engine wall W3 — see the internal walls doc, below).
289
+
290
+ ---
291
+
292
+ ## 3c. The three drafting modes — RATIO, ABSOLUTE, BITMAP (never launder one into another)
293
+
294
+ `placeRects` (§3/§3a) and `placeRect` (§3b) are *tactics*; underneath sits a
295
+ **strategic choice** that decides whether an m0 **composes**:
296
+
297
+ - **RATIO (product-native, the default).** m0 *is* ratio decomposition — a
298
+ rectangle split into weighted sub-rectangles. **Resolution-independent, nests
299
+ cleanly.** Its only failure is quantization (§3), and we have tools for that.
300
+ Reach here first.
301
+ - **ABSOLUTE (the trivial case).** Pin real pixels — effectively one weight per
302
+ pixel, so the **basis ≈ the canvas dimension**. Laser-sharp, zero quantization
303
+ at the known dims. **But its feasibility floor rises to ~canvas level and it
304
+ does NOT nest** — an absolute element carries canvas-scale precision and eats
305
+ whatever cell it is given. Correct only at the **HEAD** (top-level, resolution
306
+ known, never nested); `placeRects` / `placeRect` are its builders.
307
+ - **BITMAP (the raster extreme).** A full N×M grid where *every* cell is
308
+ addressed (null or frame), taken across time for pixel-level animation. Very
309
+ heavy → strictly **bake-once-to-an-asset, then reference forever**; never
310
+ nested or live-composed (QR module grid; `brand/logo/v3` `m-33_bitmap`).
311
+
312
+ | mode | nests | feasibility floor | resolution | typical use |
313
+ |---|---|---|---|---|
314
+ | RATIO | ✅ yes | small, ~constant | independent | the default; any nestable primitive |
315
+ | ABSOLUTE | ❌ eats its cell | ≈ the canvas | baked | a head laser-placing its own chrome |
316
+ | BITMAP | ❌ bake first | ≈ the raster | baked | pixel-level animation, baked to an asset |
317
+
318
+ **The rule:** default RATIO. ABSOLUTE only when quantization genuinely bites
319
+ *and* the element sits at the head. BITMAP only for a baked asset with rich
320
+ pixel-level motion.
321
+
322
+ > **☠ The anti-pattern — the grid/v1 silent drop (canonical telling; §3, §3a,
323
+ > and the probe below all point here).** Never launder ABSOLUTE into a fine
324
+ > RATIO split: computing absolute pixel positions and re-encoding them as a
325
+ > weighted split whose basis ≈ the axis in px is the **worst of both** — ratio's
326
+ > composability is already spent (the basis is canvas-scale) AND you inherit
327
+ > absolute's non-nestability *without* its head-only safety.
328
+ > `primitives/grid/v1` did exactly this — `buildAxisSplitGrid` computed each
329
+ > line's px then `placeRects`'d a per-pixel-basis split (`1024[0,1,0,…]` for a
330
+ > 1024px plot). Standalone it renders perfectly (1024 slices of 1024px = 1px
331
+ > each); **nested, the engine re-divides the per-pixel basis below 1px, cells
332
+ > round to 0, and the grid is SILENTLY DROPPED** — at 1024², 1000², … while
333
+ > 1080² happens to survive, so it reads as intermittent silent loss (and
334
+ > `SPLIT_EXCEEDS_AXIS` at flatten). Diagnostic signature: *renders fine
335
+ > standalone but vanishes when composed at certain canvases; child m0 basis ≈
336
+ > its pixel dimension.*
337
+
338
+ **The proper fix for a quantizing primitive is NOT raw `placeRects`** — keep it
339
+ RATIO. **The authoritative rung enumeration is the launder ladder in
340
+ [`m0-construction-methods.md`](m0-construction-methods.md) §2c**; this section
341
+ keeps the per-rung detail (rung numbers below refer to that ladder):
342
+
343
+ - **Rung 1 — Restructure as ratio; return a DIFFERENT ratio m0 for the canvas.**
344
+ A template is `(props, canvas) → m0`; at a size that would quantize, yield a
345
+ re-laid-out ratio m0 that divides cleanly. More derivation, but composability
346
+ survives.
347
+ - **Rung 2 — Decouple thin features from the basis.** For thin lines (where the
348
+ interleaved `[gap,line,gap,…]` split spreads catastrophically — §3a), don't
349
+ split at all: draw each line as a **fixed-px strip at a proportional overlay
350
+ offset** (`H*frac`). `primitives/grid/v2` is the reference — a proportional
351
+ overlay chain (`1{1{1{1{1}}}}`, feasibility 1×1) that nests into any cell. It
352
+ pays a shallow overlay chain (one composite per line, warn-only past depth 20,
353
+ and — plain colored strips, no masks — it never hits the ~25-layer
354
+ inline-mask-drop cliff, W3) instead of
355
+ surrendering composability. v1 was 1 op but non-nestable; v2 is N overlays but
356
+ nestable — for a primitive, composability wins.
357
+ - **Rungs 3 & 5 — Launder coprime pixels through the inset (zero-drift
358
+ inset-recovery).** The unifying principle: **it's the quantized SPLIT that
359
+ kills coprime; a render-time `placement.inset` makes the quantization visually
360
+ lossless.** Round each rect's CELL outward to a coarse divisor lattice (cheap,
361
+ bounded precision), then shrink the painted source back onto the exact target
362
+ inside it — the inset costs zero chars and zero precision (it lives in the
363
+ fiber — [`composition-arithmetic.md`](composition-arithmetic.md) §4). Shipped
364
+ tools: `placeInsetRects` (`@m0saic/dsl-stdlib`) + the author front door
365
+ `placeInsetPieces` (`@m0saic/template-utils`), with divisor-pitch selection and
366
+ floor-safe half-pixel-centered fractions built in. Three load-bearing constraints:
367
+ (1) **inset only SHRINKS** — cells round OUTWARD (cell ⊇ target), and between two
368
+ rects the boundary needs a lattice point in the gap (guaranteed at
369
+ `gap ≥ pitch − 1`; when it misses, the cells spill to separate overlay layers —
370
+ painted content stays disjoint, so collision costs LAYERS, not correctness);
371
+ (2) **leaf-scoped** — an inset repositions a painted SOURCE within its cell, so
372
+ container edges can't launder this way, and leaves must paint nothing outside the
373
+ inset box (a cell-filling background would show the quantized cell); (3) **coarser
374
+ pitch → bigger insets** — a `minFill` floor keeps small rects from drowning in
375
+ giant cells (clamps to a finer divisor, reported not silent). The same one move
376
+ underlies all four ratio-rebuild shapes: uniform grid = gutterless `grid()` +
377
+ gap-as-**`latticeCellInset`** (see the caveat bullet below — **not**
378
+ `gridCellInset`, which is deprecated); curves/charts = the mask path IS the
379
+ recovery, in cell-local coords (rung 4); irregular tilings = nested splits +
380
+ gap-as-inset per leaf; independent chrome = `placeInsetPieces`. Exemplar:
381
+ stat-card migrated here from the drift budget (2026-07-10) — same ~120-basis
382
+ bound, **zero visual drift**, render-verified including prime-dim canvases.
383
+ Where a cell can contain its target, this DOMINATES the drift budget (rung 6).
384
+ Design + corrections history: (internal design history).
385
+ - **⚠️ `gridCellInset` is DEPRECATED — it is approximate, not pixel-exact** (founder
386
+ ruling 2026-07-21). It fails the one-move contract in two ways, and both are
387
+ invisible in code review (the math *looks* right) and subtle on screen (±1px per
388
+ shared edge) — which is exactly why it shipped. (1) **Ideal vs quantized cells:** it
389
+ computes half-gap fractions against the IDEAL cell (`gridW / cols`), but the engine
390
+ floors `frac × actualCellPx` against the QUANTIZED cell the equal split really
391
+ produced. Whenever the grid's pixel region doesn't divide evenly — the common case
392
+ when a grid sits under a content-driven band (1080 − 110 = 970px → rows of
393
+ 243/242/243/242) — the same fraction recovers 1px on one row and 0px on the next, so
394
+ a 2px gap wobbles between 2 and 0. (2) **No half-pixel centering:** its fractions are
395
+ plain `n / cell`, so even on exactly-dividing cells `floor((1/480)·480)` can land at
396
+ 0 under IEEE — the precise failure `placeInsetRects` defends against with
397
+ `(n + 0.5) / cell`.
398
+ **Use `latticeCellInset`** (https://github.com/m0saic-project/m0saic-packages/blob/main/packages/template-utils/src/layout/latticeCellInset.ts)
399
+ for this rung: it retargets each RAW parsed cell onto an authored integer lattice
400
+ with half-pixel-centered `(n + 0.5)/rawSize` fractions — exact gutters at every
401
+ canvas, and it keeps the compact gutterless `grid()` m0. Reach for
402
+ `placeInsetPieces` instead when the helper should own the WHOLE layout (mixed bands,
403
+ non-grid pieces, z-order via importance).
404
+ `gridCellInset` **remains exported, deprecated not deleted** — it now lives in the
405
+ graveyard folder https://github.com/m0saic-project/m0saic-packages/blob/main/packages/template-utils/src/deprecated/gridCellInset.ts. As of
406
+ 2026-07-26 **no template module imports it anymore** (verified: the only importer is
407
+ the graveyard's own test; the grep hits in `heatmap/*`, `bar-graph/v1`,
408
+ `screencap_grid/*` are comments and deprecation-reason strings). It is the first
409
+ entry in the helper-level deprecation registry (`HELPER_DEPRECATIONS` /
410
+ `helperDeprecation()` / `isDeprecatedHelper()` in
411
+ https://github.com/m0saic-project/m0saic-packages/blob/main/packages/template-utils/src/deprecation.ts), the library-API sibling of
412
+ `MosaicTemplate.deprecated`. Exemplar of the fix: `@m0saic/media/screencap_grid/v2`
413
+ (gap-carved integer rects → one `placeInsetPieces` call); `v1` is kept registered as
414
+ the canonical reference for the pitfall.
415
+ - **Rung 4 — Express connected geometry as masks-in-a-cell (the general fix).**
416
+ Curves, charts, and rings are only forced-absolute when placed via per-curve
417
+ pixel bboxes. Author the marks as **inline-masks filling ONE ratio-positioned
418
+ cell, paths in CELL-LOCAL coordinates** — every mask in the same cell shares
419
+ one coordinate system and registers exactly, while the cell is a plain ratio
420
+ split → bounded precision. Scales from a sparkline (kpi-card `1.04 → 0.00`) to
421
+ an entire plot (line-chart `1.08 → 0.02`: gridlines + axis + area + line +
422
+ halo + dot masks all filling one plot cell, sibling labels inset to the SAME
423
+ plot fractions) — **including radial** (donut/v2: ring marks in one ring cell,
424
+ `0.00`). The test of "legit-absolute" is whether the ANIMATION varies the mask
425
+ PATH per frame — NOT whether the geometry is connected or radial. Reveals ride
426
+ the overlay (alpha fade / enable gate), so almost nothing truly varies the
427
+ path. (This corrects a prior belief: the donut sweep does NOT carve its arcs
428
+ per frame.) **Bounded by the ~25-mask overlay cliff**
429
+ (W3) — the marks all stack in ONE cell's
430
+ overlay chain (line-chart ≤6 masks, alpine donut ≤12 segments). A soup of many
431
+ more masks needs rung 7.
432
+ - **Rung 6 — Launder the absolute basis with a drift budget (GCD-snap).** A
433
+ primitive positioning **INDEPENDENT** elements (chrome, text blocks, badges)
434
+ at exact pixels can keep those positions and collapse the coprime basis: the
435
+ precision floor of a placed rect is `axisLen ÷ gcd(before, size, after)`, so
436
+ ONE coprime edge ⇒ `gcd 1` ⇒ the leaf pins ~its render canvas — and
437
+ back-solved through its slot (`needs = self × canvas ÷ slot`) it pins the
438
+ *parent* too. The fix trades a tiny canvas-relative drift for the coarsest
439
+ common grid: `snapRectPrecision` (single rect — exact + a Pareto frontier of
440
+ nearby low-precision rects), `placeOptimizedRects` (plural, per-rect
441
+ `driftPx`, `0` = LOCK byte-exact — a locked coprime edge correctly stays
442
+ 100%), and the author front door `placeOptimizedPieces`
443
+ (`@m0saic/template-utils`). Measured (stat-card `@480×480`, its drift era):
444
+ `100% / 5,785 chars → 17% / 899 chars` (≤2px drift, visually identical),
445
+ audit slope `1.04 → 0.08` — the card has since moved to inset-recovery
446
+ (rungs 3 & 5: same bound, zero drift). **Independent elements ONLY** — it
447
+ breaks uniform grids (equal cells snap unequal → use a ratio grid), tilings
448
+ (shared edges open gaps), and connected geometry (rung 4). Preference order:
449
+ zero-drift **inset-recovery** first where the leaves are transparent-margin
450
+ painted sources with shrink room; drift remains right for per-rect LOCKS
451
+ (must-stay-exact geometry mixed with collapsible chrome), container edges,
452
+ tiles that paint their whole cell, and wanted-snap (pixel-aligned strokes,
453
+ butt-joint cell edges).
454
+ - **Rung 7 — Self-framed coarse-quantize (many-mask rect-soups).** A soup of
455
+ many overlapping origin-relative masks — the donut's smooth `premium` sweep is
456
+ **~32 thin annular-sector slivers** — defeats the other rungs: angular
457
+ geometry isn't row/col-expressible, one cell would stack a 32-layer chain over
458
+ the mask-drop cliff (W3), and raw
459
+ `placeRects` (which packs the non-overlapping sliver bboxes into ~8 layers —
460
+ under the cliff) is absolute. Keep `placeRects` and add ONE move — build the
461
+ soup in its **own coarse-grid-aligned frame**:
462
+ 1. Pick a **canvas-proportional** pitch `P = round(min(W,H)/K)` and a square
463
+ frame side `S = ceil(regionSide/P)·P` — `S` is a `P`-multiple **by
464
+ construction**.
465
+ 2. Snap each bbox **OUTWARD** to `P`. Masks are origin-relative, so only the
466
+ invisible bbox grows to the grid — the drawn arc stays pixel-perfect.
467
+ 3. `placeRects` inside the `S×S` frame. Every band — INCLUDING trailing
468
+ margins and empty corners — is a `P`-multiple, so the split gcd collapses
469
+ to `S/P ≈ K`, constant on every canvas *including coprime dims* (1001×733:
470
+ ~51/51 vs the exact build's 1001/733).
471
+ 4. **Letterbox** the frame into the canvas with a basis-capped ratio split.
472
+ Cost: a **≤1px uniform translation** of the whole frame (zero *relative*
473
+ drift; this slop is one reason sliver seams need care).
474
+ The own frame is load-bearing: quantizing on the FULL canvas leaves a trailing
475
+ margin `≡ axisLen mod P` that collapses the gcd back to `gcd(P, axisLen)` —
476
+ and coprime canvas dims share NO coarse `P` at all. Constraints: marks must be
477
+ **origin-relative** (a media tile would SHIFT when its bbox snaps — use
478
+ inset-recovery instead); `P` must be **proportional** (`min/K`) — a FIXED snap
479
+ px bounds chars but still tracks the canvas (donut/v3's `SNAP_PX = 4` probed
480
+ slope 1.04); and on a light or deeply-nested surface, anti-aliased sliver
481
+ edges leave hairline notches that angular overlap can NOT close — the robust
482
+ fix is an **inflated seamless base** per segment (`rOuter+δ`, `rInner−δ`,
483
+ δ ≈ 2px) underneath the slivers, **timed to fade in as each segment finishes**
484
+ (not during its sweep, or the base reads as a two-tone second layer). Measured
485
+ (donut/v4, 32-sliver sweep): precision bounds at ~`K` on every canvas, m0
486
+ 6–25× smaller, ~8 layers, arcs pixel-perfect — audit `ABSOLUTE 1.04 → RATIO
487
+ ~0`, taking the template library to 0 audit warnings.
488
+
489
+ Why the laundering family works — and why an accepted DRIFT composes *better*
490
+ than a quantizing split (snapped cells stay lattice multiples; quantized cells
491
+ go coprime-consecutive and poison descendants):
492
+ [`composition-arithmetic.md`](composition-arithmetic.md).
493
+
494
+ **Rung 8 — honest ABSOLUTE at the head.** `placeRects` / `placeRect` remain the
495
+ **lazy-but-right** move for a **head** — a hero/dashboard already knows its
496
+ canvas at render, never nests, so the canvas-level feasibility floor costs
497
+ nothing (this is why the pulse beats moved from ratio to absolute — at certain
498
+ canvases the ratio squares quantized visibly). Reach for them only after
499
+ deciding the element is head-only. The primitive/head distinction IS the
500
+ RATIO/ABSOLUTE choice — see [`precision-tiers.md`](precision-tiers.md).
501
+
502
+ **Detecting the mode (build-time probe).** A template's output isn't knowable
503
+ statically, but you can **probe** it: render `defaultProps` across aspects ×
504
+ resolutions (240p→4K) and watch `computeFeasibility` + `precision` (§1–§2).
505
+ **Precision that TRACKS the canvas (slope ≈ 1) ⇒ absolute; stable (slope ≈ 0) ⇒
506
+ ratio.** Verified: `grid/v1` precision-Y = canvas height (slope 1.00 →
507
+ absolute); `grid/v2` and `charts/stat-card/v1` stay flat (slope 0.00 → ratio).
508
+ A `primitive`-role template that probes absolute is a **composability warning**
509
+ (`PRIMITIVE_ABSOLUTE_POSITIONING`). The audit lives in `@m0saic/templates`
510
+ (`gen-template-audit.ts` → `TEMPLATE-AUDIT.md`, safe canvas = per-axis max of
511
+ precision and feasibility); the dense companion `gen-precision-sweep`
512
+ (`npm run audit:precision-sweep`) catches primitives that pin on only SOME
513
+ canvases (~24–85%) even when the coarse audit reads "ratio", and ranks migration
514
+ value. For a NESTED template the top-level m0 is trivial (`line-chart` →
515
+ `1{1{1{1{1}}}}`), so the probe must **flatten** before measuring — a flatten
516
+ that fails (`SPLIT_EXCEEDS_AXIS`) at a canvas is itself a strong
517
+ absolute/infeasible signal.
518
+
519
+ **Did the layout SURVIVE, not just the mode?** The audits above measure the
520
+ precision *slope*; the **layout contract** goes further — a template tags its
521
+ sources with LABELS and declares canvas-independent ratio invariants against
522
+ them (aspect, min/max fraction, `within`, and **presence** — incl. through
523
+ nested flatten, which catches grid/v1's silent nested drop). `checkLayout` is
524
+ the pure evaluator a layout search loops on; `npm run audit:layout-envelope`
525
+ reports the canvas range where each invariant holds. The emitter round-trip
526
+ guard (`audit:geometry-matrix` / `audit:geometry-proof`) additionally locks a
527
+ zero-drift placement. Resolve-only, opt-in. See
528
+ [`../templates/construction-strategy.md`](../templates/construction-strategy.md)
529
+ "Verifying layout intent survived".
530
+
531
+ ---
532
+
533
+ ## 4. GCD — *collapse weights to lowest terms*
534
+
535
+ Greatest-common-divisor reduction is the cheapest quantization mitigation: divide
536
+ all weights by their GCD — identical proportions, fewer cells, more pixels per
537
+ weight.
538
+
539
+ - **Helpers:** `dsl-stdlib` GCD-reduces internally (`builders/_internal/barSplit.ts`,
540
+ `transforms/.../buildSplitFragment.ts`) and exports NO `gcd`/`gcdArray`. The
541
+ exported number theory is `@m0saic/template-utils` `lattice/`: `gcd(a, b)`,
542
+ `lcm(a, b)`, `lcmAll(ns, cap)`, `isSmooth`, `roughPart`, `divisors`.
543
+ - **Built in:** `weightedSplit(weights, axis)` in the default `"optimized"` mode
544
+ **auto-GCD-reduces** (and collapses to a plain equal split when all reduced
545
+ weights are 1). `placeRects`, `placeRect`, `strip`, `aspectFit`, `golden*`, and
546
+ the bar splits all GCD-reduce their segment weights too.
547
+ - `weightedSplit([35,65],"col")` → GCD 5 → `[7,13]` → 20 cells, not 100.
548
+ - `weightedSplit([10,10,10],"row")` → GCD 10 → all 1 → emits `3[F,F,F]`.
549
+ - **`mode: "literal"`** skips GCD reduction (keeps your exact cell count). Use it
550
+ only when the cell count itself is load-bearing (e.g. a thin-line grid where
551
+ the count sets line thickness) — and then mind §3.
552
+
553
+ **What to do:** prefer `optimized` (default). Reach for `literal` consciously,
554
+ knowing it raises the quantization risk.
555
+
556
+ ---
557
+
558
+ ## The agent playbook (in order)
559
+
560
+ 1. **Feasible?** `computeFeasibility(m0)` ≤ target dims, else it errors. Hard gate.
561
+ 2. **Balanced?** `axisLength % totalWeight === 0`? Yes → uniform, done. No →
562
+ quantization spread (deterministic, computable) — acceptable only if
563
+ px-per-weight is large (≥~4) and the imbalance is below perception.
564
+ 3. **Not balanced and it matters?** First reach: **compute the real pixel
565
+ positions and `placeRects` them** (round each independently; bake at the target
566
+ res) — the general fix. If you must stay weight-based: GCD-collapse → choose
567
+ quantization-free dims (`safeCanvas` / `snapGrid*` / `aspectSafeGrid`) → ensure
568
+ px-per-weight ≥ 4 → accept + derive dependents from the same weights.
569
+
570
+ The point: **the math is absolute and known ahead of time.** Feasibility and
571
+ quantization are not "render and see" — they're `computeFeasibility`, a modulo,
572
+ and a GCD. Compute them, then choose the canvas or the weights deliberately.
573
+
574
+ ---
575
+
576
+ ## See also
577
+
578
+ - [`m0-construction-methods.md`](m0-construction-methods.md) — the all-up MAP
579
+ (two questions, four currencies, the **canonical launder ladder** §2c).
580
+ - [`composition-arithmetic.md`](composition-arithmetic.md) — the NESTING math:
581
+ factor budgets, drift-vs-quantization, base×fiber, the prime-axis theorem.
582
+ - [`dsl-rules.md`](dsl-rules.md) — grammar, tokens, validation invariants,
583
+ error codes.
584
+ - Engine walls W1–W12, incl. W3 (the ~25-overlay inline-mask-drop cliff, canonical
585
+ home) — **engine-internal**: `.ai/moat/runtime/ffmpeg-limitations.md` (absent in
586
+ the shipped copy of this folder; the cliff numbers quoted above stand alone).
587
+ - [`../m0saic-thesis.md`](../m0saic-thesis.md) — why it's all rectangles.
588
+ - [`../templates/construction-strategy.md`](../templates/construction-strategy.md)
589
+ — the Rect Thesis at authoring time.
590
+ - `dsl-stdlib/src/builders/` — `safeCanvas`, `snapGrid*`, `aspectSafeGrid`,
591
+ `weightedSplit`, `grid`, and the GCD helpers.