@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.
- package/LICENSE +21 -0
- package/README.md +71 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +7 -0
- package/docs/README.md +60 -0
- package/docs/file-formats/m0-iteration-protocol.md +120 -0
- package/docs/file-formats/m0p-and-custom-field.md +195 -0
- package/docs/handbook/README.md +27 -0
- package/docs/handbook/composition-arithmetic.md +278 -0
- package/docs/handbook/dsl-complexity.md +75 -0
- package/docs/handbook/dsl-rules.md +367 -0
- package/docs/handbook/feasibility-precision-quantization.md +591 -0
- package/docs/handbook/m0-construction-methods.md +201 -0
- package/docs/handbook/precision-tiers.md +84 -0
- package/docs/m0saic-thesis.md +95 -0
- package/docs/runtime/README.md +17 -0
- package/docs/runtime/cli-usage.md +372 -0
- package/docs/runtime/ffmpeg-expression-limits.md +117 -0
- package/docs/runtime/reduce-to-one.md +96 -0
- package/docs/skills/README.md +40 -0
- package/docs/skills/axis-and-geometry.md +103 -0
- package/docs/skills/dsl-stdlib-method-catalog.md +7 -0
- package/docs/skills/identity.md +123 -0
- package/docs/skills/labels-and-masks.md +170 -0
- package/docs/skills/m0saic-string-generation.md +251 -0
- package/docs/skills/more-atoms-not-bigger-atoms.md +77 -0
- package/docs/skills/overlay-semantics.md +194 -0
- package/docs/skills/parse-apis.md +79 -0
- package/docs/skills/passthrough-semantics.md +136 -0
- package/docs/skills/structural-construction.md +86 -0
- package/docs/skills/text-in-templates.md +126 -0
- package/docs/skills/zero-overlay-analysis.md +87 -0
- package/docs/templates/README.md +65 -0
- package/docs/templates/capability-templates.md +72 -0
- package/docs/templates/construction-strategy.md +329 -0
- package/docs/templates/data-pipeline.md +324 -0
- package/docs/templates/emission-patterns.md +130 -0
- package/docs/templates/geometry-recipes.md +248 -0
- package/docs/templates/layout-contract.md +168 -0
- package/docs/templates/output-resolution-tree.md +202 -0
- package/docs/templates/patterns/case-study-lessons.md +69 -0
- package/docs/templates/patterns/perf-authoring-rules.md +100 -0
- package/docs/templates/patterns/primitive-extraction-pattern.md +103 -0
- package/docs/templates/philosophy-and-contract.md +310 -0
- package/docs/templates/recursion-nested-rendering.md +138 -0
- package/docs/templates/reference/grid.md +104 -0
- package/docs/templates/reference/json-prop-type.md +169 -0
- package/docs/templates/reference/mosaic-color.md +81 -0
- package/docs/templates/reference/mosaic-placement-props.md +103 -0
- package/docs/templates/reference/prop-bindings.md +203 -0
- package/docs/templates/reference/template-flags.md +205 -0
- package/docs/templates/render-lifecycle.md +117 -0
- package/docs/templates/rendering-model-contract.md +392 -0
- package/docs/templates/standalone-pack-authoring.md +233 -0
- package/docs/templates/theming.md +81 -0
- package/docs/templates/ui-controls.md +150 -0
- 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.
|