Nothing

Finding: the SVG engine of record

The owner-taken decision that the n0 path — one document, one cascade, one effective-value view, one compiler, one contract, one kernel — is the engine of record for SVG, static and animated; the mature Web renderer is demoted to a frozen semantics donor.

Finding: the SVG engine of record

Genre: finding and decision evidence. Not a spec and not a plan. It records the owner-directed resolution of the two-paths question that the Web-first prototype left open, and the scope of the resulting routing decision.

Status: D-N taken 2026-07-24 in the charter's registry, owner-directed in session. The n0 path (websem → rframe → n0) is the engine of record for SVG — static and animated — in this repository's product host. The mature static Web renderer (crates/htmlcss/src/svg/) is demoted to a frozen in-repo semantics donor: no new behavior lands there; evolution rungs port its semantics onto the engine of record. This settles routing and succession, not capability: no conformance claim is made or implied.

Current state

The sections below are the decision and its dated record, in the order things happened. This is what is true at the tip, so a reader need not reconstruct it from the dated addenda below:

  • The host renders best-effort by default. The admitted subset renders and every construct outside it is declared on stderr with a node path and a reason. --strict refuses on the first one instead, and is the harness that names the slice's edge. Document-level contracts refuse in both — including a load-active animation element that cannot be attributed to one skippable element (an href retarget, a root-<svg> target). A beyond-inventory animation element with an attributable target skips that target in every view, declared; it never renders the authored state Chromium overrides at load (the addendum below closed this — it was the register's one recorded silent wrong pixel).
  • The admitted slice is <rect>, <circle>, <ellipse>, <path>, <line>, <polygon> and <polyline>, filled and stroked — solid or gradient paint (<linearGradient>/<radialGradient> paint servers, the gradient rung), including context-fill/context-stroke selected through same-document use instances and fully resolved before the frame; with centred stroke geometry, the closed cap/join family, opacity, and resolved dash patterns with a checked cycle, signed local-space phase, and pathLength source calibration; <g> and <a> containers, visibility, isolated element/group/root opacity, and HTML-ancestor opacity around the selected inline SVG; the whole transform grammar in both spellings (the attribute is a presentation hint of the CSS transform property, and gradientTransform is that attribute on gradient elements); <use>/<defs> same-document references (the id-resolution table); one declared-font, single-run <text> profile; viewBox-only root sizing with the full preserveAspectRatio grammar; and one exact-time <animate attributeName="x"> on a top-level <rect>. crates/n0_cli/README.md is the statement of record.
  • The corpus is 327 Chromium-baked primitive cells plus 10 sampled frames. All byte-exact except six curved cells carrying a declared, geometrically confined tolerance (the weighted rational conic) and three gradient cells carrying a declared one-code-value ramp-quantization tolerance (one pixel against Chromium's Skia; 18 knife-edge pixels between this engine's own macOS and Linux Skia builds; 336 ramp pixels under an isolated layer's restore). The named refusal register has 81 rows.
  • Not claimed: no conformance score exists or may be computed — FLIP is unratified. The FLIP record and identity-changing review are prepared, but only the owner act on gridaco/nothing#49 may authorize them and the first run. The Web checklist, not a score, remains the work queue.

The crux

Two SVG render paths existed and both were positioned to grow:

  • the mature static routehtmlcss::svg, wide coverage, Blink-shaped, but transitional by its own documentation: a direct-Skia paint walk (a second painter beside the n0 kernel), an in-tree CSS matcher that "must not become the cascade of record" (web-first amendment), and a deliberately static value model ("we collapse the animVal/baseVal split since Grida is static");
  • the n0 path — the amendment's topology made real: one document (csscascade), one Stylo cascade, one effective-value view (Base | Sample t), one SVG compiler (websem), one resolved contract (rframe), one kernel (n0) — narrow coverage, but pixel-exact against Chromium on its admitted corpus, with exact-time sampling and seek-order determinism law-tested.

A previously drafted resolution — transplanting the time axis into the mature renderer — was rejected by the owner on review: it would grow the transitional painter and matcher, and it removed the n0 kernel from the explicit render loop. The owner's direction, quoted for provenance:

while doing the work, we will only focus to the new engine (n0) and not care about the legacy grida depending on htmlcss or other modules AT ALL. translation: you can leave grida broken, or even simply drop the htmlcss render capability from it. we can wire that back after work is done and proven, if that makes things simpler and more focused. TL;DR we are only interested in n0 path, as it is meant to be a legacy grida successor afterall.

Evidence

  • E1. The n0 path renders its enumerated corpus pixel-exact against independent Chromium bakes: the ten fixtures/web-first/ primitives and the five fixtures/web-first/animation/ frames (Base plus four exact signed-nanosecond samples), all through the one compiler and the one kernel, with byte-deterministic re-renders. Provenance is pinned in the committed oracle-bake.json manifests and re-verified by law tests.
  • E2. Exact-time sampling on the n0 path is deterministic and seek-order independent: the retained-session laws (crates/websem/tests/svg_animation_x.rs) pin shuffled seeks, pre-roll, the freeze boundary at exact nanosecond granularity, and that sampling never mutates retained state. Kernel-side identity and damage laws live in crates/n0 and crates/n0-model.
  • E3. Beyond-slice constructs refuse loudly with the construct named (compile errors and sample-time animation refusals), never wrong pixels — the refusal discipline is itself law-tested (typed_fill.rs, svg_animation_x.rs, standalone_xml_entry.rs). The HTML entry has one scoped boundary of a different shape: it compiles exactly the document's first inline SVG — when that subtree is admitted the render succeeds and the surrounding page (layout, text, later SVGs) contributes nothing. That first-SVG-only contract is pinned by a host law, not left silent.
  • E4. The mature renderer's own documentation marks it transitional: crates/htmlcss/src/svg/README.md scopes it static-only (SMIL out of scope), records the in-tree matcher as temporary ("Replacing the in-tree matcher with Stylo remains future work"), and reserves the animVal seam as unbuilt. Its pipeline is single-shot with no retained session.
  • E5. The amendment's ruled-out constructions (web-first) forbid the shapes that keeping two growing routes would produce: no temporary matcher as the cascade of record, no three renderers behind one trait, one shared downstream.
  • E6. Time on the n0 path already satisfies the chassis invariants — explicit time, immutable frame products, no ambient clock: time changes effective values; it selects no renderer, compiler, or painter. The "static render evolves to take time t" property holds by construction: Base is the absent-time view of the same compile.

The options

#OptionWhat it buysWhat it costsVerdict
1Transplant the time axis into htmlcss::svg (retained session + animVal seam in the mature renderer)immediate static+animated unification over the full mature static surfacegrows the transitional direct-Skia painter and in-tree matcher; drops the n0 kernel from the explicit route; deepens what the Stylo and kernel swaps must later undoRejected by the owner
2Keep both routes and grow bothno regression anywherethe two-paths problem this decision exists to end; permanent double maintenance and divergent semanticsNot viable
3Cut the product host over to the n0 path now, with loud refusals outside the admitted slice; demote htmlcss::svg to a frozen semantics donor mined by evolution rungsone engine of record; the succession the owner directs; refusal-gated honesty; mature semantics still harvested, not re-derivedan explicit, enumerated capability regression at the host until rungs landChosen

Decision and scope

The n0 path is the SVG engine of record. The product host (n0_cli) routes standalone SVG and HTML documents through the retained websem session — static renders are Base; --time-ns renders are exact-time Samples — painted only by the n0 kernel. The mature htmlcss::svg route retires from the host. Outside the admitted slice, the host refuses loudly with the unsupported construct named.

Scope and consequences, pinned:

  • Capability regression, stated honestly. SVG constructs the mature route rendered — basic shapes beyond rect, paths, strokes, gradients, text, filters — now refuse at the host with named errors until their evolution rungs land; the refusals are the capability statement. General HTML pages regress along the HTML entry's contract instead: the host renders the page's first inline SVG when it is admitted (and refuses by name when it is not); everything else on the page contributes nothing. Both boundaries are pinned as host laws — the refusal pins and the first-SVG-only pin — so the regression is observable, never silent. Wire-back of any legacy capability is deferred, per the owner's dispensation quoted above, until the work is done and proven.
  • htmlcss::svg is a frozen donor. Its Blink-anchored modules (typed attribute grammars, path data, viewport mathematics, resource resolution, paint order) are the reference and source material that evolution rungs port onto the engine of record — onto the cascade of record (Stylo), not the in-tree matcher. No new behavior lands in the donor.
  • Legacy grida may break. This decision does not touch it; future rungs that extract donor modules may leave crates/grida (and its htmlcss integration) broken or drop its render capability outright. The engine-of-record gates exclude legacy crates.
  • Evolution program. Per-feature and per-attribute rungs, each Chromium-baked and law-pinned, tracked the way CSS properties are tracked — admitted-set code, refusal-law tables, and the fixture corpus are the tracker. Static rung order: basic shapes (circle/ellipse/line) → full viewport semantics (preserveAspectRatio) → paths → strokes → translucency and opacity scopes → gradients and paint servers → text. Animation rungs (further attributes, elements, and the CSS×SMIL precedence surface) ride the animation-sampling kernel, which already models more than the SVG front-end admits.
  • What this does not supersede. Patrol-before-drop, oracle discipline, the frozen surfaces (v1 schema; the published wasm freeze contract), and the two gate classes all stand. FLIP remains unratified: this is a routing and succession decision with an explicit regression, not a conformance or parity claim, and no scoreboard artifact accompanies it. A capability is still not "landed" until its required gate is legally available and passes.
  • Naming note. After the cutover the n0 binary finally is the n0 engine end to end. The D-K time-model decision (realtime preview) is untouched; this record is exact-time file rendering only, and D-K keeps its own trigger and evidence bar.

Subsequent status (2026-07-24): best-effort becomes the host default

Owner-directed, same day, after the cutover landed. The host's default admission flips from refuse-loud to best-effort with declared degradations; --strict keeps the refusal harness. The owner's direction, quoted for provenance:

make best effort by default, and have a flag to noisy fail (the current default) to be explicit - for dev purposes, our own harness and TODO reminder. why: things will grow slowly, and the practical default is to "just render"

What this amends and what it preserves:

  • The invariant is unchanged, restated precisely: never silent wrong pixels. Under the default, beyond-slice subtree constructs are skipped and a beyond-inventory dynamic surface samples as the Base view — each declared on stderr with its stable node path and reason (degraded: skipped svg/circle[1]: unsupported element <circle>), and the render line carries the degraded count. A declared hole is not a guessed pixel; nothing renders wrong, some things render absent — by name.
  • The patrol is per attribute and per cascaded property, not just per element. An admitted element carrying a known rendering-relevant attribute the slice does not consume (opacity, transform, rx, stroke…, the enumerated set in the compiler) skips-and-declares by default and refuses under --strict — it is never painted wrong; only attributes outside the SVG rendering vocabulary are ignored, exactly as Chromium ignores them. The cascaded surface is patrolled for the enumerated properties (opacity, display: none, visibility, shape stroke, beside the typed fill/fill-opacity reads); cascaded properties beyond that enumeration remain a named open boundary of the slice, recorded in the compiler's module doc — not a coverage claim. <script> in a standalone document refuses in both admissions: the XML parse suspends at the element and content after it would be a silent, undeclared hole. A script inside the compiled inline SVG of an HTML page refuses likewise, at any nesting depth — a load-time script can rewrite the authored state the Base view renders; scripts elsewhere on the page stay under the pinned first-SVG-only entry contract.
  • --strict is the dev harness and the TODO surface. It refuses on the first beyond-slice construct exactly as the original decision text states. The refusal pins live on under strict; the degradation declarations are the same capability edge worn product-side. Both admissions are law-tested (crates/websem/tests/best_effort.rs, the host pins), and where nothing degrades the two admissions are identical — gated frame-for-frame across the full oracle corpus and byte-for-byte at the host's spot checks.
  • Document-level contracts do not degrade. No <svg> root, malformed standalone XML, and the outer viewport sizing/mapping checks refuse identically in both admissions: best-effort degrades subtree content, it never invents the canvas.
  • The capability regression bullet above now reads through this lens: the constructs listed there degrade by name at the default admission and refuse by name under --strict. Everything else in the decision — donor status, gate classes, the FLIP posture, the rung order — stands unchanged.

Subsequent status (2026-07-25): the viewport rung lands

The first evolution rung, taken out of the written static order — viewport semantics before basic shapes — because the best-effort default changed the payoff: root sizing is document-level, the one gap best-effort cannot soften, and viewBox-only documents (the most common real-world SVG shape) refused outright in both admissions. What landed:

  • Root sizing follows SVG2 §8.2. The host's requested raster is the initial viewport — the window a standalone document loads into. Explicit root width/height win; a missing or auto dimension resolves to 100% of it; the inline HTML entry keeps refusing until CSS replaced-element sizing (the auto → 300×150 and aspect-ratio rules) is its own rung. Percentage root dimensions refuse by name until the percentage basis chain is consumed.
  • The full preserveAspectRatio grammar — nine case-sensitive alignments plus none, meet/slice — applied through a near-literal transplant of the frozen donor's compute_viewbox_matrix (crates/htmlcss/src/svg/layout/viewport.rs, Blink lineage svg_svg_element.cc ViewBoxToViewTransform). Unequal-aspect viewBoxes letterbox under the default instead of refusing. The transplant was the rung's method test and it passed at first contact: all seven new Chromium bakes rendered pixel-exact with no math corrections. Malformed grammar — including the SVG2-dropped defer prefix, which Chromium treats as unparseable and silently defaults — refuses as BadPreserveAspectRatio in both admissions; this slice refuses rather than silently defaulting.
  • The adversarial round closed a silent-wrong-pixels class the rung would have widened: cascaded CSS width/height (a <style> rule or style attribute beats attributes and the auto default in Chromium) is now patrolled at the computed level on the root and on rects — the flex probe's refusal now names its true reason. Rust-superset number tokens (width="32.", valid f32 but not an SVG number, dropped by Chromium) refuse as bad numbers. The bare geometry longhands x/y/rx/ry do not exist in the pinned Stylo build and stay inside the named open boundary.
  • The oracle corpus grew 10 → 17 (viewBox-only sizing, no-sizing auto, letterbox, stretch, slice clip, alignment offset, explicit-PAR admission), each Chromium-baked at a window sized to its declared dimensions — the window is the initial viewport — with the ten prior oracles verified byte-identical under the resized windows. unsupported/ holds the malformed-grammar and percentage cells with typed-refusal laws. rframe and the n0 kernel needed no changes: the per-node affine carried everything.

Remaining static rungs are unchanged in content: basic shapes (circle/ellipse/line), paths + groups/transforms, strokes — the tiger milestone — then the rest of the written order.

Subsequent status (2026-07-26): the basic-shapes rung lands

The second evolution rung, and the first to add geometry rather than mapping: <circle> and <ellipse> are admitted. <line> is deliberately left out of it — fill never paints on a line (SVG2 §10.5: line elements have no interior), so before the strokes rung it cannot produce a pixel; admitting it would grow the vocabulary without moving one.

  • The kernel again needed nothing. rframe::Geometry grows Ellipse(Rectangle) — the axis-aligned ellipse inscribed in a local-space box — and n0's glyphless compiler lowers it to ItemKind::OvalFill, which the drawlist and painter already carried for the n0-XML path. The exact-bounds law and the per-node affine were unchanged: a scaling viewBox carries an ellipse exactly as it carries a rect.
  • Degenerate radii are admitted nothings, not refusals. SVG2 says an invalid radius must be ignored and a zero radius disables rendering, and Chromium implements it as a used-value clamp (LayoutSVGEllipse), so a missing, zero, or negative r resolves to zero extent and paints nothing. The rx/ry auto matrix follows: absent adopts the other axis, a negative radius is that same auto (the frozen donor's Chrome-confirmed reading, re-proved against the pinned bake version), both-auto and either-zero disable rendering.
  • The auto keyword is a CSS value, not an attribute value. The adversarial round refuted the first implementation here: Blink parses geometry presentation attributes with the SVGLength grammar, where auto is invalid and maps an explicit 0px — Chromium renders nothing, the opposite of the absent attribute's adopting auto. Reading the keyword would have painted an ellipse the browser does not, so it refuses as a bad number instead. The root width/height keyword read is not the analogous case: there the CSS sizing properties genuinely take auto.
  • Three silent-divergence classes closed with the rung, each live- probed rather than reasoned: attribute lookup ignored namespaces, so a prefixed foo:r was consumed as geometry (it now requires the no-namespace attribute every SVG rendering attribute lives in); numeric attributes were trimmed with Rust's Unicode str::trim, admitting NBSP-padded numbers Chromium rejects (now exactly the five ASCII characters the SVG grammar calls whitespace); and CSS transform/clip-path/filter/mix-blend-mode — all engine = "gecko"-gated in the pinned servo-mode Stylo, so the cascade drops the declaration and no computed value survives to patrol — rendered as if absent. The authored text is now patrolled at both ingresses: the style attribute per element, a <style> sheet at the document level, since a stylesheet is not attributable to one element without selector matching.
  • The pixel gate learned a declared tolerance, and only for curves. Chromium reaches a filled ellipse through the same SkCanvas::drawOval this engine calls, but through its own build of Skia; the two analytic-AA scan-converters disagree on fractional coverage along the curve, identically across every available construction (draw_oval, draw_circle, PathBuilder::add_oval/add_circle, an oval RRect). Byte exactness is therefore not reachable for a curved edge by any choice of call. The gate drops that one property and keeps the rest: a differing pixel must lie within a pixel of the fixture's declared ideal boundary, within a declared per-channel delta, and under a declared count — all three pinned at the measured values (worst cell: 6 pixels at delta 3; farthest differing pixel: 0.51px from its boundary). A misplaced, mis-sized, or miscolored shape still fails loudly, and svg-circle-defaults-clip bakes byte-exact with no tolerance at all. primitives.json goes to schema_version 1 because a passing gate now means something an old reader would misread.
  • The oracle corpus grew 17 → 24. Sampling stays the rect-x proving slice: an <animate> under a materialized circle is a declared blocker, not a silent admission.

Remaining static rungs: paths + groups/transforms, then strokes — the tiger milestone, at which <line> joins — then the rest of the written order.

Subsequent status (2026-07-26): the container rung lands

<g> and the transform grammar are admitted — the rung that unblocks what the previous two already built. In the legacy L0 corpus 101 of the declared skips were <g>, and because a group skip drops its whole subtree, the shapes inside were unreachable: admitting circles and ellipses had bought fewer pixels than it looked like. All 37 of those documents now render and <g> is gone from the skip list entirely.

  • Containers are flattened, not represented. A <g> contributes a transform and a place in paint order; the resolved contract already carries both per node, so rframe and the n0 kernel changed again not at all — three rungs now with no kernel change. The equivalence is exact only while every construct needing a real group scope (opacity, clip-path, mask, filter, mix-blend-mode, isolation, display) still refuses; when a scope-bearing rung admits one, the contract grows a scope then, driven by that producer, rather than speculatively now.
  • The walk became recursive, with nested degradation paths (svg/g[1]/path[1]) and per-parent ordinals. A failure on a container fails its subtree, because nothing inside can be placed without it; a failure on one descendant is that descendant's own hole. Without that split, best-effort would drop a whole illustration for one unsupported child — the L0 corpus is mostly groups full of paths.
  • The transform grammar is the donor's tokenizer with its leniencies removed. The donor filters unparseable arguments out of its list, so translate(10,abc) silently becomes translate(10,0); here one bad number invalidates the list, arity is exact, and the comma-wsp separator rules refuse a leading, trailing, or doubled comma — each of which Chromium rejects outright, painting the element untransformed. Quarter turns come from integer matrices (f32 cosine of a right angle is -4.37e-8, not zero), and that shortcut is bounded: past 90 * 2^23 every quotient is integral, so an unbounded test would snap arbitrary angles onto an exact matrix.
  • The AA boundary is narrower than the previous rung claimed. All eight new cells bake byte-exact, including a 45° rotation whose edges land off the pixel grid. Straight edges agree between Chromium's Skia and the pinned skia-safe; the declared tolerance is specific to conic edges, not to anti-aliasing. Corpus 24 → 32.
  • Two cleanups. <title>/<desc>/<metadata> join <style> as non-rendering — no geometry and no hole, where they had accounted for 72 skips reporting differences that do not exist. And a stylesheet declaring a property the cascade cannot represent is now strict-refused but best-effort-declared-and-rendered, restoring a document the previous rung's document-level refusal had regressed. That required an honest third degradation action: DeclarationIgnored — the element rendered without a value Chromium honors, which is neither a skip nor a sampling policy.

The adversarial round was the most productive yet, and six of its findings were defects in this rung's own work: the root <svg>'s transform was silently dropped (Chromium applies it to the CSS box, outside the viewBox mapping, so it refuses by name until that rung); skewY was a copy of skewX; a composed transform could overflow to a non-finite matrix from in-grammar input and kill the whole frame with nothing named; display: contents was unpatrolled; only the first stylesheet finding was reported, and on the inline-HTML entry its path was fabricated; and the depth bound could not prevent the crash it existed to prevent, because two other walks over the same tree recursed unbounded before the compiler ran.

One process note worth recording: a verifier agent mutated engine source to test whether a law would catch a transposed matrix slot, and left the mutation in place. It was caught by the very law the round asked for, but the lesson generalizes — an adversarial round with write access must be treated as a source of edits to audit, not only of findings to read.

Remaining static rungs: paths, then strokes — the tiger milestone, at which <line> joins — then translucency and opacity scopes, paint servers, text. Text is now the largest single block in the L0 corpus (167 declared skips, visible only because containers stopped masking it).

Addendum — the paths rung (2026-07-26)

<path> is admitted: the SVG path-data grammar compiles to a resolved command stream, fill-rule is consumed from the one cascade, and the elliptical arc refuses by name. Thirteen new Chromium cells bake byte-exact (corpus 32 → 46), and the contract grew its second geometry kind — the first growth in four rungs, and still no change to the n0 kernel.

  • The contract grew minimally, and the growth is checked. rframe::Geometry::Path carries a PathData: canonical absolute commands, the fill rule, the tight local bounds, and whether every contour closed. It has no arc command — an arc is authored syntax whose lowering decides the pixels, so that choice belongs to the producer, the only party that can verify it against the browser it is matching — and no rational conic, because the one producer that needs one is not admitted yet. Bounds are solved once, by the contract, because the producer needs them for the node's bounds and the consumer for coverage, and two implementations would have to agree bit-for-bit.
  • Canonical form is the producer's job, and each normalization was measured first. SVG allows move-only contours, redundant Zs and an implicit move after a close; the contract does not. Each removal was verified pixel-neutral in Chromium on anti-aliased geometry before being applied — see the adversarial finding below for why that qualifier is the whole point.
  • Two deliberate divergences, both declared. A malformed d refuses the whole path at its byte offset rather than rendering Chromium's valid prefix (SVG2 §9.3.9); where the prefix is empty the two agree exactly, because both paint nothing. And the arc refuses, for a measured reason: Blink's path normalizer decomposes an arc into cubics, and those same cubics — authored explicitly and rendered by Chromium — differ from Chromium's own A by 77 pixels at up to a 170-per-channel delta. What Chromium actually paints is identifiable: the half-ellipse arc M8 28 A24 20 0 0 1 56 28 Z is byte-identical to <ellipse cx="32" cy="28" rx="24" ry="20"> over every row they share. An arc reaches the rasterizer as the ellipse's conics. The arc rung must emit conics, and it inherits the oval departure below.
  • The AA boundary narrowed a third time. The basic-shapes rung declared a tolerance for "curved" edges; the container rung showed straight edges agree even rotated 45°; this rung shows the cubic, smooth-cubic and quadratic cells bake byte-exact. Curves as such are not the boundary — the weighted rational conic is. No path cell carries a tolerance.
  • Six CSS patrol leaks, each measured, each closed. The d property (Chromium paints a stylesheet's d: path(…) in place of the attribute); the all shorthand (all: initial makes Chromium paint nothing where this engine painted attribute geometry); vendor aliases of names already on the list (-webkit-clip-path, -webkit-transform, -webkit-filter all paint); CSS motion path (offset-path/offset move a shape, and a whole subtree on a container); a CSS comment or an ident escape adjacent to a property name (d/**/:, \000064:), which leaves the declaration valid for Chromium while the scanned text no longer names a listed property; and a <style> whose CSS is split across text nodes by a comment node, which the cascade concatenates and the patrol scanned separately. The scan now strips comments and vendor prefixes, refuses any escaped property name outright, and reads the sheet the cascade actually compiles. It is still not a CSS tokenizer, and the honest fix — tokenizing with the parser the document already carries — is recorded as such in the code.
  • A false explanation in earlier work, corrected. The CASCADE_PROPERTIES_NOT_REPRESENTED doc claimed every listed property is engine = "gecko"-gated in Stylo. Checked name by name against the pinned revision, that is true for clip-rule, paint-order, transform-box and the marker/offset families; false for transform, translate, rotate, scale, transform-origin, clip-path, filter, mix-blend-mode and isolation, which the build represents and this compiler simply does not read; and wrong a third way for backdrop-filter, mask-image, mask and offset-path, which are servo_pref = "layout.unimplemented"-gated. The list's behaviour was right — the authored-text scan is a superset of what a computed-level patrol would catch — but the stated mechanism is what a next author would rely on when deciding a name is safe to read.

The adversarial round found the rung's sharpest defect, and it was a defect in a claim. M x y Z — a contour that only moves and then closes — was dropped as "a contour that draws nothing", with both the module doc and a law asserting Chromium paints the authored and normalized forms identically, "measured, not assumed". It is not neutral: dropping it moves up to 96 pixels of the surviving geometry, because an extra contour is an extra contour to the scan converter. The law passed only because its coordinates were integer and axis-aligned, where no edge has fractional coverage to perturb — a fuzz of 40 randomized fractional-coordinate triangles diverged 40 out of 40. The construct also strokes as a cap-shaped dot, so the contract's stated reason for dropping it ("no fill area and no stroke geometry") was false in its second half and would have misled the strokes rung directly. The fix is exact rather than a tolerance: Chromium renders M x y Z byte-identically to M x y L x y Z, a form the contract already carries, so the producer resolves it into that spelling. Its corpus cell uses fractional coordinates on purpose.

Two smaller claim defects from the same round: the module doc grounded the number grammar on a spec-versus-browser disagreement it could not verify from this machine (the code now states only what was measured), and both a law and a fixtures README called the marker-* patrol "provably inert" when nothing else reads a marker property — the property is the paint trigger, so that patrol is the load-bearing one. pathLength was the over-refusal at this rung; the source path-distance amendment below later graduates it.

One finding is recorded rather than fixed, because it is systemic and predates this rung: a SMIL <animate>/<set> targeting a consumed attribute is active at load in Chromium, which paints the animated value, while a Base render paints the authored attribute and declares nothing. Admitting d and fill-rule widened the surface, but the same silence is measurable on x and fill from earlier rungs. It belongs to its own rung: declare it, keeping Base as the authored state while telling the caller the browser would paint something else.

Measured payoff. The L0 corpus renders 37/37 documents, with its 38 <path> elements compiled instead of declared; text (167 skips) and strokes (66) are the two remaining blocks. The Ghostscript tiger — 240 paths, 241 groups, no arcs — now renders at exit 0 with 78 declared degradations, every one of them a stroke or stroke-width attribute on a <g>, and nothing else. Strokes are the last rung between the ladder and the tiger.

Addendum — the strokes rung, and the tiger (2026-07-26)

Strokes are admitted, <line> joins, and the tiger renders: 240 paths, 241 groups, --strict, exit 0, zero declared degradations, both admissions byte-identical, and 96.27% of its pixels byte-identical to Chromium's render of the same file. Twenty-four new cells (corpus 46 → 70), 23 of them byte-exact. (The stroke family has since grown to 31 cells, 30 byte-exact, when the closed-contour cap was finished across every painter arm; primitives.json is the count of record.)

  • The contract grew a stroke, not a paint mode. FrameNode.stroke: Option<Stroke> carries paints, a width in the node's local space, a cap, a join and a miter limit. Two absences are deliberate. There is no alignment field: a Web stroke straddles its geometry, that is the only alignment any Web source can express, and an inside- or outside-aligned stroke grows the type when a producer needs one. And there is no invisible stroke: construction returns None for a zero width or an empty paint stack, so no consumer re-derives "is this visible".
  • The width is a length, not a number. Reading it as a typed cascaded value buys 8px, 0.5em, inheritance through a <g>, a stylesheet override, CSS keyword case-insensitivity on the cap and join, and the correct fallback for a negative width — the declaration fails the property's non-negative grammar, so the cascade drops it and the inherited or initial value stands, which is what Chromium paints. None of that is this compiler's code.
  • bounds stayed the geometry's. A stroke paints outside it, so the field's doc now says so and the consumer inflates it by the stroke's own reach (Stroke::outset, which accounts for a miter's extra length) when it needs the covered area for damage. Keeping bounds exact is what lets the exact-bounds law stay a law.
  • <line> needed no geometry kind. It compiles to a two-command path. Chromium's <line> is byte-identical to the equivalent <path> (measured), a line has no interior for a fill to cover, and a two-point path has zero area — so the caps, the joins and the zero-length rules all come out identical for free. Its endpoints default to zero, which makes a bare <line> a zero-length segment: nothing under a butt cap, a dot under a round one.
  • The zero-extent rule is an element rule, not a fill rule. SVG2 §10.1 disables rendering of a <rect> with a zero width/height, or a <circle>/<ellipse> with a zero radius — including its stroke. Chromium paints nothing for a zero-extent stroked rect (measured), while a naive stroke of a zero-extent box would draw a line. A <path> is deliberately exempt: a zero-extent path is a zero-length segment, which strokes as a cap-shaped dot.
  • A dash array that paints nothing is admitted. stroke-dasharray: none, an all-zero array, and an invalid value all render solid in Chromium (measured), so the refusal tests for a dash that would paint rather than for a non-empty list. Refusing on non-empty would have dropped documents the browser renders solid — and stroke-dasharray="0" is something authoring tools actually emit.
  • Two patrols stopped being over-refusals. vector-effect and paint-order were provably inert while strokes refused; consuming strokes made both load-bearing. That is exactly the trap the earlier rungs kept them for, and the reason pathLength entered the patrol at this rung even though nothing read it yet. The source path-distance amendment below later graduates it.
  • Strokes land inside the declared conic class, mostly below it. 23 of 24 cells bake byte-exact — including every cap, every join, the miter-limit bevel, the elliptical pen from scale(2,1), and the stroked circle and ellipse. The one exception is the round join at a closed contour's closing corner: 4 pixels at delta 3, declared with that join's arc as its boundary.

The tiger. The Ghostscript tiger is the de-facto SVG smoke test, and it is now a milestone rather than a target. It needs exactly what the last four rungs admitted and nothing else: a viewBox-only root (the viewport rung), one matrix() on a group (the container rung), 240 <path> elements with relative s/c/m/l/v/z data and no arcs (the paths rung), and stroke + stroke-width inherited through <g> (this one). Before this rung it rendered with 78 declared degradations, every one of them a stroke attribute on a group; now it renders with none.

The remaining 3.7% of differing pixels are curve boundaries — the tiger is almost entirely conics and cubic outlines — and they sit where the corpus's declared departure says they will: 3042 of the 9788 differ by a single level, and only 25 pixels of 262144 differ by more than 64.

The tiger file is local-only and deliberately not committed. It is AGPL (Ghostscript authors, derived from tiger.eps) and this repository is dual Apache-2.0 / MIT, which is precisely the license-restricted case fixtures/local/ exists for, beside the W3C SVG 1.1 suite and resvg's tests. Provenance, the fetch command and the sha256 live in fixtures/local/tiger/PROVENANCE.txt — a file that exists only on a machine that has already fetched the tiger, so this measurement is not reproducible from the repository alone, and nothing committed here carries the URL or the hash to make it so. It is a reported observation, not a gate. The engine's capability is gated by the committed byte-exact corpus, which is reproducible from a clean checkout; the tiger says only that the ladder holds on a real-world drawing of that size.

The adversarial round found three unit-basis defects, all of the same shape. The rung consumed its first cascaded length, and a length is only as good as its basis — which this build does not always have. A viewport-relative stroke-width was the sharp one: Chromium resolves 1vw against the SVG viewport (0.64 units on a 64x64 document, byte-identical to an authored 0.64), while the cascade's device is pinned at 1280x720 and computed 12.8 — a twentyfold error, painted silently. ex/ch resolve from placeholder font metrics rather than measured ones. Both families now refuse by name; threading the document's real viewport into the cascade's device is the honest fix and its own rung, since it moves media queries too. The third was the opposite problem: em was correct from a stylesheet and wrong from a presentation attribute, because a bare font-size="32" is invalid to the CSS grammar and was dropped — Blink parses SVG presentation attributes in a mode where a unitless length is user units, so csscascade now retries a bare number as px and admits font-size for its basis alone. My own law had tested only unitless, px and em-at-the-default, which is exactly why all three passed.

The round also found that Stroke::outset() did not bound what its doc claimed: it inspected only the join, and a square cap's corners sit at radius·√2 from the endpoint, so a round-joined square-capped diagonal inked outside the coverage box two consumers were told to trust. It was accidentally true in the common case, because the default miter join at limit 4 swallows the cap — which is why no law caught it. And a negative <rect> extent aborted the whole render with an internal message naming a VisualRef, where Chromium disables that one element and paints the rest: the shapes rung's defect, adjacent enough to this one's zero-extent rule that leaving it would have made the rule's doc a lie.

Where the ladder stands. The L0 corpus renders 37/37 documents, and its declared skips are down to 351 with a clear shape: <text> is 167 of them (48%), then <defs>/<use>/<filter> and the paint-server fills, then stroke-dasharray (17), <polygon>/<polyline> (12), and rounded rects (8). Text is the next rung by a wide margin. Also queued, each with its brief already written by measurement: the arc (emit conics, inherit the oval tolerance), the points shapes, dashing, opacity and translucency, paint servers, and the declaration of a SMIL animation that targets a consumed attribute.

Addendum — the cub: a committed scene, static and sampled (2026-07-26)

The corpus gained svg-scene-cub — an original composition that covers the tiger's feature set in 17 materialized nodes instead of 240, committed as a fixture pair so the whole rung ladder is gated both statically and under exact-time sampling. Five frames per pair (Base plus four samples), ten across the two sampling fixtures, every one byte-identical to Chromium.

  • It exists because the tiger cannot be committed. The tiger proved the ladder but stays untracked under fixtures/local/ for its AGPL provenance, so the capability it demonstrated had no committed cell. The cub is drawn to the same feature list — viewBox-only root sizing, a container with a transform and a nested <g>, fill/stroke/stroke-width/stroke-linejoin/ stroke-linecap inherited through both, cubics and quadratics, relative command runs, multi-subpath paths, round caps and joins, fill="none" and stroke="none", <rect>s in a group, a <line> — and renders --strict with zero declared degradations at zero differing pixels.
  • A composition is a different gate from the single-feature cells. Each primitive cell isolates one construct; the cub is the only cell where inheritance, container nesting, paint order, curve rasterization and stroking must all be right simultaneously for the pixels to land. At its gate size it passed on the first probe, before any harness code existed. At a second size it found a defect five rungs of single-feature cells had missed — recorded below.
  • The sampling laws now hold over a scene, not just a shape. The host law states the property a composition makes checkable: every pixel that differs between the Base frame and a sample lies in the animated rect's own rows. Sixteen nodes of curves and strokes render identically at every time, and the frozen pair is the same frame. The frame-side laws read the animated node by a declared index and node count (cases.json's frame block), so a fixture that silently stops materializing an element fails there instead of quietly weakening every law below it.
  • Two absences in the drawing are the slice speaking. The animated element must be a top-level <rect> (the inventory admits an <animate> only on a materialized direct child of the root), so the sliding block sits beside the figure rather than inside its group. And the scene carries no <style>, no style= and no color=: the first two are dynamic-inventory blockers, and color is a presentation attribute Chromium honors that this engine declares rather than paints — so currentColor, which the corpus reaches today only through a style attribute, is unreachable in a sampled document by either door until its own rung lands. Writing the fixture is what made that intersection visible.
  • No <circle> or <ellipse>, on purpose. The muzzle, eyes and ear tips are cubic approximations of ellipses. Filled and stroked cubics bake byte-exact while a true rational conic does not, so the scene gates at zero difference with no tolerance — the declared AA departure stays exactly where the strokes rung left it, on the weighted conic alone.
  • The sampling corpus is now plural. cases.json carries a fixtures array (schema 1), the baker loops it with a per-fixture initial viewport and validates each declaration structurally — including that a fixture's authored value differs from its first sample, so Base and Sample(0ns) can never silently coincide — and each fixture's oracles live under chromium/<id>/. The rect-x pixels moved directories and were re-verified byte-identical by the bake, which is the proof the rename lost nothing.

Verifying the cub at a second size found a defect the five rungs never showed. The scene gates byte-exact at 96x96. Rendered at 48x48 — the same document, a 1x viewport mapping — 242 of 2304 pixels differ from Chromium. That is not the declared conic departure. Bisecting it by measurement:

case (closed cubic contour, fill + stroke)stroke width, at 1xdiffering
stroke-linecap="butt"1 device px0
stroke-linecap="round"1 device px185 of 2304, worst channel delta 100
stroke-linecap="square"1 device px185, worst delta 128
stroke-linecap="round"2 or 3 device px0

A cap cannot exist on a closed contour — and Chromium agrees: its butt and round captures of that document are byte-identical to each other (0 differing pixels), so its raster is cap-invariant there. This engine's is not. The cap is carried correctly all the way down; the divergence is in the consumer, which paints a stroke by filling the outline StrokeRec::apply_to_path returns, and that outline is not cap-invariant for a closed contour at a stroke width near one device pixel. Butt is byte-exact, so butt is the right answer and the other two caps are silently wrong pixels — the invariant this program does not allow.

Two things kept it hidden. No committed cell combines all three triggers (a closed contour, a non-butt cap, and a ~1-device-pixel stroke width): the cap cells stroke open contours, and the closed-contour cell uses the default butt. And the tiger never declares a stroke-linecap at all, so its 96.27% agreement was never going to show this. The cub is the first fixture that inherits a round cap onto closed contours — it just does so at a size where the artifact vanishes.

The fix is its own rung, not a patch here: caps are inert per contour, so a path mixing open and closed contours (the cub has both) cannot be normalized wholesale — it needs per-contour cap handling in the stroke lowering, plus corpus cells at a one-device-pixel stroke width, which the corpus lacks entirely. Recorded rather than fixed, with the reproduction above.

Addendum — the cap defect, closed (2026-07-27)

Fixed. Re-measuring first moved every part of the diagnosis above, which is worth recording as much as the fix:

  • The attribution was wrong. A centred SVG stroke does not fill StrokeRec::apply_to_path's outline; it sets PaintStyle::Stroke and hands Skia the cap — the same stroker Blink drives. Two consumers of one stroker were disagreeing.
  • The fill is not involved. A closed contour with fill="none" diverges identically, so the earlier "fill + stroke" framing was incidental.
  • It tracks the device width, not the authored one. The same document rendered at 2x diverges at an authored 0.5 and agrees at an authored 1; the threshold sits between 1 and 1.25 device pixels. The register's table read as a property of the authored value, which it is not.
  • An open contour is byte-exact at every width and every cap — measured across butt/round/square at 0.5, 0.75, 1, 1.25, 1.5, 2 and 3 device pixels. So this was never thin-stroke rasterization in general. It is a cap appearing where a closed contour rejoins.

The fix is the semantics: a closed contour has no ends, Chromium's raster is cap-invariant on one, so n0 normalizes the cap to butt when every contour is closed. One draw, one composite pass, byte-exact at every width probed.

The exception the corpus caught. A contour with no extent is a point, and SVG2 §13.2 makes the cap the only thing that renders it: M44 32 Z under a square cap is a dot Chromium paints, and normalizing that cap away erased it. svg-stroke-zero-length-dot failed within minutes of the change. The predicate now declines to normalize anything that closes on the point it opened at, comparing on-curve endpoints only — a false positive merely keeps the authored cap, a false negative would delete a dot.

The mixed case refuses instead. A path carrying both open and closed contours needs two caps at once and one paint carries one. Splitting the draw was implemented and measured, and it is worse: byte-exact below a device pixel, then 32 to 47 differing pixels at 1.25 and 2 units, because the two runs' anti-aliased edges composite twice where they overlap. So websem refuses a non-butt stroke-linecap on a mixed path by name. It over-refuses — the error needs a device width the compiler cannot know — and serving the case properly means stroking each contour to an outline and unioning them into one filled path, which is its own rung.

The corpus gained the intersection it lacked: a closed contour, a non-butt cap and a one-device-pixel width, one cell per cap, all three byte-identical to Chromium. The old cap cells stroke an open line sixteen units wide and the old closed-contour cell takes the default butt, so between them they covered each half of the trigger and never both at once.

svg-scene-cub is now byte-exact at 48x48 as well as at 96x96, and the declared AA departure stays exactly where the strokes rung left it — the weighted rational conic alone.

Addendum — the load-active SMIL hole, closed (2026-07-30)

The finding recorded at the paths rung — a SMIL <animate>/<set> targeting a consumed attribute is active at load in Chromium, which paints the animated value, while a Base render paints the authored attribute and declares nothing — is closed. Measured before the fix, the hole was wider than the record stated: the host's Base-time degradation filter also swallowed the SamplesAsBase declaration the library did emit, so <set attributeName="fill" to="red"/> rendered the authored fill at exit 0 with zero declarations in both admissions, --strict included, while Chromium paints red. The refusal corpus had no SMIL row, so no gate could notice.

The fix is a classification, not a patch over the filter. The sampling inventory's findings now split by what they distort:

  • Sampling-only blockers — dynamic surfaces that leave the Base view honest: event handlers, CSS animation carriers, <style> sheets, the inline-HTML entry block. Unchanged: Base renders, strict refuses the sample request, best-effort declares SamplesAsBase and resolves samples to Base. Everything the host's Base-time filter hides is now genuinely Base-inert, so the filter stands as written.
  • Authored-state overrides — beyond-inventory animation elements. SMIL defaults begin to offset 0s, so each is active the moment Chromium loads the document: the target's authored state never honestly renders. Strict refuses at construction, like any beyond-slice construct. Best-effort recompiles with the SMIL default target (the parent) left out of the frame — a declared hole at the target's stable path, in every view, never a wrong pixel in any. An override that cannot be attributed to one skippable element — an href retarget (id resolution is not owned), a root-<svg> target (the override reaches the whole canvas) — refuses in both admissions, exactly as <script> does.

A deliberate departure from the recorded remedy. The paths-rung note sketched "declare it, keeping Base as the authored state while telling the caller the browser would paint something else." That would render a value Chromium does not paint, annotated — a declared wrong pixel, which the first law does not recognize as a category. The attribute patrol set the precedent: an admitted element carrying an unconsumed rendering attribute is skipped, not painted-with-a-note; painting-with-a-note is reserved for the one construct that is genuinely unattributable (a stylesheet, absent selector matching). An attributable SMIL override follows the attribute precedent.

Named over-refusals, kept. The inventory owns no per-element applicability model, so <animate attributeName="x"> under a <circle> — inert in Chromium, since x does not apply — still skips the circle: a declared hole where Chromium paints, preferred to the model the slice does not own. Likewise a begin-conditioned animation (begin="click", begin="2s") skips its target although Chromium's load-time picture shows the authored state; SMIL timing beyond the admitted dur/fill=freeze shape is the future animation rungs' business, and animation-sampling already models more than this front-end admits.

What remains open, now named. The admitted <animate attributeName="x"> keeps its corpus-pinned Base semantics: Base is the static projection (the animation contributes nothing), baked as base-static-projection cells against a Chromium document with the animation stripped, and "Base is not shorthand for Sample(0)" stays a law. The divergence between that projection and Chromium's load-time picture of the animated document is a documented, gated semantic for the one admitted element — no longer a silent one for every other.

Three refusal-corpus rows gate the closure (svg-smil-set-load-active, svg-smil-animate-transform, svg-smil-retarget-href), and the laws that pinned the defective behavior moved with it (crates/websem/tests/best_effort.rs, crates/websem/tests/svg_animation_x.rs, the groups and shapes contracts).

Addendum — the points rung (2026-07-30)

<polygon> and <polyline> are admitted. Both lower to the line-segment path the contract already carries — MoveTo + LineTo* (+ Close for a polygon) — exactly as <line> does, so the rung cost the contract nothing. Closure is the one semantic difference between the two elements, and the points grammar runs through the same number scanner as path data, so the two grammars cannot drift.

Measured before written. The grammar's edges were probed against Chromium 149 before the parser existed, and the probe moved the design in one place: a trailing separator after the last complete pair is accepted in points (unlike the viewBox grammar, whose trailing comma stays a refusal), so the slice admits it, Chromium-baked. The rest confirmed the plan: a leading or doubled comma, a trailing dot, and a percent are errors whose valid pair prefix Chromium renders — this slice refuses the whole element by name instead, the paths rung's declared divergence restated (svg-points-odd-coordinate is its refusal-corpus row); a filled polyline paints as if closed; and a single point splits by closure — the polygon is the zero-length closed contour whose cap paints a dot, resolved into the contract's canonical M x y L x y Z spelling (the cap-normalization exception from the cap-defect addendum fires for it unchanged), while the polyline is a neutral move-only contour that paints nothing under any cap and is admitted as not-a-node.

Eight cells, byte-exact. Fill with mixed separators, the trailing separator, an evenodd self-intersecting star (the cascaded fill-rule read, shared with <path>), the stroked closure split (closing segment and joins on the polygon, caps and no closing edge on the polyline), the implicit-close fill equivalence, and the two single-point cells. All eight bake byte-exact — no new tolerance; the declared AA departure stays the weighted rational conic alone.

The register moved with the slice. The svg-polygon/svg-polyline refusal rows graduated (the enumeration gate forces the move), the L0 basic-shapes host pin lost its three points-shape holes, and the polygon-fill-probe strict pin flipped from the capability edge to an admitted probe. At this rung the points shapes inherited the path patrols, so pathLength and the marker properties remained refusals. The later path-distance amendment graduates pathLength; the marker boundary remains.

Addendum — the visibility rung (2026-07-31)

display: none and visibility are consumed — the first rung that turns over-refusals into the correct nothing rather than admitting new paint. Both enter as presentation hints through the one cascade (csscascade's admitted set grew its ninth and tenth properties, precedence-law-gated), so the attribute and every CSS spelling resolve identically and an author rule beats the attribute — measured, and baked as the un-hide cell.

The split is semantic, and each half is measured. display: none generates no box: the subtree is pruned and a visibility: visible descendant stays gone. visibility: hidden and collapse (identical for shapes) turn off one element's own paint; the property inherits, and a descendant whose computed value is visible un-hides itself — the walk therefore still descends through hidden containers, and each element's own computed value decides its node. Neither is a hole: nothing is declared, because Chromium also paints nothing — r="0"'s admitted nothing, restated. A pruned or hidden element's other unconsumed properties stay silent too: a refusal there would turn a correct nothing into a false alarm. display: contents stays a named refusal (its own corpus row): it paints children in the parent's place, which the flattened walk cannot express without dropping a transform silently.

The oracle corrected the probe. An embedded-context probe suggested a root display: none never paints; the bake of svg-display-none-root showed a standalone document's outermost <svg> ignores the property and paints normally — only an embedded (inline-HTML) root generates no box. The compiler now splits by entry, the law file pins both halves, and the divergence between probe context and document context is exactly why cells are baked from the entry they gate.

Seven cells, byte-exact (92 total): the shape and container prunes, the standalone-root proof, hidden and collapse, the descendant un-hide, and the author-rule-beats-attribute cell. visibility_contract.rs is the rung's law file; the smuggle law's display/visibility rows graduated into it, and svg-display-contents replaced the pair in the refusal corpus.

Addendum — the translucency rung (2026-07-31)

fill-opacity, stroke-opacity, and translucent sRGB paint are consumed. One rule generates the rung: paint alpha is the product of the colour's own alpha and the paint-level opacity, multiplied in float and quantized once — Chromium composites the product, not the quantized factors, and the multiplied cell (svg-fill-opacity-times-alpha) pins the rounding byte-exactly. Both properties enter as presentation hints (csscascade's admitted set grows to twelve) and fold at the two typed paint reads; the fill and stroke stacks are separate, so the fold can never composite the wrong paint, and a zero opacity resolves to the same admitted nothing as fill: none.

Seven cells, byte-exact (99 total): the overlap composite in both spellings of the one <alpha-value> grammar, the rgba equivalence, the multiplied quantization cell, inheritance through a container, the stroke-over-own-fill compositing split, and the miter join's single-pass self-overlap. The first quantization guess (round, once) matched Chromium on every cell — no tolerance entered.

Element opacity stays refused, by design. It composites fill and stroke through one layer; folding it into per-paint alpha would double-blend where they overlap. It is the group-scope rung's first producer, and the refusal-law table says so at its row. The strokes-rung laws that pinned stroke-opacity and translucent paint as refusals graduated into the translucency contract, and the beyond-surface paint laws keep their point through a colour space the slice still refuses.

Addendum — the percentages rung (2026-07-31)

Shape-geometry and stroke-width percentages are consumed. The bases are SVG2 §7.10's, threaded once from the root: the viewport's user-unit extent — the viewBox when one maps the viewport, the root's own extent otherwise — with x-axis lengths against its width, y-axis against its height, and the "other" lengths (a radius, a stroke width) against the normalized diagonal sqrt(w² + h²)/√2. The 10%-of-64x64-is-6.4-units measurement recorded when this was a refusal became the resolution's first assertion, and all six cells (both bases, the non-square axis split, the line endpoints, the stroke width) baked byte-exact.

Scoped away from the root, deliberately. Root percentage sizing keeps its document-level refusal: its basis is the host window itself, which the element-capture baker cannot express as a cell (the window is the basis, so any declared fixture dimension is circular). It graduates only with a host-level oracle. The CSS-spelled geometry surface likewise keeps its Size::Auto refusal — the compiler reads geometry from attributes, and a cascaded width on a <rect> stays a named skip.

A refusal variant retired. UnsupportedLength existed to name the percentage refusal; with the resolution landed nothing constructs it, and its essence lives on as the PercentBases resolver and its laws. The malformed spellings (5 0%, junk digits) stay BadNumber refusals, the same posture as every other invalid number.

Addendum — the anchor rung (2026-07-31)

<a> is admitted as a container: SVG2 §16.2 makes its href interaction, not paint, so it shares <g>'s compiler, patrols, transform composition, and flattening — one law asserts the equivalent <g> resolves to the identical frame, and one Chromium-baked cell (svg-anchor-container) proves the composed transform and a translucent overlap through it. The container dispatch is now parameterized by element name; nothing else moved.

Addendum — the transform rung (2026-07-31)

The CSS transform property is consumed, and consuming it dissolved the attribute as a separate concept: CSS Transforms L1 §7 makes the SVG transform attribute a presentation attribute of the one property, so the rung moved the attribute grammar into csscascade, which rewrites a valid list into equivalent CSS text (§7.3's unit assignment; the 3-argument rotate expands to its defining translate·rotate·translate sandwich) and injects it at presentation-hint level. Precedence is therefore the cascade's, not reimplemented: any author rule beats the attribute — transform: none included — the style attribute beats the rule, and an invalid CSS declaration never enters, so the attribute stands. websem reads only the computed operation list, converting it per-op to one affine with the exact quarter-turn matrices the attribute path always had.

A 40-measurement probe matrix decided the semantics before any code. The load-bearing verdicts, each now a law or a cell: SVG elements pivot on used transform-origin 0 0 — the local user-space origin, which Chromium keeps even under a negative-min viewBox (spec letter says the reference box moves with viewBox min; the oracle says it does not); percentage translations resolve against the viewport's user-unit extent; a malformed attribute list drops whole and renders untransformed (all 23 previously-refused lists re-baked as drops, so the old refuse-by-name posture flipped to Chromium-exact silence — the pixels agree, which is the law); and the grammar carries the measured leniency no browser ever tightened (csswg-drafts#2623): numbers may run together (translate(10-10) is (10, −10)) and functions need no separator, while every comma strictness (leading, doubled, trailing — list-level trailing included, a tightening this rung added) stays enforced.

Thirteen cells baked byte-exact (corpus 106 → 119): the graduated refusal fixture, both precedence directions, none-restores, invalid-falls-back, compound composition, the percentage basis, a container's cascaded transform, the negative-quadrant rotation, the -webkit-transform alias (the pinned Stylo implements it — verified, not assumed, when the name left the scan denylist), the two leniency forms, and the malformed-drop. The refusal register moved one row out and four in: transform-origin and transform-box (the knobs that move every pixel a transform touches — the second does not exist in the servo-mode build at all), the beyond-2D function family (Chromium composes translate3d on SVG — measured — so it refuses by function name rather than flattening under-measured), and the individual rotate property (Chromium composes the individual properties with transform; consuming one without the others would compose a different matrix). The root <svg>'s transform now refuses in both spellings — the computed patrol closed the stylesheet route to the root that the scan's graduation would have opened. BadTransform retired with the drop-semantics flip; its overflow half lives on as NonFiniteTransform, still named at the element.

Addendum — the use/defs rung (2026-07-31)

The engine's first id-resolution table, and with it <use> and <defs>. The architecture is the one the probe matrix forced: the referenced subtree is physically cloned under the <use> before the one cascade runs (csscascade's svg_use, at DOM freeze), so the instance is styled by the same single pass — presentation attributes and style clone with it, and inheritance flows from the use site, which is exactly Chromium's measured behavior (fill on the use colors a clone that authors none; the clone's own attribute beats it; currentColor resolves against the use site's color — and color joined the admitted hint set for it). websem renders <use> as a container of its shadow content and skips <defs> by name; the walk, the paths, the patrols and the animation inventory all see the expanded tree, so a beyond-slice construct inside an instance is a declared hole at the clone's real path, and a cloned animation element classifies against the clone it targets.

The 31-measurement probe matrix's load-bearing verdict is the styling boundary. Selector matching against an instance is totally scoped to the cloned subtree: #id, class, type and clone-internal structural selectors match; NO selector involving any ancestor outside the clone does — not defs > rect (the original's position does not carry), not use > rect, not even descendant combinators like svg rect. A clone parented in the one flattened tree cannot reproduce that boundary in either direction, so author CSS and <use> refuse together, by name, until a shadow-matching rung earns it with Stylo's shadow machinery. The 2018-era svgwg#504 claim that Blink copies the original's computed style is measurably no longer true of Chromium 149.

