Skip to content

Commit 63f9814

Browse files
docs(consolidation): record the cub, and a thin-stroke cap defect it found
The addendum records what the scene fixture is for, what its two absences say about the admitted slice (the animated element must be a top-level rect; a sampled document can reach currentColor by neither door, since <style> and style= are dynamic-inventory blockers and color= is a declared boundary), and why it carries no <circle> or <ellipse>. It also records a defect found while verifying the cub at a second size. The scene gates byte-exact at 96x96; at 48x48 — a 1x viewport mapping — 242 of 2304 pixels differ from Chromium. Bisected: a closed contour with a non-butt cap at a ~1-device-pixel stroke width. A cap cannot exist on a closed contour, and Chromium's raster is cap-invariant there (measured: 0 differing pixels between its own butt and round captures); this engine's is not, because it paints a stroke by filling the outline StrokeRec::apply_to_path returns and that outline is not cap-invariant at that width. Butt is byte-exact, so the other two caps are silently wrong pixels. No committed cell combines all three triggers and the tiger never declares a linecap, which is why five rungs of byte-exact bakes never showed it. The fix is its own rung — caps are inert per *contour*, so a path mixing open and closed contours cannot be normalized wholesale — so it is recorded with its reproduction rather than patched here.
1 parent fc09528 commit 63f9814

3 files changed

Lines changed: 99 additions & 0 deletions

File tree

crates/n0_cli/README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,14 @@ cargo run -p n0_cli --bin n0 -- \
2020
fixtures/web-first/animation/svg-rect-x-animation.svg /tmp/t1s.png 64x32 \
2121
--time-ns 1000000000
2222

23+
# the cub: a whole composition (viewBox-only, containers, curves, strokes,
24+
# a <line>) with one animated rect — the same file, static and at 1s
25+
cargo run -p n0_cli --bin n0 -- \
26+
fixtures/web-first/animation/svg-scene-cub-animation.svg /tmp/cub.png 96x96
27+
cargo run -p n0_cli --bin n0 -- \
28+
fixtures/web-first/animation/svg-scene-cub-animation.svg /tmp/cub-1s.png 96x96 \
29+
--time-ns 1000000000
30+
2331
# dev harness: refuse on the first beyond-slice construct instead of
2432
# rendering best-effort with declared degradations (the default)
2533
cargo run -p n0_cli --bin n0 -- \

