@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,278 @@
1
+ # Composition arithmetic — the number theory of nesting
2
+
3
+ > [`feasibility-precision-quantization.md`](feasibility-precision-quantization.md)
4
+ > is the math of ONE split. This is the math of splits STACKED — why some trees
5
+ > compose exactly all the way down while others hit a wall three levels in, why
6
+ > drift composes better than quantization, and where exactness is provably
7
+ > impossible so you stop chasing it. Everything here is decidable before rendering:
8
+ > it is all divisibility. Companion to [`precision-tiers.md`](precision-tiers.md)
9
+ > and https://github.com/m0saic-dsl/m0/blob/main/packages/dsl/GCD.md (the single-asset encoder story).
10
+
11
+ ---
12
+
13
+ ## 0. TL;DR
14
+
15
+ - A canvas axis is a **prime-factor budget**; every exact split spends factors, and
16
+ Ω(T) (prime factors with multiplicity) is the nesting-depth budget.
17
+ - Keep every split count **a divisor of its axis and 5-smooth** and the whole tree
18
+ stays 5-smooth forever — composition never hits a prime wall.
19
+ - **Quantization is hereditary poison**: a non-dividing split hands children
20
+ *coprime consecutive* cell sizes (≤1px visually, arithmetically junk). Drift is
21
+ the reverse: lossy to the eye, clean to the arithmetic.
22
+ - An m0 is **base × fiber**: split lines on a shared coarse lattice (costed,
23
+ visible, composable) + leaf-private placement (inset / cell-local masks / xExpr —
24
+ free, invisible, *unable* to break composition).
25
+ - **Nesting refines to the lcm; overlay adds.** Incompatible denominators go on
26
+ separate layers, never into one refined split.
27
+ - On a prime axis, **every exact interior line costs full precision** — theorem,
28
+ not tuning. Self-frame or drift; never emit the 100% string silently.
29
+
30
+ ---
31
+
32
+ ## 1. The canvas is a prime budget
33
+
34
+ Write an axis as `T = 2^a · 3^b · 5^c · (rest)`. An **exact** split needs `N | T`
35
+ and produces cells of `T/N` — it *spends* factors from the multiset. Two numbers
36
+ describe the whole budget:
37
+
38
+ - **Ω(T)** — prime factors with multiplicity — the **depth budget**: how many
39
+ nontrivial exact splits can stack before cells reach 1px.
40
+ - **σ₀(T)** — divisor count — the **menu size**: how many exact split counts are
41
+ available at this node.
42
+
43
+ | dim | factorization | σ₀ (menu) | Ω (depth) |
44
+ |---:|---|---:|---:|
45
+ | 720 | 2⁴·3²·5 | 30 | 7 |
46
+ | 1080 | 2³·3³·5 | 32 | 7 |
47
+ | 1280 | 2⁸·5 | 18 | 9 |
48
+ | 1350 | 2·3³·5² | 24 | 6 |
49
+ | 1920 | 2⁷·3·5 | 32 | 9 |
50
+ | 2160 | 2⁴·3³·5 | 40 | 8 |
51
+ | 3840 | 2⁸·3·5 | 36 | 10 |
52
+ | 4320 | 2⁵·3³·5 | 48 | 9 |
53
+ | 7680 | 2⁹·3·5 | 40 | 11 |
54
+ | *997 (prime)* | *—* | *2* | *1* |
55
+
56
+ **Spend order matters.** Split 1080 into 8 and the cells are `135 = 3³·5` — you are
57
+ out of 2s *forever*; nothing below can ever halve exactly. Split into 9 and the
58
+ cells are `120 = 2³·3·5` — still rich on every prime. Spend the scarce prime late;
59
+ balance the spend across levels.
60
+
61
+ **The hereditary-smoothness invariant.** Divisors of a 5-smooth number are 5-smooth.
62
+ So if the root axes are 5-smooth and *every split count divides its axis*, every
63
+ cell in the entire tree is 5-smooth — the recursion never lands on a prime wall. A
64
+ generator can enforce this mechanically: track the remaining factor multiset per
65
+ node; a child template's exactness needs are a sub-multiset test.
66
+
67
+ ---
68
+
69
+ ## 2. The practical canvas lattice
70
+
71
+ m0 is general-purpose, but this renderer's delivery targets are ~all **5-smooth**
72
+ (video standards descend from 2^a·3^b·5^c tilings). That makes the practical set a
73
+ family of shared lattices — measured, not vibes:
74
+
75
+ | canvas family | per-axis gcd | meaning |
76
+ |---|---:|---|
77
+ | modern core: 1080 / 1920 / 2160 / 3840 / 4320 / 7680 (16:9, 9:16, 1:1) | **120** | any split total dividing 120 is pixel-exact on every axis in the family |
78
+ | + 720p (1280, 720) | 40 | |
79
+ | + 4:5 social (1350) | 10 | the truly universal pitch |
80
+
81
+ - **The universal basis menu for the modern core is `divisors(120)`** — sixteen
82
+ values: 1, 2, 3, 4, 5, 6, 8, 10, 12, 15, 20, 24, 30, 40, 60, 120. The absences
83
+ teach as much as the entries: **7 divides no standard dimension** (a 7-split is
84
+ never exact anywhere — fine as a small-basis ratio fill, never for registration),
85
+ and **16 divides the widths but not 1080** (a 16-col split is exact where a
86
+ 16-row split quantizes).
87
+ - **Exactness lifts by integer scaling.** 4K = 2× 1080p exactly, 8K = 4×. Prove a
88
+ layout exact at an aspect family's base size and every larger member is free.
89
+ - **Hostile strays exist** — 1.91:1 social lands on `566 = 2·283` (283 prime).
90
+ Don't design for them; route them through §6.
91
+ - A basis cap of **120 is the family gcd**, not folklore — a ≤120-basis template is
92
+ exact across the whole modern core. Persisted/portable m0 should draw totals from
93
+ `divisors(family gcd)`; a runtime template (`(props, canvas) → m0`) can use
94
+ `divisors(actual axis)` — enumerating them is `O(σ₀)` ≈ 30–40 candidates, free.
95
+ - **This is enforced, not advised (2026-09-16):** the `latticeSmooth` template
96
+ convention (throw) holds every split count above 12 to 5-smooth, at the hinted
97
+ canvas and the sweep canvases; `m0saic doctor` applies it to external packs
98
+ (declarations: [`../templates/reference/template-flags.md`](../templates/reference/template-flags.md)).
99
+ The number theory lives in `@m0saic/template-utils` `lattice/`: `gcd`, `lcm`,
100
+ `lcmAll`, `roughPart`, `isSmooth`, `factorize`/`formatFactors`, `divisors`,
101
+ `smoothDivisors`, `ceilToSmooth`/`floorToSmooth`, `nearestSmooth`,
102
+ `FAMILY_GCD = 120`, `FAMILY_BASIS_MENU = divisors(120)`, `latticeWeights`,
103
+ `quantizedSections`, and the scanner/report pair `splitCounts(m0)` /
104
+ `latticeReport(m0s)` / `latticeViolations(m0s, { canvas, allow, physicalCanvas })`.
105
+
106
+ ---
107
+
108
+ ## 3. Quantization is hereditary poison
109
+
110
+ The inverted intuition, and the single most load-bearing fact in this chapter.
111
+
112
+ A non-dividing N-split looks nearly harmless — cells of `⌊T/N⌋` and `⌈T/N⌉`, spread
113
+ ≤1px (§3 of the feasibility doc). But consecutive integers are **coprime**: the
114
+ children inherit arithmetically junk axes no matter how rich T was.
115
+ `1080/7 → 154 = 2·7·11 and 155 = 5·31.` The eye can't see the pixel; the child that
116
+ inherits a 155px axis can't split it.
117
+
118
+ Drift does the opposite. A gcd-snapped split puts every line on the `d`-lattice, so
119
+ every band is a **multiple of d** — children inherit d's factors, wealth instead of
120
+ poison.
121
+
122
+ > **Drift is lossy to the eye but clean to the arithmetic; quantization is clean to
123
+ > the eye but poisons descendants.** For anything meant to be composed, the
124
+ > arithmetic cost dominates.
125
+
126
+ Why the RATIO doctrine survives this: a **small-basis ratio child needs no
127
+ inheritance at all** — an N-slot split has cell spread ≤1px on ANY axis, hostile or
128
+ not. That robustness (not exactness) is what the audit's slope ≈ 0 actually
129
+ measures. So the division of labor is: **exactness for what must REGISTER** (across
130
+ siblings, to pixels), **small-basis robustness for what merely FILLS**. (The one
131
+ ratio danger zone is interleaved thin-line weights, where boundary error compounds —
132
+ feasibility doc §3a.)
133
+
134
+ ---
135
+
136
+ ## 4. Base × fiber — the composability algebra
137
+
138
+ Every m0 placement decomposes into two layers of meaning:
139
+
140
+ - **The base** — split lines on a shared coarse lattice. Costed in slots/chars,
141
+ visible to editors and validators, the thing children subdivide. Composition
142
+ happens HERE and only here.
143
+ - **The fiber** — per-leaf freedom *inside* a cell: `placement.inset`, mask paths in
144
+ cell-local coordinates, cell-local text, `xExpr`/`yExpr`. The fiber is
145
+ structurally free (zero chars, zero precision) and **leaf-private: fibers cannot
146
+ interact across leaves, so nothing in the fiber can break composition** — that is
147
+ a guarantee, not a heuristic.
148
+
149
+ The 2026-07 rebuild patterns are all one move (table):
150
+
151
+ | pattern | base move | fiber move |
152
+ |---|---|---|
153
+ | uniform grid (heatmap v2) | gutterless `grid()` | gap as `latticeCellInset` (`gridCellInset` is deprecated — approximate, see feasibility §3c) |
154
+ | independent chrome (stat-card) | gcd-snapped `placeOptimizedRects` | (accepted drift; or inset-recovery) |
155
+ | curves / charts (kpi-card, line-chart, donut v2) | one ratio cell | mask paths in cell-local coords |
156
+ | many-mask soups (donut v4) | self-framed coarse `placeRects` | origin-relative sector paths |
157
+ | rect soups (`placeInsetRects` / `placeInsetPieces`) | divisor-pitch quantized cells | half-pixel-centered inset recovery |
158
+
159
+ **Engine fine print — the fiber floors.** `applyInsetToRect` (core) computes
160
+ `Math.floor(f · cellSize)`, and `floor((n/W)·W)` genuinely loses a pixel on real
161
+ pairs (verified: n=15/W=22, 13/23, 15/26 …). Exact-recovery insets must be
162
+ **half-pixel-centered**: emit `(n + 0.5)/W` (verified zero failures for all
163
+ `n < W ≤ 7680`). See (internal design history).
164
+
165
+ **The fiber's price is invisibility.** What lives in the fiber doesn't exist in the
166
+ string — previews must read `placement` to show it; editors can't grab it. Spend
167
+ fiber on what nothing else needs to see structurally.
168
+
169
+ Doctrine in one line: **registration geometry on the base; private detail in the
170
+ fiber.**
171
+
172
+ ---
173
+
174
+ ## 5. Overlay is a sum; nesting is an lcm
175
+
176
+ Forcing two line families into ONE split costs the **lcm** of their denominators;
177
+ putting them on separate overlay layers costs the **sum**. Lines at thirds and at
178
+ sevenths: one split needs a 21-slot basis; two layers need 3 + 7 = 10 slots and no
179
+ lcm ever happens. **lcm blowup is THE char-count killer; overlays are
180
+ lcm-avoidance** — priced in graph depth (the ~25-mask cliff and overlay-depth
181
+ warnings are that budget). `primitives/grid/v2` (fixed-px strips at proportional
182
+ overlay offsets) is exactly this trade, chosen correctly.
183
+
184
+ The "find the coarsest grid describing the most things" game, stated formally:
185
+ partition the required lines per axis into few groups, each on a coarse lattice,
186
+ minimizing `Σ_layers T/gcd(bands)` under the layer budget. Two consequences:
187
+
188
+ - **Per-axis cost is separable** (row slots + Σ per-band col slots, each term
189
+ depending only on its own axis's gcd) — so cross-axis joint optimization gains
190
+ ≈ nothing. This resolves https://github.com/m0saic-dsl/m0/blob/main/packages/dsl/GCD.md §14.1.
191
+ - The remaining unexplored optimization is **layer partitioning by divisor-class**
192
+ (put the lines divisible by 8 on one layer, the stubborn ones on another) —
193
+ `placeRects` currently packs layers by overlap only. This is the sharper form of
194
+ GCD.md §14.2.
195
+
196
+ ---
197
+
198
+ ## 6. Where exactness is impossible (stop chasing it)
199
+
200
+ - **The prime-axis theorem.** An interior split line at `p` on axis `T` makes
201
+ sections `(p, T−p)`; slots = `T/gcd(p, T)`. For prime `T`, `gcd(p, T) = 1` for
202
+ every interior `p` — **every exact interior line costs precision T, by any
203
+ method.** Inset can't help: insets ride cells, and the cell boundary IS the line.
204
+ The remaining moves: **(a) self-frame** — build the interior geometry in its own
205
+ P-divisible frame and letterbox it with a small-basis ratio split; zero *relative*
206
+ drift, ≤1px uniform translation of the whole frame (the donut-v4 move — handbook
207
+ §3c, fix six); **(b) spend per-line drift** (§7).
208
+ - **Inset feasibility.** Inset only shrinks, so a quantized cell must contain its
209
+ target; between two rects, the shared boundary needs a lattice point **inside the
210
+ gap**: possible iff the gap contains a multiple of P, guaranteed iff
211
+ `gap ≥ P − 1`. *The gap is the inset budget* — literally, in pixels.
212
+ - **Flush shared edges at coprime positions.** A structural line two siblings share
213
+ can't move into the fiber (both cells end AT it). Restructure, or drift it.
214
+ - **The pure-inset endpoint.** Pitch `P = T` degenerates to one slot with the inset
215
+ doing all placement: precision 1 on ANY canvas *including primes* — but one
216
+ overlay layer per rect and zero structural legibility. It is the far end of a
217
+ dial (`P ∈ divisors(T)`, with `placeRects` at `P = 1`), not a free lunch: the
218
+ basis knob trades slot count vs layer count vs how much of the design the string
219
+ still shows.
220
+
221
+ ---
222
+
223
+ ## 7. Drift, principled — bounded-denominator approximation
224
+
225
+ "Best position for a line at fraction φ with basis ≤ B" is the classical
226
+ best-rational-approximation problem, and **continued-fraction convergents**
227
+ (equivalently Stern–Brocot / Farey-mediant descent) are provably optimal. The
228
+ golden builders' Fibonacci weights are literally the convergents of φ — 2/3, 3/5,
229
+ 5/8, 8/13, 13/21 — so a golden split at basis 21 is `[13, 8]` with error ~0.09%
230
+ (≈1px at 1080): optimal, not aesthetic.
231
+
232
+ Two search spaces, two tools:
233
+
234
+ - **gcd-snap** (GCD.md §6) — optimizes ALL lines jointly onto ONE lattice of a
235
+ KNOWN canvas. Use at the head / in encoders.
236
+ - **Convergents** — optimize ONE line's denominator with NO canvas in hand. Use
237
+ inside ratio primitives that must travel.
238
+
239
+ And remember §3: snapped geometry composes *better* than quantized geometry — the
240
+ drift budget is an investment in your descendants' axes.
241
+
242
+ ---
243
+
244
+ ## The agent playbook (in order)
245
+
246
+ 1. **Composing tree?** Every split count divides its axis and is 5-smooth; watch
247
+ the factor budget (don't burn all the 2s early — 1080/8 = 135 ends halving
248
+ forever; 1080/9 = 120 stays rich) — the gate measures exactly this
249
+ (`latticeSmooth`); declare the honest exceptions on `template.lattice`.
250
+ 2. **Persisted / portable m0** → totals from divisors of the family gcd
251
+ (`divisors(120)` for the modern core; 40 with 720p; 10 with 4:5). **Runtime
252
+ template** → divisors of the actual `ctx` axis.
253
+ 3. **Registration on the base lattice; everything leaf-private in the fiber**
254
+ (insets half-pixel-centered; masks cell-local). If nothing else needs to see it
255
+ structurally, it belongs in the fiber.
256
+ 4. **Incompatible denominators → separate overlay layers** (sum, not lcm), budgeted
257
+ by the mask cliff. Never refine one split to the lcm.
258
+ 5. **Hostile axis or stubborn coprime line** → self-frame (relative exactness +
259
+ ≤1px translation) or convergent-optimal drift. Never silently emit a
260
+ canvas-scale basis — that's the §3c laundering anti-pattern; the audit slope
261
+ catches it.
262
+ 6. The toolkit is **divisibility, lcm, and rational approximation** — *not* Bézout:
263
+ splits subdivide; they never form integer combinations.
264
+
265
+ ---
266
+
267
+ ## See also
268
+
269
+ - [`feasibility-precision-quantization.md`](feasibility-precision-quantization.md) —
270
+ the per-split math this chapter stacks.
271
+ - [`precision-tiers.md`](precision-tiers.md) — the head/primitive (ABSOLUTE/RATIO)
272
+ decision this chapter's arithmetic underwrites.
273
+ - [`../templates/construction-strategy.md`](../templates/construction-strategy.md) —
274
+ how the patterns of §4 show up when authoring.
275
+ - https://github.com/m0saic-dsl/m0/blob/main/packages/dsl/GCD.md — the single-asset encoder story (drift as gcd-discovery,
276
+ canvas destiny); §14.1–.2 are resolved in §5 here.
277
+ - (internal design history) — the rect-soup builder that motivated
278
+ the divisor-pitch and half-pixel-centering results.
@@ -0,0 +1,75 @@
1
+ # DSL Complexity Analysis
2
+
3
+ `@m0saic/dsl` exports lightweight complexity utilities for inspecting the
4
+ structural cost of an m0 string: how expensive is this layout to render, to
5
+ edit, and to subdivide? All functions operate via canonical-string scanning —
6
+ no geometry computation, no full tree parse.
7
+
8
+ > **Source of truth:** [https://github.com/m0saic-dsl/m0/blob/main/packages/dsl/src/complexity/complexity.ts](https://github.com/m0saic-dsl/m0/blob/main/packages/dsl/src/complexity/complexity.ts).
9
+
10
+ ## The public surface: two functions
11
+
12
+ | API | Validates? | Return type | Use when |
13
+ |---|---|---|---|
14
+ | `getComplexityMetricsFast(input)` | No | `ComplexityMetrics` (never null, never throws) | The default — editor, scoring, preflight, hot loops |
15
+ | `getFrameCount(input)` | Yes | `number \| null` (null = invalid input) | Single-metric query with strict input handling |
16
+
17
+ **These are the only complexity exports.** `getComplexityMetrics`,
18
+ `getPassthroughCount`, and `getNodeCount` were **removed** — the barrel comment
19
+ in [https://github.com/m0saic-dsl/m0/blob/main/packages/dsl/src/complexity/index.ts](https://github.com/m0saic-dsl/m0/blob/main/packages/dsl/src/complexity/index.ts)
20
+ says so explicitly ("callers use `getComplexityMetricsFast`"); `getPrecisionCost`
21
+ is an internal helper, not exported. Every other metric is a **field** on the
22
+ `ComplexityMetrics` result, not a function:
23
+
24
+ ```ts
25
+ type ComplexityMetrics = {
26
+ frameCount: number; // rendered leaves (`1`/`F`) → composited layers: the render-cost signal
27
+ passthroughCount: number; // `0`/`>` leaves → implicit donation chains: the editor-cost signal (zero render cost)
28
+ nullCount: number; // `-` leaves — gaps; structurally simple
29
+ groupCount: number; // split containers
30
+ nodeCount: number; // all structural nodes — total tree size (completeness metric)
31
+ precisionCost: number; // maxSplitAny — min-resolution viability, orthogonal to the others
32
+ precision: M0Precision; // maxSplitX / maxSplitY / maxSplitAny
33
+ };
34
+ ```
35
+
36
+ One canonicalization, one node-count scan, one precision scan — prefer the
37
+ aggregate whenever you need more than a frame count. `ComplexityMetrics` and
38
+ `M0Precision` live in [`types.ts`](https://github.com/m0saic-dsl/m0/blob/main/packages/dsl/src/types.ts).
39
+
40
+ ## How the metrics relate
41
+
42
+ | Metric | Render cost | Editor cost | Precision |
43
+ |--------|:-----------:|:-----------:|:---------:|
44
+ | `frameCount` | **primary** | secondary | — |
45
+ | `passthroughCount` | none | **primary** | — |
46
+ | `nodeCount` | correlates | correlates | — |
47
+ | `precisionCost` | — | — | **primary** |
48
+
49
+ Independent dimensions: a layout can be cheap to render but hard to edit (few
50
+ frames, many passthroughs), expensive to render but easy to edit (many frames,
51
+ no passthroughs), or precision-constrained regardless of either.
52
+
53
+ ## Two instructive examples
54
+
55
+ ```
56
+ Layout A: 3(1,1,1) frameCount=3 passthroughCount=0
57
+ Layout B: 5(0,1,0,1,1) frameCount=3 passthroughCount=2
58
+ ```
59
+
60
+ Same render cost (3 composited frames); B's passthroughs create implicit
61
+ space-donation chains the editor must visualize and the user must reason about.
62
+
63
+ ```
64
+ Nulls: 3(1,-,1) frameCount=2 passthroughCount=0 nullCount=1
65
+ Passthroughs: 3(1,0,1) frameCount=2 passthroughCount=1 nullCount=0
66
+ ```
67
+
68
+ Nulls consume space as empty gaps — structurally simple. Passthroughs donate
69
+ space forward — same frame count, higher editor complexity.
70
+
71
+ ## Scale
72
+
73
+ These metrics only bite at scale: frame count matters ~20k+ (compositing
74
+ pipeline stress), passthroughs ~10k+ (deep donation graphs), precision when
75
+ split factors push the minimum resolution past the target output dims.