This repository has no description
bez docs generated-functions.md
21 kB
Markdown
at main

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; how its output is checked is in geometry-verification.md.

flowchart LR
  fragment["fragment.rs<br/>(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.