docs/wg/consolidation/svg-engine-of-record.md

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -614,3 +614,93 @@ Text is the next rung by a wide margin. Also queued, each with its brief already
614614
written by measurement: the arc (emit conics, inherit the oval tolerance), the
615615
`points` shapes, dashing, opacity and translucency, paint servers, and the
616616
declaration of a SMIL animation that targets a consumed attribute.
617+
618+
## Addendum — the cub: a committed scene, static and sampled (2026-07-26)
619+
620+
The corpus gained `svg-scene-cub` — an **original** composition that covers the
621+
tiger's feature set in 17 materialized nodes instead of 240, committed as a
622+
fixture pair so the whole rung ladder is gated both statically and under
623+
exact-time sampling. Five frames per pair (Base plus four samples), ten across
624+
the two sampling fixtures, every one byte-identical to Chromium.
625+
626+
- **It exists because the tiger cannot be committed.** The tiger proved the
627+
ladder but stays untracked under `fixtures/local/` for its AGPL provenance, so
628+
the capability it demonstrated had no committed cell. The cub is drawn to the
629+
same *feature* list — viewBox-only root sizing, a container with a transform
630+
and a nested `<g>`, `fill`/`stroke`/`stroke-width`/`stroke-linejoin`/
631+
`stroke-linecap` inherited through both, cubics and quadratics, relative
632+
command runs, multi-subpath paths, round caps and joins, `fill="none"` and
633+
`stroke="none"`, `<rect>`s in a group, a `<line>` — and renders `--strict`
634+
with zero declared degradations at zero differing pixels.
635+
- **A composition is a different gate from 70 single-feature cells.** Each
636+
primitive cell isolates one construct; the cub is the only cell where
637+
inheritance, container nesting, paint order, curve rasterization and stroking
638+
must all be right *simultaneously* for the pixels to land. At its gate size it
639+
passed on the first probe, before any harness code existed. At a *second* size
640+
it found a defect five rungs of single-feature cells had missed — recorded
641+
below.
642+
- **The sampling laws now hold over a scene, not just a shape.** The host law
643+
states the property a composition makes checkable: every pixel that differs
644+
between the Base frame and a sample lies in the animated rect's own rows.
645+
Sixteen nodes of curves and strokes render identically at every time, and the
646+
frozen pair is the same frame. The frame-side laws read the animated node by a
647+
*declared* index and node count (`cases.json`'s `frame` block), so a fixture
648+
that silently stops materializing an element fails there instead of quietly
649+
weakening every law below it.
650+
- **Two absences in the drawing are the slice speaking.** The animated element
651+
must be a top-level `<rect>` (the inventory admits an `<animate>` only on a
652+
materialized direct child of the root), so the sliding block sits beside the
653+
figure rather than inside its group. And the scene carries no `<style>`, no
654+
`style=` and no `color=`: the first two are dynamic-inventory blockers, and
655+
`color` is a presentation attribute Chromium honors that this engine declares
656+
rather than paints — so `currentColor`, which the corpus reaches today only
657+
through a `style` attribute, is unreachable in a *sampled* document by either
658+
door until its own rung lands. Writing the fixture is what made that
659+
intersection visible.
660+
- **No `<circle>` or `<ellipse>`, on purpose.** The muzzle, eyes and ear tips are
661+
cubic approximations of ellipses. Filled *and stroked* cubics bake byte-exact
662+
while a true rational conic does not, so the scene gates at zero difference
663+
with no tolerance — the declared AA departure stays exactly where the strokes
664+
rung left it, on the weighted conic alone.
665+
- **The sampling corpus is now plural.** `cases.json` carries a `fixtures` array
666+
(schema 1), the baker loops it with a per-fixture initial viewport and
667+
validates each declaration structurally — including that a fixture's authored
668+
value differs from its first sample, so Base and `Sample(0ns)` can never
669+
silently coincide — and each fixture's oracles live under
670+
`chromium/<id>/`. The rect-x pixels moved directories and were re-verified
671+
byte-identical by the bake, which is the proof the rename lost nothing.
672+
673+
**Verifying the cub at a second size found a defect the five rungs never
674+
showed.** The scene gates byte-exact at 96x96. Rendered at 48x48 — the same
675+
document, a 1x viewport mapping — 242 of 2304 pixels differ from Chromium. That
676+
is not the declared conic departure. Bisecting it by measurement:
677+
678+
| case (closed cubic contour, `fill` + `stroke`) | stroke width, at 1x | differing |
679+
| --- | --- | --- |
680+
| `stroke-linecap="butt"` | 1 device px | **0** |
681+
| `stroke-linecap="round"` | 1 device px | 185 of 2304, worst channel delta 100 |
682+
| `stroke-linecap="square"` | 1 device px | 185, worst delta 128 |
683+
| `stroke-linecap="round"` | 2 or 3 device px | **0** |
684+
685+
**A cap cannot exist on a closed contour** — and Chromium agrees: its butt and
686+
round captures of that document are byte-identical to each other (0 differing
687+
pixels), so its raster is cap-invariant there. This engine's is not. The cap is
688+
carried correctly all the way down; the divergence is in the *consumer*, which
689+
paints a stroke by filling the outline `StrokeRec::apply_to_path` returns, and
690+
that outline is not cap-invariant for a closed contour at a stroke width near
691+
one device pixel. Butt is byte-exact, so butt is the right answer and the other
692+
two caps are silently wrong pixels — the invariant this program does not allow.
693+
694+
Two things kept it hidden. No committed cell combines *all three* triggers (a
695+
closed contour, a non-butt cap, and a ~1-device-pixel stroke width): the cap
696+
cells stroke open contours, and the closed-contour cell uses the default butt.
697+
And the tiger never declares a `stroke-linecap` at all, so its 96.27% agreement
698+
was never going to show this. The cub is the first fixture that inherits a round
699+
cap onto closed contours — it just does so at a size where the artifact
700+
vanishes.
701+
702+
The fix is its own rung, not a patch here: caps are inert per *contour*, so a
703+
path mixing open and closed contours (the cub has both) cannot be normalized
704+
wholesale — it needs per-contour cap handling in the stroke lowering, plus
705+
corpus cells at a one-device-pixel stroke width, which the corpus lacks
706+
entirely. Recorded rather than fixed, with the reproduction above.

fixtures/web-first/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ the HTML→SVG boundary through one browser-grade cascade.**
4444
| `bake_chromium.ts` | Verifies existing oracle pixels and creates missing oracles; it never overwrites a differing baseline. Run: `pnpm -C packages/grida-reftest exec tsx "$(pwd)/fixtures/web-first/bake_chromium.ts"`. |
4545
| `pages/` | The target-only real-world page corpus. It is not a runnable reftest gate yet; see [`pages/README.md`](./pages/README.md). |
4646
| `unsupported/` | Inputs that deliberately have no pixels yet and must fail explicitly instead of being approximated; see [`unsupported/README.md`](./unsupported/README.md). |
47+
| `animation/` | The sampling corpus: animated documents with their static Base projections, baked at a paused Chromium timeline. It carries `svg-scene-cub` — a whole composition that exercises this rung ladder end to end, statically and at exact times; see [`animation/README.md`](./animation/README.md). |
4748

4849
Exact expectation: every primitive's full RGBA raster matches its Chromium
4950
oracle with zero differing pixels. The gate also validates enumeration and

0 commit comments

Comments
 (0)