Everything else measured landed as law and cell: x/y translate appended inside the use's own transform; whole-document first-id-wins resolution with forward references; plain href over xlink:href; width/height inert for admitted targets; the correct nothings (an unresolved reference, a mutual cycle, an ancestor circle — each baked as pixels, each rendering exactly Chromium's nothing with nothing declared); a light-tree target painting in place and as an instance; display: none cloning onto the instance. Chains expand through with cycle guards on the expansion chain (use ids and target ids alike, push/pop so siblings never see each other's history); indirect cycles beyond the measured shapes hit a depth budget and refuse loudly as expansion overflow, as do external references and authored element children — each a register row with a fixture. <symbol> targets surface the symbol element at the clone's own path and refuse like the nested viewport they are.

Twenty cells baked byte-exact (corpus 119 → 139, the largest single rung yet). The refusal register moved one row out (svg-use graduates) and four in; svg-path-marker-end's defs half stopped declaring the moment defs was consumed, leaving the marker attribute itself as the named hole — the row now names it directly.

Addendum — the gradient rung (2026-08-01)

The engine's first non-solid paint, and the first contract amendment of the rung ladder: rframe's SolidPaintStack became PaintStack, admitting linear and radial gradients from the shared cg leaf vocabulary alongside solids (visible, normal-blend; sweep, diamond, image, and non-normal blends stay construction rejections). The repo law that read "rframe cannot express a gradient" was falsified by design and re-stated: rframe cannot express a paint that references a resource. A gradient's geometry is stated in the unit square of the item's paint box; the producer folds every SVG coordinate system into that fact, and no gradientUnits, href, or spread keyword crosses the contract.

A 72-probe Chromium matrix decided the semantics before code. The load-bearing verdicts:

  • gradientTransform is the transform property's presentation attribute on gradient elements — the attribute and an author transform declaration are byte-identical through non-quarter rotations and scales, the plain transform attribute is inert there, transform: none disarms the attribute with ordinary cascade precedence, and the value applies about the raw origin of gradient space (a scale-2 probe discriminated; the CSS-origin hypothesis died). csscascade hints gradientTransform through the same measured rewrite the transform rung built.
  • The fallback fires only on an invalid reference. A valid gradient with zero stops — including a self-cycle that composes to zero — paints nothing and leaves the authored fallback unfired; a missing id or a non-gradient target is invalid and the fallback paints. Baked as cells.
  • At this rung the backend's degenerate rules were resolved by the producer so the engine's preflight never met them: one stop and the baked r = 0 pad case became the last-stop solid; coincident linear endpoints became the last stop under pad and the ramp's integral average under reflect/repeat (measured 128,0,128 — Chromium shares the backend's rule). That evidence did not establish non-pad radial degeneracy. The opacity closure addendum below records the later measurement and correction, and also preserves one-stop gradient rasterization.
  • Ramps dither. Chromium's rasterizer dithers gradient ramps with the backend's ordered matrix; the painter now sets the same flag and 26 of 27 gradient cells bake byte-exact — including the dither pattern itself. fill-opacity over a gradient multiplies at the backend's 8-bit alpha step (measured ×128/255, not ×0.5), pinned in the painter and its cell.
  • Stops are attribute reads — the pinned servo Stylo has no stop-color/stop-opacity longhands, so author CSS on stops refuses by name (a sheet at document level, a stop's style attribute at the paint), closing what was a silent drop. currentColor in a stop resolves against the gradient's own ancestor chain through the one cascade — never the referencing element (measured).
  • The id table is the document's: whole-document, first-id-wins, <use> shadow clones excluded (a clone earlier in expanded order does not shadow the original — measured), gradients referencable outside <defs>, href beating xlink:href, chains crossing gradient types for stops and common attributes but never geometry, cycles killing only the edge.

The glyphless engine seam took its once-deferred decision: a path's paint box anchors at the tight-bounds origin (a unit-space pre-translate on the gradient's transform), and preflight_gradients — generic over the drawlist owner now — runs inside the glyphless compile, so a gradient the backend cannot shade is a named BuildError before any product exists, never a painter panic.

