Skip to content

Proposal: assertion-driven SVG test harness using the resvg suite (no similarity scores) #140

Open

Description

@softmarshmallow

Proposal in one minute

Status: approved by the owner on 2026-09-09; the bounded pilot is merged in gridaco/nothing#141.

The first implementation is available in the SVG assertion tool's contract and setup. It carries two exact rendering assertions and one separately labelled refusal assertion. This issue stays open for separately reviewed expansion and legacy succession; it does not authorize tool retirement.

Audience: the maintainer reviewing the setup and mental model, and contributors who will later implement or use it.

Build an assertion-driven SVG test harness, initially using inputs from the resvg test suite. An assertion is a precise statement we can check: for example, “this rectangle renders exactly like the declared Chromium reference under these settings.”

The result is PASS or FAIL for that statement, not a percentage of visual similarity. If the setup or reference is not trustworthy enough to judge, the result is UNRESOLVED, with a reason. Unreviewed inputs remain observations, not invented tests.

The resvg suite supplies questions. Our harness executes tests. Chromium is the existing browser-compatibility reference; fresh resvg renders and stored suite PNGs are separately identified second opinions. None is automatically correct for every case.

This proposal is for automated comparisons and useful regression gates, not just a gallery. It does not propose an SVG completeness score.

1. Why the unit is a claim, not an image percentage

A similarity percentage depends on how much of the picture the interesting feature occupies. A large correct background can hide a completely missing small feature. Adding empty space can improve that percentage without fixing anything.

For a declared exact-image test, one differing channel in one pixel fails the assertion. That means “this exact assertion was violated,” not “the renderer is entirely wrong.” Pixel locations, differing-pixel counts, and channel deltas explain a failure; they are not grades.

An atlas may exercise several independent behaviors. Preserve the original as an observation, then create focused, separately described cases for its claims where needed. A whole-atlas comparison may still be a valid integration assertion, but it must not masquerade as proof of every feature pictured. Do not crop or hide failing regions after seeing a result to manufacture a pass.

Even binary test results must not be averaged into “SVG support”: duplicate easy cases and missing hard cases would distort that number. No similarity scores, pass-rate percentages, rankings, or conformance aggregates are in this proposal. The independent FLIP decision remains with gridaco/nothing#49, unchanged.

2. The mental model

Pinned upstream SVG + resources
             |
             v
Describe the claim and its environment
             |
             v
Render with n0 strict / best effort, Chromium, and resvg
             |
             v
Preserve images, diagnostics, versions, and repeat checks
             |
             v
Compare named pairs -- without choosing a winner
             |
      +------+------------------+
      |                         |
Not ready to judge       Reviewed assertion + valid setup
      |                         |
Observation / unresolved       PASS or FAIL
      |                         |
Investigate or fix setup       Enforce in regression gate

These are separate concepts:

  • Input: the SVG and every dependency needed to interpret it.
  • Claim: the specific behavior being tested, including what it does not establish.
  • Observation: what one identified renderer actually produced, including errors and omissions.
  • Reference: the independently justified expected result for a particular claim.
  • Assertion: the executable check against that expectation.
  • Verdict: whether that assertion holds, or why it cannot yet be judged.

A description and feature tags live in case metadata, not painted labels that could affect the test. A filename or the presence of an element is only a discovery hint; neither proves the behavior is active.

3. What PASS, FAIL, and UNRESOLVED mean

Keep the kind of assertion separate from its verdict.

Kind PASS FAIL
Rendering assertion Required dimensions and decoded RGBA match the declared reference; n0 admits the case without degradation; both admissions and required repeats agree. Pixels or dimensions differ, an unexpected refusal/omission occurs, or repeated output is unstable.
Refusal assertion Strict mode rejects with the expected stable name/node path, and best effort reports the corresponding declared degradation. The refusal disappears, changes unexpectedly, or best effort silently drops the construct.
Harness integrity assertion Inputs/references are unchanged, required outputs exist, and recorded tools/settings match the declared setup. Inputs were overwritten, output is missing, the environment drifts, or the runner crashes/times out.

A passing refusal assertion is not a rendering-support pass. Its visible label should be “expected refusal verified.” Any fallback pixels it also checks need their own explicit expectation.

