# Generated functions A **generated function** is one layout rule pulled out of the hand-written engine into a plain-data function: typed inputs in, typed outputs out, no access to the DOM or Stylo. The hand-written code around it (`crates/layout/src/fragment.rs`) decides *when* the rule applies and hands it inputs already reduced to one decision; the generated function decides *what the answer is*. Because the function is that small, a model can write it, and the harness can score it alone with every other generated function held fixed. How one is generated and admitted is in [`generation-loop.md`](generation-loop.md); how its output is checked is in [`geometry-verification.md`](geometry-verification.md). ```mermaid flowchart LR fragment["fragment.rs
(hand-written walk)"] -- "plain inputs" --> gen["generated function"] gen -- "plain outputs" --> fragment fragment --> tree["fragment tree"] ``` ## The fifteen generated functions Scores are on each function's own corpus, as recorded in the committed candidate's provenance record under `crates/layout/src/generated/`. The recipe corpus is 227 cases; the other corpora are per-function. | function | rule (CSS 2.1 unless noted) | committed candidate | score | how it was built | | --- | --- | --- | --- | --- | | width | 10.3.3 width equation, 10.4 min/max | `qwen3.8-flash-next` | 140/227 | hand fixture first | | margin | 8.3.1 margin collapsing | `qwen3.8-27b` | 156/227 | hand fixture first | | percentage | 10.2 / 10.5 / 8.4 percentages | `qwen3.8-27b` | 116/227 | hand fixture first | | absolute | 9.3, 10.3.7, 10.6.4 absolute positioning | `qwen3.8-flash-next` | 82/108 | model-first | | float | 9.5, 9.5.1, 9.5.2 floats and clearance | `claude-code:default` | 109/109 | model-first | | relpos | 9.4.3 relative positioning | `claude-code:default` | 104/104 | model-first | | stacking | 9.9.1, Appendix E.2, CSS Color 3 opacity | `claude-code:default` | 105/105 | model-first | | height | 10.6.3 / 10.6.7 auto height | hand-written | 105/105 | extracted from working code | | overflow | scrollbar presence (CSSOM View) | `qwen3.8-27b-fast` | 105/105 | model-first | | containing block | 10.1 which box is a box's containing block | `qwen3.8-flash-next` | 106/106 | model-first | | transform | CSS Transforms 1 §3/§4/§5, the 2D transformation matrix | `qwen3.8-27b-fast` | 110/110 | model-first | | inline atomic | 9.4.2, 10.8, 10.8.1 line boxes over atomic inlines | `qwen3.8-flash-next` | 111/111 | model-first | | table | 17.5.2.1 fixed table layout, 17.5.3 row heights | hand-written baseline | 0/0 | no batch drawn yet | | aspect ratio | CSS Box Sizing 4 §4, `aspect-ratio` and its size transfers | `qwen3.8-27b-fast` | 33/33 | model-first, repaired | | box generation | CSS Display 3 §2.5 box generation, CSS 2.1 9.7 `display`/`position`/`float` | `qwen3.8-27b-fast-2` | 48/48 | model-first, admitted by a feature job | On the recipe corpus a function scores only the cases its own rule decides, so width, margin and percentage each fall short of 227 alone; run together in `layout()` they pass 227/227 (`roadmap.md` → "Proof of concept", claim 1). The containing-block function is the one whose answer is a choice of box rather than a number: it names which rect the absolute function's equations are then solved against, which is what `position: fixed` needed in order to exist at all. The transform function is the one whose answer is a matrix, and the only one so far not drawn from CSS 2.1. The inline-atomic function is the one whose answer is a set of positions rather than a single value, and the first to place more than one box per call: everything before it decided one box's geometry at a time. Which of these the engine's real entry points actually call is traced in `roadmap.md` → "Which resolver each generated function runs". ## Per function Each function starts from a baseline candidate that reproduces the hand-written rule it replaced, then model batches scored against browser-measured cases. Most functions here were built with the older standalone `*_generation_loop` binaries, which also take two or more **seeded-wrong fixtures** (deliberately broken candidates the gate must reject). The job pipeline scores candidates against the browser-measured cases alone, with neither seeded-wrong fixtures nor a hand-written reference implementation (`docs/generation-loop.md` step 2 gives the reason). **Width** (`crates/layout/src/width.rs`). Hand `correct` fixture 112/227; two seeded-wrong fixtures 0/227. The first Claude batch compiled 0 of 5 probed (missing imports, redefined types, and a fence-extraction bug); after fixing the prompt and extraction, a second batch compiled 8/8, all 132/227 — above the fixture because the model also implemented 10.4 min/max clamping. Qwen 27B batches mostly failed to compile on an invented `LengthPercentageOrAuto::Percentage` variant until one prompt sentence forbade it (1/8 → 6/8 compiling, winners 119/227). GLM-5.3 Flash compiled 0/8 (its analysis and code share one reply field) and was replaced. A 32-candidate `qwen3.8-flash-next` batch across four replicas compiled 19/32, admitted 14, and its best, 140/227, replaced the committed Claude candidate — the first free-hardware winner to beat a paid one. **Margin collapsing** (`crates/layout/src/margin.rs`). Two functions, not one: `classify` (does this box's margin adjoin its neighbours?) and `combine` (the arithmetic for one adjoining group). Admission also requires agreement with the `crates/logic` Datalog rules box by box, because a candidate can reach correct geometry by coincidence (`docs/margin-collapse-experiment.md` → "Verdict"). Hand fixture 156/227; one seeded-wrong fixture (`ignores_self_collapsing`, 138/227, zero regressions) was rejected *only* by the rules check. The first Qwen batch's five 156/227 candidates were all rejected by the rules for gating `self_collapsing` on `!establishes_bfc`; one clarifying prompt sentence got half the next batch through. **Percentage resolution** (`crates/layout/src/percentage.rs`). Hand fixture 116/227; 8 of 8 Qwen candidates matched it on the first batch. Limitation carried forward: a percentage-only mistake cannot regress anything while the baseline fails every percentage case, so the geometry gate cannot reject it. **Absolute positioning** (`crates/layout/src/absolute.rs`), the first model-first generated function. 108 cases, twelve categories. Two Claude batches plateaued at 74/108; a third was refused for billing. A 32-wide `qwen3.8-flash-next` batch closed `OverConstrainedRtl` (0/8 → 8/8) and its candidate replaced Claude's at 82/108. Still at 0%: `BottomHeightTopAuto`, `OffsetsDefineSize`, `PercentageOffsetsAndSizes`. Cost comparison: `docs/model-first-cost.md`. **Floats** (`crates/layout/src/float.rs`). The structural half (float lists, the second layout pass, shrink-to-fit width) is hand-written; the generated function is the placement rule. The regression set is seeded from every fixture's passes (97 cases) because a baseline that ignores `clear` could not otherwise reject a candidate that also ignores it. One `claude-code` batch: 8/8 compiled, all 109/109. No hand-written candidate exists. **Relative positioning** (`crates/layout/src/relpos.rs`). One `claude-code` batch reached 104/104. Its corpus was the first to depend on two other generated functions' real behaviour, which exposed three harness bugs before anything could be scored honestly. **Stacking order** (`crates/layout/src/stacking.rs`), the first generated function whose output is an ordering. Needed a new oracle (`document.elementsFromPoint`, exact three-way match, `docs/dump-format.md` → "Stacking probe") and two extra corpus categories before the seeded-wrong fixtures could be rejected. One `claude-code` batch: 8/8 at 105/105. **Block height** (`crates/layout/src/height.rs`), extracted from working hand-written code with no behaviour change. All 8 `claude-code` candidates matched it at 105/105, none exceeded it, so the hand-written rule stays. Its first loop run scored the known-correct baseline at 93/105, which exposed a harness bug (margin collapsing held at its baseline). **Overflow** (`crates/layout/src/overflow.rs`). Decides whether a box shows a scrollbar. A first candidate reached 90/105 (right for boxes that clip, wrong for `visible` spill); a mixed batch from two gateway models produced a 105/105 candidate (`docs/model-first-cost.md` → "Overflow: a mixed batch"). **Aspect ratio** (`crates/layout/src/aspect_ratio.rs`). Gives a box with `aspect-ratio` the content size the ratio implies, with min and max sizes transferred through it. Its cases are existing WPT pages (`oracle/suites/features/aspect-ratio.json`) measured by the browsers, 33 train and 7 held out, and its prompt shows each case as the function's inputs and expected outputs. Of a batch of 8 first replies from `qwen3.8-27b-fast`, 5 did not compile and the best passed 25 of 33; one round of `bench/repair-loop.sh` (8 repair requests) gave the committed candidate, 33 of 33 train and 7 of 7 held-out. Admitted on passing every train case, with the held-out score recorded and not gating. Its provenance record's `definition_closure_hash` is the SHA-256 of the three referenced CSS 2.1 sections' text as the prompt cites them, and `prompt_hash` that of the first prompt, before the repair conversation. **Containing block** (`crates/layout/src/containing_block.rs`), the generated function that made `position: fixed` possible. Its answer is a choice of box rather than a number: which rect CSS 2.1 10.3.7/10.6.4's equations are then solved against. Before it, `fragment.rs` decided that itself with one line — the tree root, or the nearest ancestor whose `position` is not `static` — which has no way to say "the viewport", so `fixed` was flattened to `static` in `dom::computed::position` and a fixed box was laid out in normal flow. Over the corpus as it stands (106 cases): baseline 36/106, seeded-wrong fixtures 37/106 and 71/106, both rejected on 13 regressions each, committed candidate 106/106. One batch across four gateway models compiled 77 of 82 candidates, and every candidate that scored reached 105/105 — the whole corpus at that batch, one recipe smaller — but one. The loop stopped because no failing case was left, not on a plateau. The corpus had to be built against a trap: the oracle never scrolls, so at scroll 0 a fixed box and a box against the initial containing block land in the same place. Every fixed recipe but the deliberately easy top-level one either sits inside a positioned ancestor it has to escape or measures against the viewport's own 600 px height, which no ancestor box in these documents is tall enough to fake. The recipe added after the batch is the one that separates a shift from a position: its `relative` ancestor is moved with `top`/`left` rather than with `margin`, so CSS 2.1 9.4.3 moves the ancestor's fragment without moving its in-flow position. An escaping fixed box must not be dragged along by that, while an absolutely positioned sibling must move by exactly it — two demands on the same arithmetic that a corpus of margin-moved ancestors cannot tell apart. **Transform** (`crates/layout/src/transform.rs`), the first function drawn from a spec other than CSS 2.1 and the first whose answer is a matrix: a box's `transform` list, `transform-origin` and border-box size in, the six components of an affine matrix out. The structural half is hand-written — `fragment.rs` composes each box's matrix with every ancestor's and reports the bounding box of the transformed border box, which is what `getBoundingClientRect()` returns. Normal flow is untouched by design: CSS Transforms 1 says the transform does not affect the flow, so one corpus category puts a transformed box between two untransformed siblings and measures that they do not move. 110 cases, fourteen categories, none dropped for engine disagreement — arbitrary-angle rotations and skews included, which was the open risk, since those land off every engine's pixel grid. The regression set is seeded from the union of every fixture's passes (102), not the baseline's: the baseline returns the identity and so passes only the `rotate(180deg)` cases, and a fixture that composes the list right to left instead of left to right beat that comfortably and was admitted on the first run. One `WEBENGINE_MODELS` batch over four gateway models then compiled 20 of 82 candidates, twelve of them at 110/110; `model_22` from `qwen3.8-27b-fast` is committed. Out of scope and stated in the module's own doc: 3D functions and `perspective`, `transform-box`, the separate `translate`/`rotate`/`scale` properties, a transformed box becoming a containing block or a stacking context, painting, and scrollable overflow. **Inline atomic** (`crates/layout/src/inline_atomic.rs`), the function that gives the engine line boxes. Before it, `boxtree.rs` gave every computed `display` other than `none` one block-level box, so a `display: inline-block` element stacked in normal flow like a block and `vertical-align`, `line-height` and `text-align` reached nothing at all. Now a container whose in-flow children are all inline-level establishes an inline formatting context, `fragment.rs` lays each atomic inline out as a block formatting context root at its explicit size, and the function decides where on which line each margin box goes, how tall each line box is, where its baseline sits, and what the container's auto height becomes. Its answer is a set of positions rather than a single value — one placement per input box, plus the line boxes themselves — which makes it the first function here that can be wrong about one box and right about its neighbour on the same call, and the first that can panic on a well-typed input by indexing its own output. The runner catches that the way the transform runner does. Scope is what makes it possible without text. In: line breaking between atomic inlines, `vertical-align` in all six of its font-free forms, `line-height` as the strut, `text-align: left | right | center`, margins/padding/borders on the atomic inlines, an inline-block's own baseline (CSS 2.1 10.8.1's last-in-flow-line-box rule and its bottom-margin-edge fallback), and the container's auto height as the sum of its line boxes. Out, and stated in the module's own doc: text and every font-dependent value, `width: auto` inline-blocks, a container mixing block-level and inline-level children, floats and absolutely positioned boxes inside the inline formatting context, `text-align: justify`, bidi, vertical writing modes, `inline-table`, `inline-flex` and replaced elements. The corpus's own design carries the weight. Every container sets `font-size: 0` beside an explicit `line-height` in px, so the strut's ascent and descent are zero, its baseline sits at the middle of a known height, and `vertical-align: middle` aligns on that baseline rather than on half an x-height nobody can measure. `crates/harness/src/cases.rs` already emits no whitespace between tags, so no text node can appear between two atomic inlines, and the case file asserts that for every case it produces. 111 cases over fifteen categories, none dropped for engine disagreement — which is the answer to the open question this corpus was built around: the three engines agree on a zero-size font's strut. Admitted on the first batch at 111/111. Four candidates reached the whole corpus and each came from a different one of the four gateway models, which makes this the third function solved completely on its first batch, after floats and transforms. The two seeded-wrong fixtures are worth more than the usual here: they are wrong about disjoint halves of the function — one ignores `vertical-align`, the other never breaks a line — and between them they pass all 111 cases, so the regression set covers the corpus from the first batch and a candidate must pass everything to be admitted at all. **Table** (`crates/layout/src/table.rs`), the fixed table layout, and the first function whose structural half had to be built from nothing. Before it, tables did not exist in the engine: `boxtree.rs` modelled no anonymous boxes and its own doc comment said so, a `display: table-cell` box was stacked like a block and filled its containing block's width, and `display: table` was one of the keywords `dom::computed::Display` swept into its `Other` catch-all. Now the box tree generates the anonymous rows and cells CSS 2.1 §17.2.1 calls for inside a fixed-layout table, `fragment.rs` has a third formatting context alongside stacking and line boxes, and the function decides every column's width and x offset, every row's height and y offset, and the table's own used width and height. It is the second function with two exported halves, after margin collapsing, and they are split on the axis §17.5.2.1 splits the algorithm on: `columns` is answered from the table's own width and the first row's cells alone, before any cell's content has been laid out, and `rows` is answered afterwards from the heights that content produced. That order is the whole content of the claim that a fixed table's horizontal layout does not depend on its cells. Scope is what keeps it measurable. In: `table-layout: fixed` on a table with an explicit `width`; columns from the first row's cells as a length, a percentage or `auto`, with `auto` columns sharing what is left and a table wider than its columns spreading the difference; one- and two-value `border-spacing`; padding and borders on the table and on cells under either `box-sizing`; a cell's margins ignored; a row taller than its cells; and the two anonymous table objects. Out, and stated in the module's own doc: `table-layout: auto` and every table whose used layout is auto, column and column-group elements, row groups, captions, `colspan`/`rowspan`, the collapsing-borders model, `vertical-align` other than `top`, a table `height` larger than its rows, `inline-table`, and text. No batch has been drawn. The committed candidate is the function's own deliberately wrong baseline — every column the full width of the table, every cell stacked under the one before it, which is exactly what the engine did with these boxes before the function existed — committed as the starting candidate the way `overflow` and `inline_atomic` both were, so the three `layout*` entry points default to something. The corpus, the runner, the three binaries and the two seeded-wrong fixtures are all in place and compile; the build lane was unreachable for the whole session, and both the browser oracle that fills the corpus and a model batch refuse to run anywhere else. The structural half is checked instead against hand-computed geometry in `fragment.rs`'s own tests, which is what a browser would otherwise have been asked. **Box generation, admitted by a feature job** (`specs/display-contents.md`). `qwen3.8-27b-fast-2`'s `c6_qwen3_8_27b_fast_2_p017f6528` (seed 6), stored by the `display-contents` job after 0 repair round(s) at train 48 of 48 and held-out 15 of 15, passes 48 of 48 on the score check's corpus. The job's review passed it and its `admit` stage put it into the engine. ## What the generated functions show - **The generated part is small by construction.** The hand-written walk decides which margins adjoin, which basis a percentage uses, which children stack; the model writes the leaf decision. - **The model has rarely beaten a hand-written answer.** Where a hand fixture existed first, winners matched it except width, which added min/max clamping. Model-first functions (absolute, float, relpos, stacking, overflow, containing block, transform, box generation) were solved without any hand candidate. - **Building the scaffolding around a function costs more than generating it.** Corpus, oracle, types and prompt are hand work, and that cost is what decides the thesis (`docs/platform-economics.md` → "Per-feature cost"). - **Common failure kinds:** invented types, replies cut off before the code, one plausible misreading of the spec fixed by a single prompt sentence, and harness bugs that only a known-correct candidate exposes. - **A function's scope is a corpus decision before it is a code decision.** The inline-atomic function exists at all because one shape of inline content — atomic inlines at explicit sizes — can be measured without a font, and the corpus is built to keep every font-dependent value out. Everything the three engines might disagree about was designed out before the first oracle run rather than dropped after it.