What refuses by name: focal radials (the shared radial leaf is concentric — fx/fy/fr wait on the paint RFD's focal amendment), color-interpolation: linearRGB (honored by Chromium, measured, and inexpressible in one backend ramp), font-relative units in gradient geometry, percentages in a gradient's computed transform (Chromium resolves them against mismatched spaces — measured and declined), external references, and <pattern>.

Twenty-seven cells baked (corpus 139 → 166), 26 byte-exact on the first gate run. Two carry the corpus's new ramp-quantization tolerance, both at one code value with measured counts: the off-center radial differs from Chromium's Skia in one pixel (an ulp at a ramp knife-edge), and the non-monotonic-stops cell — byte-exact on macOS — differs in 18 clamp-edge pixels under the Linux Skia build's SIMD path, the corpus's first measured cross-platform departure. Mapping it taught the gate to sweep the whole suite before failing, so a platform difference is now one CI round-trip. The refusal register moved svg-gradient-paint-server out and five named rows in.

Addendum — the group-scope rung (2026-08-04)

Element opacity is consumed, and consuming it grew the contract's first structural amendment — the second amendment overall, and the one the container rung's flattening promise waited on. rframe's flat node list became a checked painter-ordered item stream: a compositing Scope encloses a contiguous span as one isolated group, with balance, non-emptiness, and bounded nesting proven at construction (PathData's posture), and the new item enum forcing every consumer match site at compile time — the shape was chosen precisely so a consumer cannot read the nodes and silently skip the scopes. The effect vocabulary opens with Opacity over the open unit interval (identity and zero are producer resolutions, not scope facts) and grows per producer — clip-path is the named next effect. What a scope refuses is what the crate refuses: an effect that references a resource (mask, filter, pattern) has no representation.