UNRESOLVED means a correctness judgment is not available: for example, a missing resource, unmatched font environment, unreviewed reference disagreement, or a case that does not exercise its claimed branch. An input with no reviewed claim is simply an observation. Neither receives a hidden zero or a green badge.

If a required CI case becomes unresolved or cannot run, the job fails closed as an infrastructure/review problem. It must not silently skip that case and succeed. Conversely, exploratory comparisons do not make every existing unsupported feature a new required CI failure.

4. Which picture is the reference?

Keep these observations distinct:

Source Role
Pinned Chromium capture Existing Web compatibility target, captured through the shared instrument. A match establishes the declared compatibility assertion, not universal standards correctness.
Stored upstream PNG Historical external evidence; preserve its bytes and record what is known or unknown about its production.
Fresh pinned resvg render A current second implementation's observation, with its own settings and warnings. Not interchangeable with the stored PNG.
n0 strict / best effort The system under test. Neither selects the reference nor creates its own correctness oracle.

The comparison report shows separate named pairs, such as n0–Chromium and n0–resvg. A pass against either one does not automatically satisfy the other. There is no “take whichever matches n0 best” rule, and the stored PNG plus resvg are not automatically two independent votes.

For a standards disagreement, inspect the applicable specification and a focused control that distinguishes the interpretations. Another browser may provide evidence later. Record the reasoning and obtain the required review before selecting a new reference. This issue does not ratify a general consensus policy or a resvg-derived gate.

For exact comparisons, compare dimensions and decoded RGBA, not compressed PNG-file bytes. Retain file hashes for provenance. Invisible RGB under zero alpha may be reported separately, but it is not silently normalized away. No blanket antialiasing exclusion, resizing, background flattening, or new tolerance is introduced. Existing narrowly justified comparison rules remain confined to their current cases.

5. Worked examples from the scratch spike

These are measured, not celled observations from 2026-09-09, not newly admitted tests. The spike examined 32 selected upstream inputs and 16 follow-up source forms, not a representative conformance denominator. Source revision: d8e064337faf01bc5a9579187a56dbdbe3eacc72; resvg 0.47.0; Chromium 149.0.7827.55; n0 at ef2e125168a610ac0a64456a12c395963a41911e. Full artifacts remain local-only, not published evidence or dependencies of this issue. Reproduction and durable controls are required before promotion.

  • Viewport units: shapes/rect/vw-and-vh-values.svg renders the rectangle in Chromium. Current resvg warns and skips it; n0 refuses it by name. The same viewport-unit and explicit-number sources match in Chromium; removing the subject changes the picture. Interpretation: a real exercised branch, not a global resvg-vs-Chromium winner. n0 has no rendering pass here; its refusal can be tested separately.
  • Missing external image: filters/feImage/simple-case.svg references a relative PNG. The sealed Chromium environment cannot load it, while resvg can. n0 best effort and Chromium match because both omit it; n0 declares the omission. Interpretation: environment-unaligned, not image support.
  • Placeholder: filters/feDisplacementMap/simple-case.svg contains no displacement primitive, filter, or filtered target. Matching its decorative frame proves no displacement behavior. Interpretation: do not promote it as that feature's test.
  • Actual n0 mismatch: the star-shaped clip-path case renders without a refusal but differs from Chromium at four pixels, with maximum channel delta 25. Interpretation: preserve and investigate the difference; do not label it harmless or relax a comparison rule to admit it. The renderer fix is separate work.

6. Setup and daily use

Two lanes share the same capture and comparison machinery:

  1. Explore: explicitly provision a pinned upstream suite into ignored local storage; select cases by meaningful applicability; run comparisons and inspect images/diagnostics. Broad inputs may remain undescribed observations. This lane finds work; it does not grant support.
  2. Gate: review a small set of described assertions, prove their sensitivity, and register reproducible fixtures through the existing admission workflow. CI runs these assertions without requiring someone to inspect every unchanged picture.

A normal feature session becomes: select a claim → run its cases → understand differences → fix or retain a named refusal → add a reviewed regression assertion → let CI protect it. Known mismatches remain visible work items; selection into the required set is explicit and reviewed, not automatically “only whichever cases already match.”

