# The generation loop
How one layout rule goes from spec text to committed engine code. The unit of work is a
**generated function**: one rule pulled out of the hand-written engine into a small
plain-data function (`docs/generated-functions.md`). Everything below happens once per
generated function, and again whenever the spec or
the browsers change.
```mermaid
flowchart TD
spec["1. Spec section
(e.g. CSS 2.1 §10.3.3)"] --> types["2. Types and corpus
(hand-built)"]
types --> truth["3. Ground truth
3 browsers, cached once"]
types --> prompt["4. Prompt"]
prompt --> gen["5. Model pool writes
N candidates"]
gen --> score["6. Compile and score
against the cache"]
truth --> score
score --> admit{"7. Admit?"}
admit -- "no: failures go
into the next prompt" --> prompt
admit -- yes --> commit["8. Commit source
+ provenance record"]
commit --> land["9. Land on main
behind score floors"]
```
1. **Spec section.** One algorithm from one spec section is the unit, because a whole
algorithm is the smallest thing a browser can check. The spec text travels with the
generated function and is hashed, so a later spec change can find the code derived from it
(`docs/provenance.md`).
2. **Types and corpus.** A person (or agent) writes the function's input and output types, a
baseline that reproduces the old behaviour, a set of **recipes** (small HTML page
generators, one per case the spec distinguishes), and two or more deliberately wrong
candidates. The wrong ones must be rejected; if they are not, the corpus cannot tell
right from wrong and needs more cases. This is the expensive, hand-made step.
3. **Ground truth.** Every corpus page is rendered once in Chromium, Firefox and WebKit,
and each page's box geometry is saved to a gitignored cache
(`crates/generation/src/groundtruth.rs`). Nothing after this starts a browser. How the
three answers become one reference is in `docs/geometry-verification.md`.
4. **Prompt.** The spec text, the function's Rust types, and one failing case per failing
category (`crates/generation/src/prompt.rs`).
5. **Candidates.** A model draws N independent versions of the function — 8 per batch
from a paid API, 32 at once from the self-hosted model pool
(`crates/generation/src/model.rs`, `docs/rig-capacity.md`). Many cheap tries beat one
careful one, because the browsers say which tries are right.
6. **Compile and score.** Each candidate is compiled into a small probe crate against the
function's types and run over every cached case inside the real engine, in under a millisecond per
case. A case passes when every measurement matches the browsers' vote within 1/60 px.
7. **Admit.** Two gates, both required (next diagram). A rejected batch's failures seed
the next prompt; a batch loop stops itself when per-category scores stop improving or
after six batches, and may escalate once to a stronger model first
(`specs/automated-repair-iteration.md`, `specs/escalation-tier.md`).
8. **Commit.** The winner is written under `crates/layout/src/generated/` with a
provenance record (spec URL, text hash, model, prompt hash, seed, score). From then on
it is ordinary engine source; no model runs at build or run time.
9. **Land.** The track merges to `main` through `bench/land.sh`, which runs
`cargo test --workspace` and the score of every generated function against its own floor
(`bench/verify.sh`), and `bench/fragment-floor.sh` (the recipe corpus must stay at 227/227).
A generated function's floor is its `.floor.json`, which `bench/after-land.sh` raises
after a landing and nothing lowers.
## Admission
```mermaid
flowchart LR
cand["candidate
scores"] --> reg{"loses any case in
the regression set?"}
reg -- yes --> reject["rejected"]
reg -- no --> best{"beats the committed
candidate's score?"}
best -- "no (tie or lower)" --> keep["committed one stays"]
best -- yes --> admit["admitted and
committed"]
```
- **Regression set.** Every case any admitted candidate has ever passed. A new candidate
may not lose one, so the loop cannot trade old correctness for new
(`crates/generation/src/admit.rs`).
- **Beats the committed candidate.** Strictly more cases passed; a tie keeps what is
already committed (`admit::beats_committed`).
- **Margin collapsing only:** a third gate checks the candidate's intermediate decisions
against Datalog rules in `crates/logic`, because right geometry can come from wrong
reasoning (`docs/margin-collapse-experiment.md`).
## Where it runs
```mermaid
flowchart LR
subgraph cpu["CPU servers"]
oracle["browser oracle
(builds the cache)"]
verify["compile and score"]
end
subgraph gpu["GPU model pool"]
sample["candidate generation"]
end
sample -- "candidate source" --> verify
verify -- "failing cases" --> sample
oracle -- "cached ground truth" --> verify
```
Scoring is fast (272,930 cases/s at sixteen threads, `docs/rig-capacity.md`); writing
candidates is the bottleneck. Hardware, gateway and tiers: `docs/infrastructure.md`.
## When things change
A spec edit, a browser release or a WPT update re-enters this loop at different points:
see `roadmap.md` → "Ongoing development lifecycle".