A 49-document probe matrix decided the semantics before code, and its load-bearing verdict reframed the rung: Chromium's element opacity is not one route but two, one code value apart, and both are meaning.

  • The fold. Over content that is a single un-transformed, un-folded draw, element opacity is byte-identical to the fill-opacity fold — it joins the translucency rung's one float product (colour alpha × paint-level opacity × element factor), quantized once; the multiplied probes pinned the product in both stacking orders. The fold reaches through plain containers, past zero-draw and hidden siblings, and holds on stroke-only shapes (ink beyond the geometry bounds included).
  • The layer. Everything else composites through a real isolated layer, whose quantization sits one code value below the fold: fill+stroke on one shape (the double-blend fact that motivated the refusal — 57 code values from the per-paint spelling at the overlap), sibling overlap (topmost child at the group alpha, composited once), and nesting, which quantizes per layer and never flattens to a scalar product (g(.5)›g(.5) differs from opacity=.25 by exactly one code value across the whole fill).
  • The boundary between them is structural. The fold fires at most once per draw — a group over an already-folded draw runs a layer — and any non-none computed transform strictly below the scope element breaks it, while transform and opacity on the same element still fold. The measured rule websem implements: fold iff the span is exactly one draw, un-folded, un-scoped, and un-transformed below the scope element; layer otherwise — a fold replays the span with the factor threaded into the paint resolve so the product still quantizes once, identically in both admissions.