Each run records source/resource/font identities, renderer and capture versions, initial viewport, authored root size, export mapping, device scale, background/color handling, time, commands, diagnostics, and repeated outputs. Matching output dimensions alone does not establish matching environments.

The first implementation slice should be static, self-contained SVGs that fit the existing capture contract. External resource I/O, general font resolution, arbitrary export sizing, animation expansion, and WPT adapters are follow-ups, not hidden prerequisites or permission to add another browser capture route.

The downloaded corpus remains local-only and explicitly provisioned. Required CI fixtures must be available from a clean checkout with their licenses/provenance; they must not depend on a developer's ignored download. Original upstream inputs and references remain immutable. Diagnostic reductions receive separate identities and are rendered independently.

7. Implementation tracking

  • Review this model. Settle assertion granularity, result meanings, reference policy, and initial scope here before code or tool retirement.
  • Define the case/result contract. Document identity, claim, environment, reference decision, observation, assertion kind, verdict/reason, and promotion history. No similarity field or score aggregation. Keep runner errors separate from semantic refusals.
  • Build the smallest runner and viewer. Invoke the actual n0 CLI in both admissions; reuse the sole pinned Chromium capture; record fresh resvg and stored PNGs separately. Preserve repeatable artifacts and display the claim, chosen expectation, diagnostics, original images, and exact differences together. Use bounded execution/concurrency and explicit timeouts. Do not create a new renderer or semantic parser in the harness.
  • Exercise the harness's failure modes. Test one-channel perturbation, wrong dimensions, missing output, changed source/reference hash, nondeterminism, an unexpected refusal, a missing expected refusal, a non-discriminating placeholder, and matching images with missing content. No missing required case may yield a successful gate.
  • Promote a small reviewed pilot. Start with aligned, self-contained exact cases plus separate refusal assertions. A source control must demonstrate that each claimed branch matters; deliberately breaking the engine path must fail its gate, followed by exact restoration and a green re-gate. Full-image equality and sparse semantic probes are distinct assertions; a probe must not replace a failing exact-image requirement.
  • Integrate without weakening existing gates. Register new committed primitive cells using just add, then just bake, just gate, and just status; use the corresponding established workflow for other assertion kinds. Preserve all existing oracles, admitted-slice records, and checklist tick rules. The tool routes reviewed evidence into the existing corpus, not a second authoritative support list.
  • Review legacy succession separately. Inventory useful behavior in the old runners, map what is preserved or replaced, verify the replacement, and only then propose retirement. Do not port best-of-oracles scoring or legacy renderer routing. No deletion is approved here.

Acceptance for the pilot: a contributor can reproduce the setup, read a result without guessing what it proves, see why an unresolved case cannot pass, and deliberately make each required check fail. The tool must not compute or inspect conformance scores, change FLIP, silently bless images, or tick feature rows.

8. Relationship to existing records and feedback

This is the approved pilot model and its follow-up tracker, not a new normative support record. The existing admitted slice, Web checklist, and fixture tooling retain their roles. In particular, a passing example does not establish a whole checklist row's listed grammar.

The consolidation program, gridaco/nothing#43 supplies the standing boundaries. Corpus provisioning should coordinate with gridaco/nothing#7, not duplicate it. The old scored runners remain reference-only for this work; the current Chromium-gated corpus is not being retired with them.

The approval settled these initial questions:

  • Whether “one described claim, one discrete verdict” is the right unit and is clear enough to use.
  • Whether the separation of rendering assertions, refusal assertions, and unresolved observations is understandable.
  • Whether the two-lane setup and narrow first pilot are the right starting point.

The owner gave implementation approval after reviewing this issue. Wider resource/font/export profiles, WPT adapters, a general reference-consensus rule, and legacy retirement still require their own scoped work. FLIP remains unchanged.

