--- enabled: false touches: [crates/layout/src/transform.rs, crates/layout/src/fragment.rs, crates/layout/src/lib.rs, crates/layout/src/generated/transform.rs, crates/layout/src/generated/transform.provenance.json, crates/layout/src/generated/mod.rs, crates/dom/src/computed.rs, crates/harness/src/transform_cases.rs, crates/harness/src/lib.rs, crates/generation/**, docs/model-first-cost.md, roadmap.md, corpus/transform-ground-truth/**, crates/provenance/src/*transform*.rs, crates/provenance/src/lib.rs, docs/generated-functions.md] depends-on: [fixed-positioning] --- # `transforms2d`: 2D `transform` and `transform-origin` as a generated function `transform` is ignored. `crates/dom/src/computed.rs` has no accessor for it, and `crates/layout/src/fragment.rs` `Fragment::walk_absolute` reports every box's border box where normal flow put it. The browsers do not: `getBoundingClientRect()` and `getClientRects()` (what `oracle/oracle.mjs` `domDump()` records) return the bounding box of the transformed border box, for the element and for every descendant. The Web Features map (`docs/web-features-map.json`, feature `transforms2d`) lists it as `none` with 887 WPT test files under `css/css-transforms`, the largest layout-and-rendering feature after flexbox and grid. A transform never moves other boxes (CSS Transforms 1, "The Transform Rendering Model": it does not affect the flow), so the whole feature is one function from a box's transform list, origin and size to a matrix, plus hand-written bookkeeping that carries matrices down the tree. The provisional rule for features whose math an existing generated function already covers (lift the adjacent hand-written logic into a new generated function; the roster files it as an open decision) does not come into play: no existing generated function computes a matrix. **Scope.** In: `transform` with the 2D functions `translate()`, `translateX()`, `translateY()` (lengths and percentages of the border box), `scale()`, `scaleX()`, `scaleY()`, `rotate()`, `skew()`, `skewX()`, `skewY()` and `matrix()`, in lists of any length (composed left to right); `transform-origin` in lengths, percentages and keywords (two values; the third, z, stays 0); transforms on nested boxes, where a descendant's bounding box goes through every ancestor's matrix. The reference box is the border box (`transform-box` initial value). Out: 3D functions and `perspective`; `transform-box`; `translate`, `rotate` and `scale` as individual properties (CSS Transforms 2); a transformed box becoming the containing block for positioned descendants and establishing a stacking context (both real, but they change other generated functions' inputs, so the corpus has no positioned or `z-index` descendants of a transformed box); painting transformed boxes in `crates/layout/src/bin/paint.rs`; scrollable overflow from transforms. Say so in the new module's doc. Build the same set the absolute-positioning function has: - `crates/dom/src/computed.rs`: `transform()` returning a list of plain-data `TransformFunction` values (lengths in px, percentages as fractions, angles in radians), and `transform_origin()`. Accessors only. - `crates/layout/src/transform.rs` — `TransformInputs` (the border-box width and height, the function list, the origin as length-or-percentage per axis), `TransformOutputs` (the affine matrix `[a, b, c, d, e, f]` taking a point in the box's own border-box coordinates to its parent's coordinates, relative to where normal flow put the box's border-box origin, with the origin already applied: translate by the origin, apply the list, translate back, as CSS Transforms 1 "The Transform Rendering Model" computes the transformation matrix), `type TransformResolver`, and a deliberately wrong `resolve_baseline` returning the identity, which is what the engine does today. - `fragment.rs`: `Fragment` carries the resolver's matrix, identity for a box with `transform: none`, which leaves every existing fragment exactly as it is. `walk_absolute` composes matrices from the root down and reports each box's bounding box of its transformed border box. That is hand-written: the four corners through the composed matrix, then min and max. Keep `walk_absolute`'s signature, so `fragment_check`, `held_out_check` and `paint` need no change. One more resolver argument on the three `layout*` entry points. - `crates/harness/src/transform_cases.rs` — own `Category` enum with `pub const ALL`, `Case`, `enumerated()`, `random(seed, count)`, built on `cases::{Elem, Source, px, RESET}` like `absolute_cases.rs`. Categories at least: translate in px, translate in percentages, scale, rotate by a right angle, rotate by an arbitrary angle, skew, `matrix()`, a list whose order matters (`rotate` then `translate` against the reverse), a non-default `transform-origin` in each unit, a transformed parent with a transformed child, a transformed box between untransformed siblings (whose positions must not move). Never add to `crates/harness/src/cases.rs` `RECIPES`. - `crates/generation/src/transform_runner.rs`, a `Seam::Transform` variant (`candidate_crate.rs`), `prompt::build_transform`, `admit::record_for_transform`, and `transform_ground_truth_cache`, `transform_generation_loop`, `transform_real_model_run` under `crates/generation/src/bin/`, plus one baseline and two seeded-wrong fixtures under `crates/generation/fixtures/transform-candidates/` (for example one that ignores `transform-origin`, one that composes the list right to left). Rotations produce values off every browser's grid. `docs/geometry-verification.md` explains why the vote tolerance is 1/60 px, and Firefox rounds rects onto its own 1/60 px grid. Record the ground-truth cache's per-category drops for engine disagreement. If arbitrary-angle rotation drops most of its cases, say so and keep only the angles the engines agree on, rather than loosening the tolerance. **Shared files.** `crates/layout/src/fragment.rs`, `crates/layout/src/lib.rs` and `crates/generation/**` are edited by every spec that adds a generated function; such specs have landed concurrently before, so keep each hunk small and additive. `Fragment` gains one field here; every other spec that builds fragments passes through the constructor this spec updates. `crates/dom/src/computed.rs`, `crates/harness/src/lib.rs`, `crates/layout/src/generated/mod.rs`, `roadmap.md` and `docs/model-first-cost.md` take additions only. The model endpoint and keys are read from the environment, and the binaries do not load `.env` themselves. Before every run: `set -a; source .env; set +a` in the worktree (`.env` is a symlink installed by `tracks.json`). Never print or commit any value from it. Run, in order, each as `cargo run --release -p generation --bin `: 1. `transform_ground_truth_cache` — fills `corpus/transform-ground-truth/` from the three browsers via `oracle/oracle.mjs`; record the case count and drops for engine disagreement. 2. `transform_generation_loop` — the baseline seeds the regression set; both seeded-wrong fixtures must be rejected. If one is admitted, fix the loop or the corpus, not the fixture, and record what was wrong. 3. `transform_real_model_run` against the folknet model gateway, with `NEV_PROVIDER` unset and `NEV_MODELS=qwen3.8-flash-next:16:16,qwen3.8-27b-fast:32:32,qwen3.8-27b-fast-2:32:16,deepseek-v4.1-flash:2:2:1800` set after sourcing `.env`. Each entry is `model:count:concurrency[:timeout_secs]`, and the concurrencies match what each alias serves (`docs/infrastructure.md` → "Rig change log"). It stops on its own after two flat batches or six, whichever comes first. Score each model's share as it arrives and print per-model counts the way `overflow_real_model_run` does (`score_share`, `conclude_batch`, `crate::orchestrate` `print_per_model_summary` and `best_per_model`, and the `model_of` lookup at admission), so provenance records the winner's own model. Only if the gateway does not answer, fall back to `NEV_PROVIDER=claude-code`, and record that fallback and what the gateway returned as a Decision. Never set `NEV_PROVIDER=anthropic`. When a candidate is admitted, commit it as `crates/layout/src/generated/transform.rs` byte for byte with the header line `generated/width.rs` uses, `transform.provenance.json` beside it, `pub mod transform;` in `generated/mod.rs`, and the three `layout*` entry points defaulting to it. Record every batch in `docs/model-first-cost.md` → "Batch scores and token totals", with per-model counts, and append the outcome to `roadmap.md` → "Phase 1 — Engine bootstrap" step 4 in the form the other generated functions use. A hand-written candidate is allowed only after every batch has failed to admit one, and is recorded as an escalation with the batch count that preceded it. A floor for this function in `bench/seam-floor.sh` is a hand change after landing: the landing script refuses a diff under `bench/`, and a score line in `seam_floor_check` with no cache rebuild behind it would fail every later landing. Say so in the track's handoff. **Files beyond the function's own.** The spec text the function is generated against goes in a new provenance module whose file name contains `transform` (for example `crates/provenance/src/css_transforms_transform.rs`), so the `touches` glob `crates/provenance/src/*transform*.rs` matches it, with its one `pub mod` line in `crates/provenance/src/lib.rs`, the way `css21_containing_block.rs` was added. The function's row in the table and its "Per function" entry go in `docs/generated-functions.md`. Do not update `docs/feature-map.json`, `docs/dashboard.json` or `README.md` here: `docs/dashboard.md` asks every spec that lands a generated function to, but those files belong to `tasks/dashboard-history-graph.md`, and that instruction does not apply to an unattended track; the landing script refuses any file outside `touches`. In `docs/*.md` and `roadmap.md` prose, write "generated function", never the older word the root `AGENTS.md` forbids; `crates/harness/tests/no_seam_word.rs` fails `cargo test --workspace` otherwise (code identifiers and paths in backticks are exempt). The generated-function specs run one after another through `depends-on`, since each adds a resolver argument to the same `layout*` entry points and edits the same shared files. **Gates.** `cargo test --workspace` passes. `fragment_check --random` stays 227/227 with every category unchanged: no recipe in `cases.rs` sets `transform`, so every matrix there is the identity. `bench/seam-floor.sh`'s floors hold (floats 109, stacking 105, relative positioning 104, block height 105).