The painter's existing opacity route was measured and found to be a different meaning. n0's arithmetic-blender BeginOpacity (the model's backdrop-initialized group) produces the fold bytes — one code value from Chromium's layer. Rather than change the model's meaning, the drawlist vocabulary grew BeginIsolatedOpacity: a plain Skia layer restored source-over at the group alpha, which matches the oracle byte-for-byte, with two in-crate laws pinning the layer and nested-layer rasters against the measured bytes. Scope owners carry identity and provenance like nodes; damage treats a scope-opacity edit as the union of its span.

Fourteen cells baked (corpus 167 → 181), thirteen byte-exact on the first gate run — the rotated translucent group's straight-edge AA and the per-layer nesting quantization included. The fourteenth, the dithered ramp under a real layer, carries the corpus's third ramp-quantization tolerance at its measured bounds (336 of 2304 pixels, one code value): the layer restore halves every ramp value, and the two Skia builds round one code value apart across the ramp — the radial knife-edge's physics, multiplied by the layer.

At that rung the refusal register moved one row out and two in. svg-element-opacity graduated (the lone-fill fold, byte-exact), while svg-element-opacity-gradient and svg-root-opacity became load-bearing patrols. The gradient patrol named the missing post-materialization alpha factor; the root patrol named the then-missing transparent whole-frame scope. Those are historical rung facts, not the current boundary: the opacity-closure addendum below graduates both after supplying exactly those two facts.

opacity joined csscascade's admitted presentation hints (one grammar; percentage spelling, clamping, hint precedence, and invalid-drops all measured and baked). <use> and <a> scope exactly as <g>; opacity through a translucent target compounds per-layer, byte-identical to the nested-group cell. opacity: 0 renders the correct nothing for shapes and containers alike. The scoreboard suite's element-opacity rows are now within reach; rx/ry is the remaining row-1 construct.

Addendum — the conic rung (2026-08-06)

The elliptical arc and the rounded rect are consumed, and consuming them grew the contract's third amendment: rframe::PathCommand gained ConicTo { x1, y1, x, y, weight } — the rational quadratic, the curve class Chromium's rasterizer draws arcs and rounded corners through. The weight has its own checked domain (positive and finite, BadConicWeight by index), a conic's tight bounds are solved from the rational derivative in f64 (the quadratic N'D − ND', whose cubic terms cancel), and the amendment deliberately stops there: no arc command remains the crate's refusal. The arc parameterization is authored syntax, and which curve sequence it becomes decides pixels, so the resolution stays with the producer — exactly as the module doc's reserved slot said it would.

The repo law that read "the contract carries no conic command yet" was repaid rather than falsified; but one measured prediction fell: the corpus README reasoned that admitting arcs "inherits exactly this departure" — the oval cells' declared conic scan-converter divergence. Measured, it does not. All eleven new cells — the half-ellipse identity arc, all four flag combinations, a rotated elliptical sweep, both degenerate correct-nothings, uniform and elliptical rounded rects, the measured clamp order, and both strokes — bake byte-exact against Chromium 149.0.7827.55 with no tolerance blocks. The departure class belongs to the drawOval construction, not to the conic as a curve.

A 24-probe Chromium matrix decided the semantics before code:

  • A rounded rect is a conic path. <rect rx="8"> and the equivalent explicit A-command contour render byte-identically in Chromium itself (elliptical corners: 1 pixel at delta 2). The rect therefore lowers at the producer to four quarter-turn conics of weight cos 45°, and rframe::Geometry needed no rounded-rect variant.
  • The rect's resolution order is measured, not assumed. auto adopts the other axis's authored value first, then each axis clamps to half its own extent independently — rx="30" on a 40×48 rect is (20, 24), not (20, 20). Negative is invalid-to-auto; a used zero on either axis squares every corner; percentages base on width and height respectively.
  • Arc corrections are the spec's, byte-for-byte. Too-small radii scale up uniformly (identical to authoring the scaled radii); negative radii take absolute value; zero radius is the authored straight line; coincident endpoints elide; a smooth cubic after an arc reflects about the current point.
  • Nothing canonicalizes, and nothing reduces. Chromium paints a rotated circular arc 2 pixels from its unrotated spelling and 390° 51 pixels from 30° — its internal float noise, which no external construction can or should chase. The producer feeds the authored angle through plain f64 arithmetic: a circle's rotation then cancels algebraically, the unreduced angle's residue sits below f32, and each cell gates against its own oracle. n0-model's native-route canonicalizations (circle rotation to zero) were deliberately not copied — the Web producer's conversion is its own, verified against the browser it matches.

Consuming rx/ry closed a latent silent-divergence class: Chromium honors the CSS rx/ry spellings over the attributes (measured), and the pinned cascade cannot represent those longhands — so both names joined the d-property stylesheet patrol, declared by name instead of silently painting attribute geometry where the browser paints the sheet's. The websem parser's arc arm keeps the malformed-before- unsupported ordering it always had, now with nothing left to be unsupported: PathDataError::UnsupportedCommand and CompileError::UnsupportedPathCommand are deleted — the d grammar's every command letter emits, and the sole path refusal is a value that stops being path data. The whole-path refusal on erroneous data (vs Chromium's valid-prefix rendering) stands unchanged; admitting the prefix rule remains a rung of its own.

Graduated: svg-path-arc and svg-rect-rounded left the refusal register for the admitted corpus (the identity-measurement geometry is now its own cell), and the checklist's rx/ry presentation-attribute rows tick. d stays unchecked while the prefix rule is refused, and the CSS-property twins stay unchecked at the pin. With rx/ry landed, the row-1 scoreboard construct the register named after the gradient rung is in hand: the corpus-growth step toward FLIP eligibility no longer waits on capability.

Ratified amendment — resolved stroke dash intervals (2026-08-13)

The resolved render contract may now carry an optional stroke-dash interval cycle. This is the fourth contract amendment of the Web-first ladder, ratified before producer or consumer code under the contract-first precedent of gridaco/nothing#75.

The fact is deliberately narrower than the source grammar. It is an immutable, even-length sequence of local-space distances, alternating painted and unpainted intervals and beginning with paint. A present cycle is non-empty; each distance is finite and non-negative; and the finite sum of the cycle is strictly positive. The phase is exactly zero and the cycle restarts at the beginning of every contour. Nothing in the fact names a source unit, percentage, cascade rule, authored list length, or path calibration.

The producer resolves the source language into that canonical form. An odd source list repeats once to become even. A source none or an all-zero list reaches the same solid-stroke dash absence. An invalid declaration contributes nothing, so the cascade's surviving winner — a lower declaration, an inherited cycle, or the solid initial value — is what resolves. A positive cycle with zero-length painted intervals remains meaningful under round or square caps, which paint at those interval endpoints; under a butt cap, a cycle whose every painted interval is zero paints nothing and the stroke itself resolves away. Dashes do not alter the stroke's conservative reach beyond its geometry.

Two nearby facts remained expressly outside this amendment when it landed. A non-zero dash offset is phase, and pathLength is authored calibration rather than a resolved render fact. Both were load-bearing refusals once dashing existed: the probe matrix measured offset moving the pattern, and measured pathLength scaling dash distances on paths, rectangles, circles, and ellipses. The latter patrol therefore belonged on every admitted geometry element, not only path-like elements. The later signed-phase and source path-distance amendments resolve those ordinary cases without changing the cycle contract stated here.

The same matrix fixed the producer boundary before code: numbers and lengths, comma and whitespace separators, attribute and CSS spellings, inheritance, invalid-declaration fallback, and odd-list repetition all converge on the one cycle; percentages and mixed length-percentage arithmetic resolve against the normalized viewport diagonal. As earned in gridaco/nothing#80, unit classes with their own checklist rows may remain refused only when every silently divergent ingress is named, registered, and guarded. Viewport-, container-, and unavailable font-metric bases, variable indirection, poisoned font-relative bases, and escaped spellings retain that refusal burden here. The standard grammar remains the bar, following gridaco/nothing#77; a partial admission would be the unticked split established by gridaco/nothing#81, not grounds to widen this contract.

Dasharray rung verdict — cells landed, rows split

This subsection records the gridaco/nothing#83 verdict as it landed. Its cycle-sum premise is superseded by the 2026-08-14 ratified correction below: the apparent authored-magnitude remainder was Chromium's fixed-length used clamp, not an unrepresentable resolved-frame fact.

The amendment is exercised by 25 Chromium-149-baked cells. Together they cover the attribute and CSS spellings; number, length, percentage, exponent, comma and whitespace forms; CSS math and an authored font-size basis; odd-list repetition; invalid-declaration fallback; author-over-hint precedence; inheritance through containers and use-site instances; the none, all-zero, negative-invalid, and zero-painted cap edges; all admitted geometry kinds; closed and mixed contours; per-contour restart; and local-space scaling. Every new cell is byte-exact through the shared Web producer, resolved contract, and kernel. The corpus therefore moves from 230 to 255 gated cells. The final cell pins Chromium's renderer-level saturation of a cycle dense enough to exceed the painter's bounded dash expansion: line, cubic, rect, ellipse, and round-cap routes do not share one coarse fallback, so the cell discriminates both an all-solid and an all-absent implementation. This is measured renderer behavior, not a source normalization or a resolved-contract limit.

The probe also turned two prepared over-refusals into load-bearing patrols. Dash offset changes phase for positive, negative, and percentage values. pathLength calibrates dashes on paths and the basic geometry shapes, and calibrates dash offset on paths; at this rung its patrol stated one law across all seven admitted geometry elements. Neither fact was smuggled into the zero-phase, uncalibrated contract. Both ordinary cases graduate in the later signed-phase and source path-distance amendments. The solid-stroke mixed-contour cap refusal narrows in the other direction: dashed segments have ends on closed contours, so one authored cap is correct for every contour and the dashed case is admitted.

At this rung six new residual classes held named refusal rows: dash offset; untrustworthy unit bases; variable indirection; a poisoned font-relative basis; escaped spelling; and a cycle whose individually finite intervals summed beyond the representable contract range. The existing pathLength patrol was the seventh load-bearing class. The first five new classes and pathLength carried their gaps in their own checklist rows or the unit rows, under the gridaco/nothing#80 precedent. The later path-distance amendment graduates that seventh refusal without replacement. The cycle-sum class does not: Chromium honors the standard-track dasharray grammar for that magnitude class, and a zero-painted round-cap probe proves it cannot be normalized wholesale to a solid stroke. That is gridaco/nothing#81's exact split condition. The cells land, but both stroke-dasharray checklist rows remain unchecked until a representability rung closes the measured, registered remainder. No record claims the rows ahead of that evidence.

Ratified amendment — context paint resolves before the frame (2026-08-13)

The resolved render contract does not gain a context-paint value. The owner ratified this fifth contract amendment by authorizing the full context-paint arc after its recursion and reference-box scope was stated. A context paint is a source relationship, not a visual fact: it selects another element's fill or stroke, while the frame states only the eventual no-paint, solid, linear-ramp, or radial-ramp result. The relationship must therefore finish before the resolved boundary, and no context element, use chain, paint-server reference, or reference-box provenance may cross it.

The selection rule is exact. Without a context element, either context keyword selects no paint. Inside an instantiated subtree, the immediate use element is the next context: context-fill selects its computed fill and context-stroke selects its computed stroke. A selected context keyword repeats that step. Selection ends at the first ordinary paint value or at no paint. The paint's own colour alpha belongs to the selected paint; fill and stroke opacity remain separately inherited properties and are not copied as part of context selection.

A selected paint server keeps the coordinate space and reference box of the context element that authored that eventual server value. An intermediate use whose own paint is another context keyword does not take ownership of the server's box. Before the resolved frame is stated, the selected ramp is rebased from that context space into each destination geometry's self-contained paint facts. Object-bounding-box and user-space ramps therefore remain continuous across differently transformed descendants without asking the consumer to recover an instance relation. The context box contains geometry hidden by visibility or zero opacity, but excludes a display-pruned subtree.

The amendment is deliberately no wider than the resolved paint vocabulary. Patterns, images, and external resources remain inexpressible even when a context relation selects them; source handling must refuse them by their own names rather than disguise them as context-paint failures. Marker context is a separate applicability rung. A source parser's extension that permits a fallback after a context keyword is likewise not admitted: the standard-track paint grammar permits a fallback only after a URL, and the Chromium oracle drops the extended declaration.

This was ratified as a contract-first amendment: by itself it authorized a producer to emit an already selected and rebased paint through the existing resolved vocabulary and made no claim that the producer, cells, or checklist rows had landed. The rung verdict below records that subsequent evidence.

Context-paint rung verdict — four rows close

A 102-capture Chromium 149 matrix fixed the source semantics before the capability landed. It measured both context keywords in both destination properties and both source spellings; the no-context no-paint result; host none, currentColor, colour alpha, inheritance and CSS-wide values; all four property crossings; nearest-context recursion and independent instances; URL fallback; linear and radial gradients in object-bounding-box and user space; the eventual outer URL owner's box through nested context references; and the box contribution of hidden, zero-opacity, and display-pruned geometry. Paint opacity is not part of selection: it remains an independently inherited property.

Twenty-two of those cases are now committed Chromium cells. Eight are the atomic product of destination fill/stroke, selected fill/stroke, and attribute/CSS spelling. Four cells isolate no context, host no-paint, currentColor/alpha, and inherited/CSS-wide values. Two establish recursive selection, nearest-owner precedence, light-tree absence, and independent instances. The remaining eight establish missing-URL fallback, the inert fallback behind a valid stopless gradient, both gradient kinds in both unit systems, eventual-owner anchoring, and the three box-participation cases. Every new cell crosses the same resolved-frame and kernel boundary as the existing corpus; no context relation or reference-box provenance crosses it.

Two scratch follow-ups remain explicitly measured, not celled. Every <use> x/y translation on a nested consumption chain moves the selected paint, while the eventual ordinary paint owner remains the owner of its URL and box. Chromium constructs a context object bounding box from each descendant's transformed local axis-aligned box, not from exact post-transform curve extrema; rotated and skewed controls discriminate the two. A singular destination transform paints nothing across the admitted filled and stroked shape classes. These verdicts close the coordinate forks without widening the resolved contract.

The standard-track boundary stays sharp. Chromium drops a fallback following context-fill or context-stroke, because <paint> permits that tail only after a URL; the pinned source parser accepts the extension, so one new named registered refusal and guards cover attribute, inline-style, and stylesheet ingresses. The former load-bearing context-paint refusal graduates, leaving the register at 56 rows at that rung (and still 56 through the stroke-width replacement below). Context-selected patterns and external paint resources were also measured to propagate in Chromium, but remain refused by their own resource names. Marker context and author stylesheets across a use-shadow boundary likewise retain their own rows. Under the own-row precedent of gridaco/nothing#75 and gridaco/nothing#80, none of those gaps belongs to the four paint rows; under gridaco/nothing#77, the non-standard fallback extension is outside their grammar bar.

The CSS SVG-presentation fill and stroke rows and their SVG presentation-attribute twins therefore tick together. This is a capability verdict only. It produces no conformance score and takes no FLIP action.

Ratified correction — stroke used-value range (2026-08-14)

The broad width-refusal conclusion recorded here is historical rung evidence. The 2026-08-20 amendment below supersedes it: general positive percentage saturation is now carried and Chromium-celled, while a narrower source-precision alias remains refused.

The browser's used-value range, not the resolved render contract, closes the dasharray remainder recorded by gridaco/nothing#83. Chromium clamps each pure fixed stroke length before using it. The authored ceiling is 33,554,429 in its fixed-point CSS length range, represented in the resolved scalar vocabulary as 33,554,428. For a dash list, that clamp happens member by member before an odd list is repeated. The existing contract's finite, non-negative, positive-sum cycle therefore already states the browser's resolved fact; no contract or consumer amendment is warranted.

Percentages follow a distinct used-value rule. A moderate resolved percentage may exceed the fixed-length ceiling and remains unclamped. At the extreme, an overflow during percentage resolution causes Chromium to drop the dash effect, so the stroke becomes solid while retaining its authored cap. The same extreme percentage used as stroke width is not governed by that dash-effect result: under a discriminating transform, Chromium paints a butt-capped round or bevel join but not the default miter or round/square-cap variants. It therefore entered as a named capability at this correction instead of being normalized to one universal no-stroke result. These are property-specific outcomes, not a shared normalization of every large length.

The earlier short-path probe did not discriminate authored 3.4e38 from the browser's used clamp: either interval stayed longer than the entire path, so both painted the same apparent solid or initial cap dot. The corrected matrix put the transition inside large user-space geometry and located the fixed ceiling directly: 33,000,000 differs, while 34,000,000, 40,000,000, and 3.4e38 converge. It separately measured percentages above that ceiling and the extreme percentage outcomes for dasharray and width.

Two byte-identical Chromium cells now cover the presentation-attribute and CSS dasharray spellings. Together they discriminate the fixed used clamp, odd-list repetition, restart at each subpath, and the cap-preserving solid result for an extreme percentage. A third cell repairs the fixed-length evidence of the previously closed width family by covering the clamp in both spellings. At this correction, the extreme percentage width class was measured, not celled, and its presentation-attribute, CSS, and percentage-only arithmetic ingresses were guarded by one named refusal. The corpus moves from 277 to 280 cells; 97 of its 98 svg-stroke-* cells are byte-exact, with only the previously declared closed-path tolerance. At this correction, the cycle-overflow refusal left as the percentage-overflow width refusal entered, so the register remained at 56 rows. Dashoffset, pathLength, unit-basis, variable-indirection, escape, and font-basis patrols were unchanged.

The earned precedents apply without revision: gridaco/nothing#77 supplies the standard-track grammar bar, gridaco/nothing#80 assigns independent unit and spelling gaps to their own guarded rows, and gridaco/nothing#81 remains the rule for a genuine measured split. Here the corrected evidence removes that split's premise for dasharray, so both stroke-dasharray checklist rows tick. It also corrects gridaco/nothing#80's width evidence: the valid extreme-percentage class has no independent row and Chromium paints some of it, so both stroke-width rows reopened under gridaco/nothing#81 pending that named capability. This is a capability verdict only; it produces no conformance score and takes no FLIP action.

Ratified amendment — finite wide stroke reach (2026-08-20)

Every accepted resolved stroke now has a finite, conservative, direction-free reach outside its geometry. This is the sixth contract amendment of the Web-first ladder. The carried width, cap, join, miter limit, paint, and optional dash cycle do not change. Only the arithmetic domain of the reach derived from those facts widens before any operation, so independently valid finite members cannot become an unrepresentable stroke merely because their aggregate bound exceeds the carried scalar range.

The bound still means exactly what it did. A round or bevel join and a butt or round cap reach one half-width. A miter is bounded by the larger of one and its miter limit, in half-widths. A square cap reaches a half-width along and across the endpoint, so its direction-free bound is the half-width times the square root of two; the represented irrational result rounds outward. The result is finite for every combination of finite carried members, including the largest width and miter limit the contract can state.

A consumer that needs a finite damage envelope projects this wide reach through the node transform before narrowing it. It intersects the projection with the finite frame clip and only then encodes a frame-bounded envelope; a fully clipped node has no pixel envelope but retains its material attribution. Scope unions obey the same frame bound. None of this changes the exact stroke sent to the painter, the geometry's own bounds, or a gradient's geometry reference box. It widens conservative bookkeeping, not paint semantics.

Stroke-width saturation rung verdict — cells landed, rows split

The Chromium 149 matrix fixed the percentage operation order and its renderer edges before the amendment. For a pure percentage, the authored percentage is multiplied by the normalized-diagonal basis before division by 100. Positive overflow in that intermediate resolves to the maximum finite width. It does not mean one universal no-stroke result: under discriminating transforms, round and bevel joins paint where the default miter does not; a miter limit of exactly 2 paints while the next representable value does not; an ordinary dash cycle can survive; closed and cornered topologies can drop; and round and square caps that drop under normal-axis-only compression paint when both axes are compressed.

Four Chromium-baked cells carry that evidence in presentation-attribute and CSS twins. Two cover an unambiguous direct finite 5e36% control, direct positive saturation, miter/join, dash, and topology branches. Two cover the round- and square-cap split with independently discriminating alpha passes. All four are byte-exact. The primitive corpus moves from 280 to 284 cells; 101 of its 102 svg-stroke-* cells are byte-exact, with only the already-declared closed-path tolerance.

The same probe found a narrower remainder that is not saturation-specific. On a 64×32 user space, the valid adjacent values 100.00000762939453% and 100.00001525878906% collapse to the same percentage bucket in the pinned cascade (0x3f800001), while Chromium retains distinct used widths: their amplified rasters differ by 16 pixels. The last-finite and first-overflow pairs at 64- and 32-unit normalized-diagonal bases expose the same loss at the saturation boundary. A non-f32 decimal pair around 57384.265625% differs by 864 pixels despite reaching the same computed bucket, while non-identity percentage math reaches further rasters after the cascade has erased its operation history — including authored length terms that cancel to zero before the pure computed percentage arrives. These cases are measured, not celled. Once distinct source values with different oracle results have become one computed fact, a producer cannot select any result without being silently wrong for another. Direct ambiguous values and folded percentage math therefore refuse by name across the attribute, inline-style, stylesheet, and inherited ingresses.

The former broad percentage-overflow refusal graduates into the four cells and the percentage-precision-alias refusal enters in its place, so the refusal register remains at 56 rows. Unit-basis, mixed arithmetic, variable-indirection, escape, and font-basis patrols remain unchanged. The standard-track bar from gridaco/nothing#77 admits both aliased spellings; the own-row rule from gridaco/nothing#80 cannot move this gap elsewhere because it has no independent row. It is therefore the gridaco/nothing#81 split condition that gridaco/nothing#86 reopened the width family to resolve: cells land, but the CSS stroke-width row and its SVG presentation-attribute twin remain unchecked. This is a capability verdict only. It produces no conformance score and takes no FLIP action.

Ratified amendment — signed stroke dash phase (2026-08-21)

A resolved dash pattern now pairs its checked interval cycle with one finite phase in the same local path-distance space. The phase is not source syntax: units, percentages, cascade, authored signs, and odd-list repetition have already resolved before the boundary. Construction canonicalizes the signed value modulo the positive repeated cycle. Positive phase advances into that cycle, equivalent signed and multi-cycle values have one representation, and the same canonical phase restarts at the beginning of every contour.

Phase cannot exist without a positive cycle. An absent cycle, none, an all-zero cycle, or a cycle dropped by the browser's extreme-percentage rule remains the single solid-stroke state, and dash offset is inert there. Moving a live cycle changes along-path paint placement but cannot change the stroke's direction-free reach outside its geometry. The phase, intervals, width, and geometry remain local under the node transform. Path-length calibration was not part of this amendment when it landed; the following source path-distance amendment resolves it into these same interval and phase facts and graduates the separate refusal.

The fixed used-value range is signed and asymmetric before phase canonicalization. Blink's positive fixed ceiling is authored 33,554,429 and is carried as f32 33,554,428; its negative floor is -33,554,430 exactly. On an 8 4 cycle, extreme positive and negative fixed offsets therefore canonicalize to phases 4 and 6. Percentage offsets follow the percentage used-value route, including finite saturation at the extreme, rather than either fixed bound.

Dashoffset rung verdict — cells landed, rows split

Chromium 149 fixed the ordinary phase semantics in a 91-source matrix captured twice. Its 69 exact pair verdicts covered the attribute and CSS spellings; positive, negative, zero, unitless, px, exponent, and percentage values; normalized-diagonal basis in a non-square viewBox; signed and multi-cycle modulo after odd-list doubling; zero painted slots under all caps; open, closed, and multi-contour restart; every admitted geometry; uniform and non-uniform transforms; inheritance, use-site inheritance, and CSS-wide values; precedence and invalid-declaration fallback; solid-stroke inertness; and path-length calibration. A separate numeric matrix established both fixed bounds, direct percentage saturation, and the source-precision boundary. A focused residual matrix then established the guarded viewport-unit, variable, escape, and poisoned-font-basis ingresses with exact numeric controls.

Thirteen of those cases are committed Chromium cells; the complete cell ledger and its measured-not-celled mutation controls live in the Web-first evidence table. Two cells cover the base attribute/CSS grammar, two cover signed percentages and their viewBox basis, two cover odd-cycle modulo and zero-length cap slots, two cover contours and every geometry/transform route, three cover inheritance, <use>, CSS-wide values, precedence, and invalid fallback, and two cover the fixed and percentage used ranges. Every cell is byte-exact; the three attribute/CSS twin matrices and the two phase-four cascade outcomes are byte-identical. Scratch zero-phase mutation controls (measured, not celled) change every oracle by the exact counts recorded in the Web-first evidence table.

Measured facts deliberately not assigned their own cells include omitted, explicit zero, and negative-zero identity; further equivalent signed modulo pairs; duplicated transform/topology/cap cross-products; phase inertness when no live cycle exists; and path-length calibration, which was measured here but not celled by this rung. The following path-distance amendment gives it its own exact cells and removes that refusal. The residual unit, variable, escaped spelling, and poisoned-font-basis probes remain registered guards rather than capability cells. The unit and variable families retain their independent checklist rows under the gridaco/nothing#80 precedent; the Chromium-invalid comment-split property spelling may be conservatively over-refused under gridaco/nothing#77.

The source-precision blocker is decisive. The valid authored percentages 57384.265625% and 57384.267578125007% collapse into one pinned-cascade f32 bucket while Chromium retains distinct phases: their rasters differ by 120 pixels in both source spellings. Their negative mirrors differ by 142 pixels. The last-finite and first-overflow percentage sources likewise collapse while Chromium differs by 688 pixels. An adjacent pair around 100% was identical on the same sensitive geometry, so this is a narrower provenance gap rather than a rejection of percentage precision in general. Tested non-identity percentage math happened to agree with its direct high control on that matrix, but the cascade erases its operation history; one bank cannot establish every such expression, and re-evaluating CSS math after the cascade would be a second matcher.

The broad dashoffset refusal therefore graduates, while a stable percentage-precision-alias refusal and four source/basis guards replace it. The primitive corpus moves from 284 to 297 cells. Its stroke inventory moves from 102 to 115 cells, 114 byte-exact, with only the existing closed-path tolerance. The refusal register moves from 56 to 60 rows. The valid Chromium-honored precision class has no independent checklist row, so this is gridaco/nothing#81's SPLIT condition: the CSS stroke-dashoffset row and its SVG presentation-attribute twin both remain unchecked. This is a capability verdict only. It produces no conformance score and takes no FLIP action.

Ratified amendment — source path-distance calibration (2026-08-21)

An SVG geometry may now calibrate its dashed stroke from one authored pathLength number without adding source syntax to the resolved render contract. Let A be the browser-compatible total of the geometry's non-empty local contours and L a positive authored length. Every already-resolved dash member and the signed raw phase receive the common factor A / L. An odd list still repeats into one even cycle, and phase canonicalization still happens against the resulting positive cycle. The frame boundary remains unchanged: it carries only those final local-space intervals and the canonical phase, not the authored number or a second calibration fact.

Calibration is a geometry-local operation. It precedes a node transform and the root viewBox mapping, and every contour restarts with the same calibrated phase. The attribute is non-inherited and applies only to path, line, rect, circle, ellipse, polyline, and polygon. On an instance, the referenced geometry's value participates; a spelling on the use site or a container does not. With no live dash cycle the attribute is inert. Chromium's legacy value branches are preserved: absence and a negative number leave distances uncalibrated, while zero, negative zero, and a malformed present value select the authored-zero branch. On geometry with a non-zero local metric, the resulting scale saturates rather than becoming zero; a zero local metric instead yields a zero calibration factor. At the tested numeric edges this makes the resulting effect solid or densely cap-painted according to the ordinary dash and cap rules; it is not a new stroke state. A saturated scale can also leave a tiny interval cycle finite while overflowing a large finite authored phase. Chromium then rejects the dash effect and retains the solid stroke. Malformed-present values reach the same outcome through the authored-zero branch, and both admissions carry it without a refusal.

The metric and the eventual dash traversal must observe one consistent set of local coordinates. Chromium's native oval is four rational conics measured and traversed in f32, and that arithmetic is observably translation-sensitive. Moving an oval's box origin into a transform is equivalent over real numbers but can move antialiased dash endpoints. A calibrated dashed oval therefore retains its absolute local bounds through painting. This is a precision invariant, not a new geometry kind or a change to solid-oval semantics.

The current standard also defines the CSS property path-length: none | <length [0,∞]>, non-inherited on the same seven geometry elements, and maps the attribute as a pixel-length presentation hint. The pinned Chromium 149 build has that experimental property disabled. Valid inline and stylesheet declarations therefore drop wholesale and do not override the active legacy attribute. Matching that drop requires no parallel matcher and follows the gridaco/nothing#77 precedent: a valid listed member the browser itself drops does not keep a row open once the drop is celled.

Path-length rung verdict — nine cells landed, both rows tick

A twice-deterministic 96-pair attribute matrix established a broad source branch before admission: standard number spellings; negative, zero, malformed, underflow, overflow, and the probed large-finite behavior; positive and negative phase; modulo after odd-list repetition; zero-length painted slots under every cap; solid-cycle inertness; all seven geometry routes; straight, quadratic, cubic, conic, rounded, open, closed, mixed, and move-only contour metrics; transforms, viewBox, instances, group inapplicability, and XML case sensitivity. Chromium also scales fixed and percentage members alike after percentage resolution on the normalized diagonal, including a mixed list. That measured behavior disagrees with the current SVG2 statement that pathLength does not affect percentage distance-along-path calculations; the Chromium-gated cells record the disagreement rather than silently substituting the standard's operation order.

A supplemental numeric black-box bank then exercised 269 authored cases in five composites, each captured twice in Chromium for determinism and rendered once through the engine for comparison. After correcting a raster-layout confound, the invalid, rounding, range, and deterministic composites were exact, and 45 of the 48 grammar cases were exact. The three remaining spellings — 5., 5.e0, and 125.e-3 — are invalid-present in Chromium rather than valid trailing-dot numbers. Their interaction with a saturated scale, a tiny finite cycle, and a large phase exposed the solid-fallback edge now carried by the ninth cell and the corrected admission. The post-correction rerender makes all five composites exact, with zero differing pixels, zero maximum channel delta, and no degradation.

Three DOM metric pins close the native-oval precision question at the exact tested positions: a radius-12 circle centered at (40, 30) is 74.91236114501953 (0x4295d321); radii 16×10 centered at (80, 30) are 82.2452621459961 (0x42a47d93); and radii 24×12 centered at (140, 30) are 115.52963256835938 (0x42e70f2c). Adjacent raw dash values discriminate every pin. Source-number measurements separately pin the long-decimal results 123456789.123456789 → 0x4ceb79a2 and 1.654435761 → 0x3fd3c48e, the smallest-subnormal and underflow boundary, exact overflow rejection, and negative-zero bits.

Nine committed Chromium cells carry the admission. They separate attribute grammar; phase/list/cap algebra; fixed and percentage scaling; every geometry; curves and summed contours; transforms, viewBox, and instance ownership; numeric extremes; the finite-cycle/non-finite-phase fallback; and the disabled CSS property in inline and stylesheet spellings. All nine engine renders have zero differing pixels and zero maximum channel delta; representative strict renders are declaration-free and byte-identical to best effort. Their 50 scratch mutation controls comprise 44 pixel-discriminating differences and six exact equivalence controls. The per-cell mutation counts and the measured-not-celled remainder are enumerated in the Web-first evidence table.

Facts measured but deliberately not assigned further cells include the remaining equivalent number spellings and invalid-present forms, duplicated transform and topology cross-products, further modulo identities, zero-length geometry, inapplicable group and wrong-case spellings, and the exact numeric and oval metric pins above. The CSS cell carries representative none and non-negative pixel-length members through both source ingresses; the disabled property registration makes the remainder a full-grammar audit rather than a second collection of identical drop cells.

The primitive corpus moves from 297 to 306 cells. The path-length refusal graduates without replacement, so the named register moves from 60 to 59. The stroke-prefixed inventory remains 115 cells, 114 byte-exact, with only the existing closed-path tolerance. The previously missing CSS path-length row is added and ticked, and the SVG pathLength attribute row ticks. Path consumers not admitted here — including markers and text-on-path — retain their own checklist rows and named capability boundaries; they do not reopen this attribute's carried grammar. This is a capability verdict only. It produces no conformance score and takes no FLIP action.

Addendum — opacity closure (2026-08-21)

The two opacity remainders left by the group-scope rung are now resolved. A valid gradient paint can carry element opacity without flattening it into the gradient's intrinsic opacity, and opacity on the root <svg> can enclose the complete transparent SVG-local frame. The same outer-scope meaning extends to each non-identity HTML ancestor of the selected inline SVG. These are three source spellings of two source-neutral render facts: a factor on one paint pass, or an isolated scope around a composite.

rframe::PaintStack now carries one checked PaintAlphaFactor in the closed unit interval. It is neither intrinsic paint opacity nor group opacity. Each paint entry materializes its own alpha first; the factor then multiplies that float alpha before coverage and source-over compositing, without a second 8-bit quantization. On a multi-paint stack it applies independently to each entry and changes no paint order. Identity is the ordinary producer default, and zero normalizes the whole stack to no paint. The factor is part of the resolved stack's equality, so a factor-only change is also a raster-identity and damage change rather than an invisible producer annotation.

The n0 boundary copies that bit-exact value into its private drawlist as a post-paint opacity. Every fill and stroke route applies the same float fold after its existing solid/gradient alpha materialization. Native n0 producers state identity, so no n0-model public API grew. Preflight sees the same drawlist fact as rasterization, and the factor participates in item equality and damage. This is the measured one-draw fold of Chromium's alpha-layer effect; it is not a replacement for a group scope. Fill plus stroke, overlapping descendants, nested opacity, and the existing structural fold boundaries continue to use the isolated Scope fact and retain their per-layer quantization.

The producer keeps the alpha routes distinct:

  • A direct solid combines colour alpha, fill/stroke opacity, and the eligible element factor in its existing one-product fold.
  • A valid paint server keeps fill/stroke opacity intrinsic to its paint and attaches element opacity as PaintAlphaFactor. A one-stop server becomes a two-identical-stop constant gradient, retaining Chromium's gradient and dither route. An exact geometric degeneracy may resolve to a solid only when the staged alpha remains representable, and it still retains the later element factor.
  • An invalid URL's authored fallback is an ordinary direct solid. Its factors fold as solid alpha; it does not acquire paint-server staging merely because its source mentioned url().
  • Root opacity is an isolated scope around the complete item stream in both standalone and inline entries. Each HTML ancestor contributes its own outer scope in tree order. Opacity remains non-inherited by default; an explicit inherit on the SVG adds the root scope beneath the ancestor scopes. A zero root or host scope resolves to the transparent empty frame before an unsupported descendant can leak a false refusal. Every scope consumes the same checked nesting budget and retains ordinary scope identity, provenance, and damage semantics.

Evidence and the retained stop-precision boundary

Eight committed Chromium-149 cells carry the closure, all byte-exact: svg-element-opacity-gradient, svg-element-opacity-gradient-css, and svg-element-opacity-gradient-stroke cover gradient fill/stroke, both source spellings, intrinsic paint opacity, and a non-half element factor; svg-opacity-grammar-attr and svg-opacity-grammar-css cover the twin grammar and cascade surface; svg-root-opacity and svg-root-opacity-zero cover the transparent whole-frame scope and its zero; and html-inline-svg-ancestor-opacity covers three distinct non-half host/root layers, precedence, and explicit inheritance.

The scratch evidence remains explicitly outside the committed corpus. A twice-repeated 15-pair standalone bank produced six identities and nine differences; a twice-repeated eight-pair HTML bank produced two identities and six differences. The discriminating mutations include flattened gradient factors, all-identity grammar controls, per-child substitution for root/group scopes, visible substitution for zero, identity substitution for each host layer, and flattening two or three host scopes. Every source was captured twice per run and every output was byte-identical across runs. The exact pair names, pixel counts, and maximum channel deltas are enumerated in the Web-first evidence table, not promoted to additional cells.

A separate 32-case grammar composite is exact against Chromium. Focused transparent-root probes measured attribute/CSS, root/whole-content-group, zero/empty, above-one/ordinary, and direct/math identities. Twelve adjacent number, percentage, presentation-attribute, and calc() decimal pairs around the tested 127.5/255 and 64.5/255 alpha thresholds were also identical. Those precision aliases are harmless at the final element-opacity raster step, so opacity has no stroke-width-style authored-provenance blocker on the measured surface.

The same investigation found a different, own-row gap in gradient stops. Chromium resolves a stop colour's base alpha byte, multiplies stop-opacity in float, and carries the effective alpha into the shader. A non-byte product cannot cross the current RGBA8 gradient-stop contract honestly. A valid degenerate server also cannot collapse an exact-byte translucent stop with a non-endpoint fill/stroke opacity, or with a later non-identity post-paint factor, into one RGBA8 solid. The exact stop-77, paint-identity, element-.6 witness for the latter differs by 2,304 pixels at one code value. Finally, the integral average of a degenerate reflect/repeat ramp can synthesize either a nonintegral alpha or a nonintegral colour channel: opaque #000000#010000 produces the decisive later-opacity red-half witness, while exact-byte-alpha #00000080#01000080 exposes the same rounding at identity. Twice-captured zero- and negative-radius radial banks additionally fix the tile-specific degenerate result: pad selects the last stop, while repeat and reflect select the integral ramp average. This is therefore a component-precision boundary, not alpha provenance alone.

One stable svg-gradient-stop-precision refusal guards those five semantic loss classes. Live and constant exact-byte gradients and endpoint-safe pad degenerates remain admitted. The alpha-average guard conservatively over-refuses both the measured 77→128 ramp and an endpoint 0→1 ramp when the derived alpha is non-byte. A fractional RGB average remains admitted only when its own alpha is opaque and both later opacity stages are identity, where measured bytes agree; a non-endpoint average alpha or later paint/post-paint opacity exposes its rounding and refuses. That deliberate over-refusal belongs to the independent stop-color/stop-opacity capability family. Flattening the refused witnesses changes up to 2,304 pixels by one code value. Those two presentation-property rows and both presentation-attribute rows remain open; their author-CSS ingresses remain unimplemented. Under the gridaco/nothing#75/#80 own-row precedent, this discovered and quarantined gap does not hold the CSS opacity property or SVG presentation-attribute opacity row open.

The primitive corpus moves from 306 to 314 cells. The two broad opacity refusals graduate and the narrower stop-precision refusal enters, so the named register moves from 59 to 58. The CSS opacity property and its SVG presentation-attribute twin tick; stop-color and stop-opacity do not. This is a capability verdict only. It produces no conformance score and takes no FLIP action.

Rung: the SVG <stop> presentation attributes (2026-08-22)

stop-color and stop-opacity are read directly off the <stop> element, because the pinned Stylo build has no such longhand. That is what made the previous rung's account of them incomplete: nothing cascades over these two values, so nothing resolves them either, and four spellings that need a resolver were falling back to the initial instead of refusing. All four were measured painting a wrong pixel silently. stop-color="inherit" under an ancestor gradient carrying stop-color="red" paints red in Chromium and painted the initial black here — 4,096 pixels at Δ253; the stop-opacity twin is Δ190. var() is substituted inside a presentation attribute (Δ190). A CSS math function is valid in stop-opacity, whose SVG 2 value is the opacity property's own, and Chromium evaluates it: calc(1 / 3) is byte-identical to the literal third, against Δ169 here. A non-legacy sRGB stop colour — color(srgb …) — parses into the same colour space as hex and was admitted, but it changes how Chromium interpolates the whole ramp rather than only its endpoint: 4,080 pixels at Δ26, where the identical value as a solid is byte-identical to #010000. Each now refuses by name, and each names a construct carrying its own checklist row.

The previous rung's svg-gradient-stop-precision refusal split in two, and the split is the substance of this rung. Its live-ramp half was a contract limit, not a browser fact: Chromium hands the ramp the float product of a stop colour's alpha byte and stop-opacity, and stop-opacity="0.5" is distinguishable from both 127/255 (992 pixels) and 128/255 (2,424). The resolved leaf now carries that product unquantized — cg::GradientStop and the model tier's GradientStop hold checked unit-sRGB float components, which is the width format/grida.fbs already declared and the decoder was narrowing. Byte RGB is enough: every path that could have carried sub-byte RGB is either quantized by Chromium itself (measured — a solid, a degenerate substitution, rgb(), and a colour's own alpha all land on bytes) or is a colour function with its own row.

Its degenerate half survives, renamed to what it actually is. Chromium does not paint a degenerate paint server as a flat colour; it keeps a shader, and that shader dithers. Where every stage lands on a byte there is nothing to dither and the flat solid is byte-identical, and those cases are now celled: a repeat average of 77/255 and 129/255 landing exactly on 103/255, a reflect colour average of 0 and 3 rounding up to 2, pad with a byte-exact translucent last stop, and the zero-radius radial route. Where a stage does not, Chromium's output matched no flat solid and no constant ramp the probe could construct — a degenerate pad at stop alpha 0.5 under fill-opacity=".7" sat 2,560–4,096 pixels from every candidate at Δ1, the signature of a dither tied to the degenerate shader's own geometry. Guessing that rule would be a silent wrong pixel, so svg-gradient-degenerate-precision refuses it. The refusal fires on the collapsed value, not on how it arose: two byte-exact stop-colors whose ramp average lands between codes trip it with no stop-opacity present at all. It is therefore a gradient-geometry gap belonging to the already-ticked <linearGradient>/<radialGradient> rows, not a stop-grammar gap, and it does not hold the two attribute rows open. It is filed as gridaco/nothing#93.

The primitive corpus moves from 314 to 319 cells; the named register moves from 58 to 62. The SVG presentation-attribute rows for stop-color and stop-opacity tick. Their CSS presentation-property twins stay open and unchanged: the pinned cascade has no longhand for either, so a sheet or a stop's style attribute remains a document-level refusal, and closing those rows needs the longhands, never a second matcher around Stylo. This is a capability verdict only. It produces no conformance score and takes no FLIP action.

Rung: SVG geometry presentation attributes cx/cy/r (2026-08-22)

The rung began at the raw-number boundary, before any admission work. These three attributes never meet the cascade: the pinned Stylo build has no cx/cy/r longhands, so the compiler reads the presentation-attribute text directly. That ownership remains correct. Routing the values through a new matcher around Stylo would create a second cascade. The unresolved question was narrower and numeric: whether Rust's direct f32 parse and arithmetic retain the same used value as Blink's CSS-number path.

They do not. Four amplified, twice-deterministic Chromium-149 probe families establish the boundary:

  • For the stroke-rung source alias 57384.267578125007%, Chromium selects the lower normalized control for each of cx, cy, and r. The higher control differs by 88 pixels at maximum channel delta 240, 89 pixels at delta 236, and 80 pixels at delta 255 respectively. The previous geometry route selected that higher neighbour; direct Chromium-versus-engine source renders differed by 88, 89, and 144 pixels.
  • A decimal just above the exact midpoint between f32 1 and its successor is a second divergence class. Chromium's decimal-to-f64-to-f32 path selects the lower neighbour, while the direct decimal-to-f32 route selected the higher. The three higher controls differ from Chromium by 88 pixels at delta 242, 88 pixels at delta 236, and 80 pixels at delta 255.
  • Ordinary percentage arithmetic has an independently observable order. On a ten-unit basis, r=".5%" is byte-identical to 0.05000000074505806 in Chromium and differs from the division-before-multiplication neighbour 0.04999999701976776 by 2,176 pixels at delta 255. The producer now multiplies by the basis before dividing by 100. The extreme oval amplification needed to reveal one ULP also exposes a separate 144-pixel Blink/Skia raster difference even when both receive the same numeric control; that arithmetic fact is therefore measured and bit-lawed, not promoted into an inexact corpus cell.
  • A clamp-sensitive range matrix exposed a separate silent boundary. On a 64-unit basis Chromium drops 3.4e38% for each attribute exactly to empty, while finite direct 2.176e38 controls survive its used-value clamp and, under scale(.000001), paint 230 pixels for cx, 227 for cy, and 3,520 for r, all at delta 255. The old percentage route leaked infinity and made n0 fail later with generic invalid frame bounds. The direct-center controls returned success with no declaration but painted empty, by those same exact differences; direct r also produced invalid bounds.

The first two valid classes now trip one stable unsupported SVG geometry: <attribute> numeric precision alias loses Chromium used-value provenance guard for cx, cy, and r. Its f64 route is a one-way classifier only: agreement leaves the raw parser in charge; disagreement refuses, and the shadow value is never substituted as a second parser. A nine-row refusal addition records this precision class, the percentage-overflow and fixed-used-range classes, the absent CSS longhands, unit-bearing values, CSS math, var(), CSS-wide keywords, and CSS comments. The first four value families each retain their own unchecked checklist row. Comments and the range classes are no-own-row valid gaps: Chromium strips the comments and renders the ordinary number exactly, drops the overflowing percentage, and clamps the direct extreme; this producer refuses all three boundaries rather than guessing or leaking a backend-only drop.

The semantic repair is otherwise local. Negative cx and cy remain valid coordinates. Missing and explicit-zero centers coincide at zero. A missing, zero, or negative circle radius now produces no frame node: negative r is an invalid element geometry, not a magnitude to clamp, and all three cases render the same honest nothing. Percentages keep the existing PercentBases contract: the x and y axes for cx/cy, and sqrt(width² + height²) / sqrt(2) for r, using the mapped viewBox extent when present and initial-viewport user units otherwise. <use>, transforms, and strokes consume those resolved facts without changing their meaning. A resolved non-finite value now refuses at its attribute, and cx/cy plus positive r values outside the established Web fixed-length range refuse until the geometry used-value clamp is represented; negative r keeps its invalid no-node meaning. Circle box construction checks the radius, origin, diameter, and both expanded corners for finiteness before any rectangle fact crosses the resolved-frame seam; the shadow checks never supply a replacement geometry.

No cross-crate seam changed. rframe already states circles and ellipses as an ellipse over a local rectangle; n0 already lowers that fact to its native oval fill/stroke routes. The compiler's correction stops before that boundary. CSS declarations are quarantined at their authored ingresses: a stylesheet is named at the sheet and a style declaration at its element. The CSS presentation-property twins remain open at the pinned-cascade cap, with no matcher layered around Stylo.

Five committed Chromium cells carry the admitted subset. The grammar cell pins absent/explicit-zero centers, negative centers, number spellings, and the three non-rendering radius cases. A 64×32 unmapped initial viewport and a 70×10 viewBox separately pin axis and normalized-diagonal bases. The final two cells carry <use> instances and transform-plus-stroke composition. Their exact controls reproduce each oracle. The wrong-center and positive-radius mutations differ by 132 and 672 pixels; the unmapped and viewBox wrong-basis mutations differ by 778 and 1,492; deleting the instances differs by 97; and the composed wrong-basis mutation differs by 480. Every maximum delta is 255.

The gate's sensitivity was tested, not assumed. Temporarily routing cx percentages to the y axis made just gate fail on four cells, including the new unmapped and viewBox witnesses at 796 and 2,378 differing pixels. Restoring the axis table returned the complete gate to green. All candidate sources were also rendered through the actual n0 CLI path; the five admitted cells were declaration-free and decoded-pixel exact to their Chromium probes.

The review-time range probes were also run through the actual CLI before and after the patrol. Before it, the overflowing-percentage composite failed only downstream as an invalid frame and the finite direct-center controls exited cleanly with wrong empty pixels. Afterwards best-effort succeeds while naming all three skipped attributes, and strict refuses on the first stable unsupported SVG geometry reason; no non-finite frame reaches rframe or n0. Temporarily lowering the new used-range ceiling to one made just gate fail immediately on svg-circle-defaults-clip; restoring the measured ceiling returned the full gate to green.

The primitive corpus moves from 319 to 324 cells; the ten exact-time sampled frames are unchanged. The named refusal register moves from 62 to 71 rows. Neither the SVG presentation-attribute nor CSS presentation-property rows for cx, cy, or r tick: the valid numeric-precision, range, and comment classes have no independent row, while the CSS twins remain structurally unavailable at this Stylo pin. This is a measured SPLIT verdict under the gridaco/nothing#81/#89/#90 precedent, not a capability closure. It produces no conformance score and takes no FLIP action.

Rung: SVG geometry presentation attributes x/y/width/height (2026-08-22)

This follow-on began with the same source-number question as the preceding circle rung. Rect coordinates and extents take the same raw finite number/percentage route, while Chromium reaches their presentation values through its CSS-number path. Root sizing, CSS property spellings, and the attributes' applications to resource-bearing elements remain separate contracts; the probe first asked only whether the shared raw route preserved Chromium's used geometry.

It did not. Twice-deterministic Chromium-149 probes reproduce both known alias classes on every attribute (measured, not celled):

  • 57384.267578125007% selects the lower normalized control in Chromium and the higher neighbour on the former producer route. The higher controls differ by 32 pixels for x and y, and 16 for width and height, all at maximum channel delta 238.
  • A decimal just above the exact midpoint between f32 1 and its successor selects the lower neighbour in Chromium and the higher neighbour on the former producer route. The four differences have the same 32/32/16/16 pixel shape at maximum delta 218. For both classes, the producer source was pixel-identical to Chromium's higher control while both explicit controls were cross-engine exact.

A second range matrix exposed a distinct silent boundary (measured, not celled). Chromium carries finite direct 2.176e38 values through its fixed Web used-length clamp: positive x/y equal the upper-bound control, negative x/y equal the lower-bound control, and positive width/height equal the upper-bound control. Each clamped position paints 144 pixels beyond empty (delta 218 for positive and 233 for negative); each clamped extent paints 544 pixels beyond empty at delta 217. The former producer positions exited cleanly and painted empty. Its huge extents painted 896 pixels from the origin instead of the clamped 544, leaving 368 wrong pixels. Negative extents are different: every magnitude is invalid element geometry, and both engines paint the same honest nothing. A finite 3.4e38% source overflows only when the viewport basis is applied; Chromium leaves no visible rect for all four attributes — off-canvas coordinates and disabled extents — and the attributable overflow refusal is pixel-equivalent in best-effort.

The cascade boundary had one further leak (measured, not celled). Chromium honors inline and stylesheet x/y declarations over the presentation attributes. The pinned Stylo build has neither longhand, so both declarations formerly painted the attribute position with no departure; each measured 512 pixels from Chromium. They now refuse at their authored ingress. CSS width/height are represented at the pin but remain unconsumed for SVG geometry, so their existing computed-style refusal continues to guard both ingresses. No matcher was added around Stylo.

The admitted rect semantics are otherwise unchanged and now explicit. Coordinates default to zero and accept signs, leading-dot and exponent number forms; negative x/y remain valid. Missing, zero, or negative extents disable the whole element, including its stroke. Percentages use the independent viewport axes in unmapped root units and in mapped viewBox units, and retain those values through same-document <use>, transforms, and centered stroke. The source-provenance classifier is one-way: a disagreement refuses by attribute and never substitutes its shadow value. Coordinates refuse outside both measured Web boundaries; positive extents refuse above the upper boundary; negative extents keep their invalid no-paint meaning.

The broader presentation-value grammar remains quarantined, not silently defaulted (measured, not celled). Chromium makes px, calc(16px + 16px), and var(--v) exact to a literal 32 for all four attributes; strips comments around ordinary values; resolves initial, unset, revert, revert-layer, and unoverridden inherit to coordinate zero or auto; and makes rect width="auto" and height="auto" exact no-paint geometry. This producer refuses every one by its exact attribute. Units, CSS math, custom properties, and CSS-wide values retain their own unchecked rows. Numeric provenance, the unimplemented used clamp, comments, and rect auto are valid no-own-row gaps and therefore hold the four attribute rows open.

Three new Chromium cells carry only new evidence. The grammar cell pins defaults, number forms, negative coordinates, and the six disabled extent branches; its canonical control is exact and the wrong mutation changes 792 pixels at delta 255. The percentages rung's existing svg-percent-rect-root-units and svg-percent-rect-in-viewbox cells already pin the two basis contexts and were reused rather than duplicated. A new use cell is exact to its expanded numeric light tree and differs from no instances by 512 pixels at delta 238. A new 64×32-user-space transform-and-stroke cell is exact to its numeric control and differs from the swapped-axis mutation by 1,121 pixels at delta 238. All candidate sources and controls were also rendered through the actual CLI path; each admitted source was declaration-free and decoded-pixel exact to Chromium.

The gate's sensitivity was proved by temporarily routing y-axis percentages through the width basis. just gate failed loudly, including the retained new transform-and-stroke cell at 1,094 differing pixels and maximum delta 238. Restoring the axis mapping returned the complete gate to green. Ten registered refusal rows cover numeric provenance, percentage overflow, fixed used range, CSS properties, units, CSS math, custom properties, CSS-wide values, comments, and rect auto; strict and best-effort name the same attributable reason for every skipped element.

No cross-crate seam changed. Resolved rectangles, instance transforms, and stroke facts already cross the frame contract and lower through the one n0 kernel; the correction stops at source admission. Root auto remains the admitted absent-dimension value, while root percentage sizing and cascaded CSS sizing remain document-level refusals. <image>, <pattern>, and <mask> keep their own element/resource refusals, so this rect evidence does not claim their geometry.

The primitive corpus moves from 324 to 327 cells; the ten exact-time sampled frames are unchanged. The named refusal register moves from 71 to 81 rows. Neither x/y CSS presentation-property row nor any of the four SVG presentation-attribute rows ticks. This is a measured SPLIT verdict under the gridaco/nothing#81/#89/#90 precedent, not capability closure. It produces no conformance score and takes no FLIP action.

On this page

Finding: the SVG engine of recordCurrent stateThe cruxEvidenceThe optionsDecision and scopeSubsequent status (2026-07-24): best-effort becomes the host defaultSubsequent status (2026-07-25): the viewport rung landsSubsequent status (2026-07-26): the basic-shapes rung landsSubsequent status (2026-07-26): the container rung landsAddendum — the paths rung (2026-07-26)Addendum — the strokes rung, and the tiger (2026-07-26)Addendum — the cub: a committed scene, static and sampled (2026-07-26)Addendum — the cap defect, closed (2026-07-27)Addendum — the load-active SMIL hole, closed (2026-07-30)Addendum — the points rung (2026-07-30)Addendum — the visibility rung (2026-07-31)Addendum — the translucency rung (2026-07-31)Addendum — the percentages rung (2026-07-31)Addendum — the anchor rung (2026-07-31)Addendum — the transform rung (2026-07-31)Addendum — the use/defs rung (2026-07-31)Addendum — the gradient rung (2026-08-01)Addendum — the group-scope rung (2026-08-04)Addendum — the conic rung (2026-08-06)Ratified amendment — resolved stroke dash intervals (2026-08-13)Dasharray rung verdict — cells landed, rows splitRatified amendment — context paint resolves before the frame (2026-08-13)Context-paint rung verdict — four rows closeRatified correction — stroke used-value range (2026-08-14)Ratified amendment — finite wide stroke reach (2026-08-20)Stroke-width saturation rung verdict — cells landed, rows splitRatified amendment — signed stroke dash phase (2026-08-21)Dashoffset rung verdict — cells landed, rows splitRatified amendment — source path-distance calibration (2026-08-21)Path-length rung verdict — nine cells landed, both rows tickAddendum — opacity closure (2026-08-21)Evidence and the retained stop-precision boundaryRung: the SVG <stop> presentation attributes (2026-08-22)Rung: SVG geometry presentation attributes cx/cy/r (2026-08-22)Rung: SVG geometry presentation attributes x/y/width/height (2026-08-22)