Activity

  1. softmarshmallow commented on Sep 9, 2026

    @softmarshmallow
    MemberAuthor

    Maintainer approved this proposal and implementation in the review conversation on 2026-09-09. The first implementation is the bounded static, self-contained SVG pilot described above: actual n0 CLI in both admissions, the existing shared Chromium capture, discrete assertions, and separately labelled optional resvg/stored-PNG observations.

    No similarity score, conformance aggregate, FLIP change, new tolerance, feature tick, or legacy deletion is included. Implementation and independent verification are underway; the PR will carry the measured verdict and gate-sensitivity evidence. Legacy succession remains a separate follow-up.

  2. softmarshmallow commented on Sep 9, 2026

    @softmarshmallow
    MemberAuthor

    The approved bounded pilot is implemented in gridaco/nothing#141. Local full tests, fixture gates and the final independent CLI reproduction pass; CI and PR review are still running.

    The PR includes the measured gate-sensitivity narrative and the decoder/input protections discovered during review. It adds two described scene assertions plus a separate existing-refusal assertion—no feature tick, similarity score, new tolerance, oracle replacement or legacy retirement.

  3. softmarshmallow commented on Sep 12, 2026

    @softmarshmallow
    MemberAuthor

    The first real regression-suite expansion found a reference-environment boundary: the measured verdict on draft gridaco/nothing#142.

    Pinned Chromium149.0.7827.55 with the same capture module/source bytes produces repeat-stable differences between Linux/x64 CI and macOS/arm64 in three near-unit opacity cases. Both n0 builds agree with the committed macOS captures. The harness correctly stays red; the original pilot still passes.

    Follow-up before this expansion lands: isolate the environment difference, then explicitly define and review the canonical capture environment and how incompatible observations are labelled. A version string alone is not a complete raster identity. No automatic best-of-reference rule, oracle overwrite, tolerance, dropped required case or runner relocation has been applied. The exact underlying OS/architecture/backend cause remains unproved.

  4. softmarshmallow commented on Sep 14, 2026

    @softmarshmallow
    MemberAuthor

    Capture-environment investigation and proposed contract clarification

    For maintainers reviewing the exact-reference policy. Research only: no reference selection, golden rewrite, capture-posture change, tolerance, required-case removal, or CI relocation has been applied. gridaco/nothing#142 remains draft.

    What the measurements establish

    The three retained .999 disagreements now have an exact arithmetic explanation (measured, not celled).

    Chromium 149.0.7827.55's DEPS pins Skia 53348aa333da02b77c4b5797e2de722f5abde7d0. Its byte source-over implementation uses different integer arithmetic on ARM/NEON and SSE2/AVX2:

    • NEON: s + floor((d * (255 - a) + 127) / 255).
    • SSE2/AVX2: s + floor(d * (256 - a) / 256).

    Here s is a premultiplied source channel, a its alpha byte, and d the destination channel, with saturating addition. The paint alpha conversion rounds .999 to 255 and .998 to 254; the sprite dispatcher selects its global-alpha flag only below 255.

    Removing only the opaque background from each high-opacity source, retaining the .999 group, exposes the source pixels. Recovering their premultiplied bytes and applying those two upstream formulas over the original #426589 background gives:

    Source Observed local–Linux Chromium difference NEON prediction → local capture SSE2/AVX2 prediction → retained Linux capture
    Blur 1,926 pixels, maximum channel delta 1 Exact complete RGBA Exact complete RGBA
    Pattern 217 pixels, maximum channel delta 1 Exact complete RGBA Exact complete RGBA
    Radial gradient 1,168 pixels, maximum channel delta 1 Exact complete RGBA Exact complete RGBA

    No residual differences, tolerance, hidden-RGB normalization, or reference replacement. A second arithmetic implementation enumerated unique source bytes, including exact PNG unpremultiplication ties, and confirmed the result. Both checks were performed by the same agent, not an independent-person review. These are predictions checked against retained CI captures, not new Linux source-only captures or an instruction trace.

    Sixteen diagnostic source variants were captured twice through the unchanged shared probe instrument and rendered twice in each actual n0 CLI admission: 64 CLI executions, stable and exactly matching local Chromium without degradation. Source-only .999 and 1 are not interchangeable for every source: pattern and radial controls differ. No source substitution is being smuggled into the assertion.

    The current local capture is an ARM64 Chromium headless-shell executable (Playwright build 1228; SHA-256 11e393326c7d20a7c56641a7c65def33ea9c280da3b0b74cf8563b07989a0ee3) on macOS 26.5.1/build 25F80. CDP reports software rasterization/compositing. The capture-module hash remains 069296201718c43d29efe356fbea893781b73250dca51de3b6d47468d74027b0. This is today's measured identity, not retroactive provenance for older bakes. The browser command-line query was unavailable; no extra launch flag was added to make it available.

    Recommended policy — proposed, not enacted

    Keep one portable n0 pixel target. Make its reference environment explicit.

    1. Record a reference's browser binary/distribution identity, OS/version, architecture, relevant CPU/backend facts, launch/capture identity and existing viewport/color/time settings separately from the n0 build environment. Unknown required identity fails closed. Preserve historical bake records; new recapture attestations must not invent missing historical provenance.
    2. Recommend preserving the existing ARM software-Chromium compatibility target, subject to fresh attestation in a declared reproducible environment. The reason is continuity with independently baked evidence and the already documented portable UNORM8 behavior—not selecting whichever browser agrees with this repair. Runner provisioning and identity enforcement still need implementation/review.
    3. Separate the two obligations explicitly:
    Obligation Required result
    Engine correctness on every supported n0 platform Both admissions match the same immutable reference. Linux remains mandatory.
    Reference reproducibility in its declared capture environment Fresh Chromium and controls reproduce that reference exactly. Missing or drifting canonical capture remains red.

    Other Chromium environments remain separately identified observations with reviewed incompatibilities. They cannot select another golden, certify canonical reproduction, or turn an unresolved required check green. Any change to today's combined CI obligation must be reviewed before execution; this is not permission to change runs-on merely to pass.

    Separate blocker, still real

    The final full Linux Rust job also failed svg-opacity-source-blur-arc: 25 pixels, maximum delta 3. Its first differing pixel and values match the local native-circle-versus-arc signature. The complete Linux arc PNG was not retained, so full-image equivalence and the cause are not yet established.

    That engine failure is not excused by Chromium's rounding split. Next diagnostic: retain Linux CLI arc/circle images and compare emitted conic/control/weight bits before the painter, then repair the cause or quarantine it by name. Keep both original geometries and oracles.

    The focused execution tracker and fresh probe artifacts remain ignored/local-only. No tracked files changed in this investigation; main remains 549ece62, PR head c62e7f1b. No score, FLIP change, feature tick, or new support claim.

  5. softmarshmallow commented on Sep 18, 2026

    @softmarshmallow
    MemberAuthor

    Owner GO on 2026-09-18 approves implementing the proposed explicit reference-environment contract while preserving one portable n0 pixel target and mandatory Linux engine tests.

    The first bounded step is on draft #142: commit e7dd281c records passive browser executable/ABI/backend identity, adds the existing arc cell to actual-CLI assertions, and preserves full Rust failure images/frames. A new macOS ARM job is explicitly observe-only pending attestation; it does not replace or satisfy the existing Linux gates. No reference profile is yet enforced, and no golden/capture posture/tolerance changed.

    Local nine-assertion reproduction and all fixture gates pass; 45 tool tests/typechecks and targeted strict Clippy pass. A deliberate arc-fill perturbation failed the gate at 2,880 pixels / maximum delta 215 and retained its diagnostics; exact restoration returned the complete gate green. Hosted diagnostics now supply the missing evidence before the arc cause is patched.

  6. softmarshmallow commented on Sep 18, 2026

    @softmarshmallow
    MemberAuthor

    The approved reference-environment continuation is merged in gridaco/nothing#142, e6d91f2f.

    The tool contract now distinguishes engine correctness from canonical reference reproduction, using one unchanged set of expected pixels. Linux checks the first; the required canonical ARM job checks both. Reports preserve both verdicts, so an engine-only pass cannot hide a reference mismatch.

    Manifest v2 declares the actual browser executable ABI/hash/revision, full host kernel identity and effective raster status. Missing or changed identity fails reference reproduction even if pixels match. The runner label only provisions the machine; it is not the identity pin. Updates require explicit review and fresh attestation, not automatic re-blessing. This is new reproduction evidence, not retroactive provenance for old bakes.

    Actual-CLI sensitivity checks proved that browser-identity drift leaves exact engine evidence separate, while changed capture-module integrity fails both obligations. Required Linux/ARM CI, tool tests and independent review passed. The pilot remains three assertions; the ordinary-opacity suite now has seven rendering assertions and two separately labelled refusals. No alternate golden, similarity score, resvg-derived reference or legacy retirement was introduced. This issue remains open for separately scoped expansion and succession.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions