From fa207a91b6e38b7b0da89de1a751f16de6fe3d4d Mon Sep 17 00:00:00 2001 From: Universe Date: Fri, 28 Aug 2026 06:20:44 +0900 Subject: [PATCH 1/2] websem: admit exact-time SVG pattern clients --- crates/n0_cli/README.md | 17 +- crates/websem/src/svg.rs | 29 +++- crates/websem/src/svg_animation.rs | 22 ++- crates/websem/tests/filter_contract.rs | 43 +++++ crates/websem/tests/pattern_contract.rs | 14 +- crates/websem/tests/svg_animation_x.rs | 64 +++++++- crates/websem/tests/unsupported_corpus.rs | 15 ++ docs/wg/consolidation/svg-engine-of-record.md | 77 ++++++++- docs/wg/consolidation/web-checklist.md | 12 ++ fixtures/web-first/README.md | 2 + fixtures/web-first/STATUS.md | 5 +- fixtures/web-first/animation/README.md | 17 +- fixtures/web-first/animation/bake_chromium.ts | 33 ++-- fixtures/web-first/animation/cases.json | 58 +++++++ .../svg-pattern-client-animation/base.png | Bin 0 -> 197 bytes .../sample-0ns.png | Bin 0 -> 197 bytes .../sample-1000000000ns.png | Bin 0 -> 197 bytes .../sample-2000000000ns.png | Bin 0 -> 197 bytes .../sample-250000000ns.png | Bin 0 -> 205 bytes .../sample-3000000000ns.png | Bin 0 -> 197 bytes fixtures/web-first/animation/oracle-bake.json | 151 +++++++++++++++++- .../svg-pattern-client-animation.svg | 18 +++ .../animation/svg-pattern-client-base.svg | 16 ++ fixtures/web-first/unsupported/README.md | 9 +- .../svg-pattern-tile-phase-precision.svg | 12 ++ .../svg-smil-number-precision-alias.svg | 6 + .../svg-smil-number-source-syntax.svg | 6 + 27 files changed, 585 insertions(+), 41 deletions(-) create mode 100644 fixtures/web-first/animation/chromium/svg-pattern-client-animation/base.png create mode 100644 fixtures/web-first/animation/chromium/svg-pattern-client-animation/sample-0ns.png create mode 100644 fixtures/web-first/animation/chromium/svg-pattern-client-animation/sample-1000000000ns.png create mode 100644 fixtures/web-first/animation/chromium/svg-pattern-client-animation/sample-2000000000ns.png create mode 100644 fixtures/web-first/animation/chromium/svg-pattern-client-animation/sample-250000000ns.png create mode 100644 fixtures/web-first/animation/chromium/svg-pattern-client-animation/sample-3000000000ns.png create mode 100644 fixtures/web-first/animation/svg-pattern-client-animation.svg create mode 100644 fixtures/web-first/animation/svg-pattern-client-base.svg create mode 100644 fixtures/web-first/unsupported/svg-pattern-tile-phase-precision.svg create mode 100644 fixtures/web-first/unsupported/svg-smil-number-precision-alias.svg create mode 100644 fixtures/web-first/unsupported/svg-smil-number-source-syntax.svg diff --git a/crates/n0_cli/README.md b/crates/n0_cli/README.md index 9aaea35d..30c727a2 100644 --- a/crates/n0_cli/README.md +++ b/crates/n0_cli/README.md @@ -539,8 +539,8 @@ cargo run -p n0_cli --bin n0 -- \ The filter estate contains 26 chassis/blur cells, 60 shadow-graph, 28 native drop-shadow, 27 color-matrix, 32 component-transfer, 38 blend, 37 morphology, 91 turbulence/displacement, 41 convolution-rung, and 71 diffuse-lighting - cells. The complete corpus contains 936 Chromium-baked cells plus 10 sampled - frames, with 169 named + cells. The complete corpus contains 936 Chromium-baked cells plus 16 sampled + frames, with 172 named refusal rows. `feFlood`, `feComposite`, `feMerge`, `feMergeNode`, `feDropShadow`, `feColorMatrix`, `feComponentTransfer`, `feBlend`, `feMorphology`, `feConvolveMatrix`, @@ -837,6 +837,19 @@ cargo run -p n0_cli --bin n0 -- \ conservative provenance patrol remains. The `` and `patternTransform` checklist rows therefore stay open; only `patternUnits` and `patternContentUnits` close. + The exact-time `` slice may target the same direct + sharp-cornered rectangle while it carries admitted pattern paint, including + a filter inside the tile and a source-derived filter around the target. Six + committed Chromium frames cover Base and exact samples at 0, 0.25, 1, 2, + and 3 seconds; strict and best-effort pixels are exact and identical. + Pattern source geometry and tile geometry remain static. A fractional X or + Y tile phase refuses at the measured picture-shader boundary: four + discriminating witnesses differ from Chromium by 96–576 pixels at maximum + channel delta 1. Animation endpoints use SVG-number syntax and a one-way + Chromium-normalization patrol; trailing-dot and Unicode-whitespace forms + formerly admitted only by Rust, and one valid midpoint-adjacent decimal + formerly selected the wrong binary32 neighbour. Three focused refusal rows + guard those classes. The `` and `` rows remain open. `` is consumed (the text rung), and its font environment is the host's: text resolves only against fonts declared with `--font FAMILY=PATH@sha256:HEX` (repeatable), whose bytes are **verified diff --git a/crates/websem/src/svg.rs b/crates/websem/src/svg.rs index be99b9bb..a06347f7 100644 --- a/crates/websem/src/svg.rs +++ b/crates/websem/src/svg.rs @@ -2906,6 +2906,12 @@ impl<'a> PatternCompiler<'a> { active_patterns, first.node_id(), )?; + let is_integer = |value: f32| value.is_finite() && value == value.round(); + if !items.is_empty() && (!is_integer(x) || !is_integer(y)) { + return Err(format!( + "pattern #{fragment} tile phase resolves to a fractional coordinate at the pinned-backend picture-shader phase precision boundary" + )); + } let pattern = PatternPaint::new( width, height, @@ -6634,6 +6640,17 @@ mod filter_resource { /// and drop shadow). Keep that operation-independent source profile here /// so a newly admitted primitive cannot silently bypass the same raster /// boundary. + fn has_non_animation_element_child(element: HtmlElement<'_>) -> bool { + let mut child = element.first_element_child(); + while let Some(candidate) = child { + if !crate::svg_animation::is_animation_element(&candidate.local_name_string()) { + return true; + } + child = candidate.next_element_sibling(); + } + false + } + fn filter_source_crosses_pattern_profile_boundary<'d>( target: HtmlElement<'d>, servers: &PaintServers<'d>, @@ -6661,7 +6678,7 @@ mod filter_resource { if let Some(pattern_property) = pattern_property { if !is_target || element.local_name_string() != "rect" - || element.first_element_child().is_some() + || has_non_animation_element_child(element) || get_attr(element, "rx").is_some() || get_attr(element, "ry").is_some() { @@ -6733,7 +6750,7 @@ mod filter_resource { if !matches!( target.local_name_string().as_str(), "rect" | "circle" | "ellipse" | "path" | "polygon" | "polyline" - ) || target.first_element_child().is_some() + ) || has_non_animation_element_child(target) { return Ok(false); } @@ -11221,6 +11238,14 @@ fn geometry_number_source_loses_provenance(source: &str, percentage: bool) -> bo } } +/// Whether a direct unitless source number's correctly rounded `f32` differs +/// from Chromium's measured CSS-number route. The shadow `f64` conversion is +/// a classifier only: callers refuse the source and never substitute it as a +/// second parser. +pub(crate) fn direct_number_source_loses_chromium_provenance(source: &str) -> bool { + geometry_number_source_loses_provenance(source, false) +} + #[cfg(test)] mod geometry_number_resolution_tests { use super::*; diff --git a/crates/websem/src/svg_animation.rs b/crates/websem/src/svg_animation.rs index c25a672d..4fc3d818 100644 --- a/crates/websem/src/svg_animation.rs +++ b/crates/websem/src/svg_animation.rs @@ -16,7 +16,10 @@ use csscascade::dom::{DemoNodeData, NodeId}; use style::dom::TElement; use crate::effective_values::EffectiveValues; -use crate::svg::SourceEntry; +use crate::svg::{ + SourceEntry, direct_number_source_loses_chromium_provenance, dots_carry_digits, + trim_svg_whitespace, +}; /// A deterministic rejection from the deliberately closed rect-x animation /// slice. @@ -487,7 +490,14 @@ fn required<'a>( } fn parse_finite_number(value: &str, name: &str, path: &str) -> Result { - let parsed = value.trim().parse::().map_err(|_| { + let source = trim_svg_whitespace(value); + if !dots_carry_digits(source) { + return Err(AnimationError::new( + path, + format!("{name}={value:?} is not a unitless SVG number"), + )); + } + let parsed = source.parse::().map_err(|_| { AnimationError::new( path, format!("{name}={value:?} is not a unitless SVG number"), @@ -499,6 +509,14 @@ fn parse_finite_number(value: &str, name: &str, path: &str) -> Result` child as source geometry over-refuses an otherwise exact P2 +/// pattern/filter program. +#[test] +fn animation_child_does_not_widen_the_filtered_pattern_rect_profile() { + let source = document( + r##" + + + + + + + + + + + + + + + "##, + ); + let strict = SvgFrameSource::from_standalone_svg(source.clone(), viewport()) + .expect("the direct animated pattern rect stays inside the measured profile"); + let best = SvgFrameSource::from_standalone_svg_best_effort(source, viewport()) + .expect("best effort admits the same dynamic source"); + assert!(best.degradations().is_empty()); + assert_eq!(strict.base_frame(), best.base_frame()); + for time_ns in [0, 250_000_000, 1_000_000_000, 2_000_000_000, 3_000_000_000] { + let time = animation_sampling::SampleTime::from_nanoseconds(time_ns); + assert_eq!( + strict.sample_frame(time).expect("strict sample"), + best.sample_frame(time).expect("best-effort sample"), + "admissions agree at {time_ns}ns" + ); + } +} + #[test] fn filtered_descendants_refuse_same_scope_clip_and_partial_opacity() { let defs = r##" diff --git a/crates/websem/tests/pattern_contract.rs b/crates/websem/tests/pattern_contract.rs index 31eeb15b..ba2fdf9a 100644 --- a/crates/websem/tests/pattern_contract.rs +++ b/crates/websem/tests/pattern_contract.rs @@ -129,7 +129,7 @@ fn invalid_and_valid_empty_patterns_select_different_fallback_outcomes() { assert_eq!(solid_of(fallback), CGColor::from_rgb(0xef, 0x44, 0x44)); let valid_empty = admit_both(&document( - r##" selected local content + r##" selected local content "##, )); let pattern = pattern_of(&valid_empty, 0); @@ -368,6 +368,18 @@ fn every_measured_picture_shader_boundary_refuses_in_both_admissions() { r##""##, "sampling precision boundary", ), + ( + r##""##, + "phase precision boundary", + ), + ( + r##""##, + "phase precision boundary", + ), + ( + r##""##, + "phase precision boundary", + ), ]; for (defs, reason) in cases { diff --git a/crates/websem/tests/svg_animation_x.rs b/crates/websem/tests/svg_animation_x.rs index 00174a01..cd459d8c 100644 --- a/crates/websem/tests/svg_animation_x.rs +++ b/crates/websem/tests/svg_animation_x.rs @@ -4,10 +4,9 @@ //! pixels. These tests consume only those artifacts; they never run the sealed //! consolidation scoreboard or compute a similarity score. //! -//! The slice itself is one `` on a top-level ``'s `x`. Two -//! fixtures exercise it: a bare rect on a backdrop, and `svg-scene-cub` — a -//! whole composition whose animated rect is one node among seventeen, so the -//! sampling path is gated over a scene rather than only over a single shape. +//! The slice itself is one `` on a top-level ``'s `x`. The +//! fixtures exercise a bare rect, a whole curve-heavy composition, and a +//! pattern-painted rect whose source and target both cross admitted filters. mod support; @@ -101,6 +100,7 @@ struct BakeManifest { schema_version: u32, kind: String, bake_script_sha256: String, + capture_module_sha256: String, suite: String, suite_sha256: String, capture: CapturePolicy, @@ -111,6 +111,7 @@ struct BakeManifest { struct CapturePolicy { viewport: String, device_scale_factor: u32, + javascript_enabled: bool, network: String, timeline_control: String, comparison: String, @@ -235,11 +236,16 @@ fn chromium_animation_oracle_provenance_is_current() { manifest.bake_script_sha256, sha256_file(&root.join("bake_chromium.ts")) ); + assert_eq!( + manifest.capture_module_sha256, + sha256_file(&root.join("../chromium_capture.ts")) + ); assert_eq!( manifest.capture.viewport, "per-fixture declared dims (the initial viewport)" ); assert_eq!(manifest.capture.device_scale_factor, 1); + assert!(!manifest.capture.javascript_enabled); assert_eq!( manifest.capture.network, "all http(s) requests aborted; zero attempted" @@ -546,6 +552,26 @@ fn load_active_animation_refuses_at_construction_and_skips_by_declaration() { r#""#, "positive integer in ms or s", ), + ( + r#""#, + "not a unitless SVG number", + ), + ( + r#""#, + "not a unitless SVG number", + ), + ( + r#""#, + "not a unitless SVG number", + ), + ( + r#""#, + "not a unitless SVG number", + ), + ( + r#""#, + "source number loses Chromium used-value provenance", + ), ]; for (markup, expected) in CASES { @@ -594,6 +620,36 @@ fn load_active_animation_refuses_at_construction_and_skips_by_declaration() { } } +#[test] +fn measured_svg_number_controls_remain_admitted_animation_endpoints() { + for spelling in [" 1 ", "+1", ".1e1"] { + let svg = format!( + r#" + + + + "# + ); + let strict = SvgFrameSource::from_standalone_svg(svg.clone(), host_viewport()) + .unwrap_or_else(|error| panic!("measured control {spelling:?}: {error}")); + let best = SvgFrameSource::from_standalone_svg_best_effort(svg, host_viewport()) + .unwrap_or_else(|error| panic!("best-effort control {spelling:?}: {error}")); + assert!(best.degradations().is_empty()); + + let strict_sample = strict + .sample_frame(SampleTime::ZERO) + .expect("strict control sample"); + let best_sample = best + .sample_frame(SampleTime::ZERO) + .expect("best-effort control sample"); + assert_eq!(strict_sample, best_sample); + let Geometry::Rect(rect) = &strict_sample.nodes()[0].geometry else { + panic!("measured control must remain a rectangle") + }; + assert_eq!(rect.x, 1.0, "measured control {spelling:?}"); + } +} + #[test] fn sampling_refuses_dynamic_side_channels_and_unclosed_inline_html() { for (rect_attributes, child, expected) in [ diff --git a/crates/websem/tests/unsupported_corpus.rs b/crates/websem/tests/unsupported_corpus.rs index 06b6a503..f016f496 100644 --- a/crates/websem/tests/unsupported_corpus.rs +++ b/crates/websem/tests/unsupported_corpus.rs @@ -648,6 +648,11 @@ const CORPUS: &[(&str, Departure, &str)] = &[ DeclaredByBestEffort, "source cannot compile completely", ), + ( + "svg-pattern-tile-phase-precision", + DeclaredByBestEffort, + "picture-shader phase precision boundary", + ), ( "svg-pattern-tile-sampling-precision", DeclaredByBestEffort, @@ -683,6 +688,16 @@ const CORPUS: &[(&str, Departure, &str)] = &[ DeclaredByBestEffort, "animation element ", ), + ( + "svg-smil-number-precision-alias", + DeclaredByBestEffort, + "source number loses Chromium used-value provenance", + ), + ( + "svg-smil-number-source-syntax", + DeclaredByBestEffort, + "not a unitless SVG number", + ), ("svg-smil-retarget-href", BothRefuse, "href"), ( "svg-smil-set-load-active", diff --git a/docs/wg/consolidation/svg-engine-of-record.md b/docs/wg/consolidation/svg-engine-of-record.md index ae145287..afba9384 100644 --- a/docs/wg/consolidation/svg-engine-of-record.md +++ b/docs/wg/consolidation/svg-engine-of-record.md @@ -81,16 +81,18 @@ from the dated addenda below: sharp-cornered rectangle may supply a filter's source image; one declared-font, single-run `` profile; viewBox-only root sizing with the full `preserveAspectRatio` grammar; and one exact-time - `` on a top-level ``. + `` on a top-level ``, including a client + carrying admitted repeating-pattern paint and admitted source/target filter + composition. `crates/n0_cli/README.md` is the statement of record. -- **The corpus** is 936 Chromium-baked primitive cells plus 10 sampled frames. +- **The corpus** is 936 Chromium-baked primitive cells plus 16 sampled frames. All byte-exact except seven curved cells carrying a declared, geometrically confined tolerance (the native-oval/conic boundary) and four 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; 576 after a masked ramp becomes luminance alpha). The named refusal - register has 169 rows. + register has 172 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 @@ -4024,3 +4026,72 @@ on `svg-pattern-context-filter-color-matrix` with the existing color-matrix source-layer refusal. Restoring the classification returned the complete 936-cell gate to green. No conformance score was produced, and no FLIP record, rule, or baseline changed. + +## Rung: exact-time SVG pattern clients (2026-08-28) + +The verdict is ADMIT/SPLIT with no checklist closure. The existing exact-time +animation of a top-level rectangle's `x` coordinate may now move a client that +carries the P2 repeating-pattern profile: a same-document template, a filter +inside the tile program, and a source-derived filter around the client. Pattern +source geometry and tile geometry remain static. ``, ``, and +all shared dynamics and geometry rows therefore remain open. + +This needs no second temporal or painting meaning. Sampling still changes one +effective scalar on the existing rectangle and recompiles the same immutable +resolved frame. The pattern source program, target filter, resolved contract, +and painter vocabulary are unchanged. An animation element contributes no +static draw to the filtered-pattern source profile; the closed animation +inventory remains solely responsible for deciding whether that dynamic surface +is admitted. Treating the non-rendering animation child as source geometry had +incorrectly widened the otherwise exact direct-rectangle profile. + +The committed oracle adds six frames: the static Base projection and exact +samples at 0, 0.25, 1, 2, and 3 seconds. Chromium 149.0.7827.55 captured every +case twice on fresh pages, then reproduced it through shuffled retained seeks. +The shared hash-pinned capture module disabled script and network access and is +recorded in the animation manifest. Strict and best-effort n0 renders are exact +decoded RGBA against every frame and against each other. The quarter-second +case distinguishes an interpolated value from both endpoints; the three-second +case distinguishes frozen fill from continued motion. + +The wider scratch matrix crossed direct and templated clients, object-box and +user-space patterns, filters inside and outside the tile, ``, strokes, +axis maps, reflections, and exact quarter turns. Moving the client stayed exact +through the admitted profiles (measured, not celled). Moving pattern source +geometry did not earn admission: a fractional source coordinate reaches the +retained source-coverage precision boundary. Pattern-phase motion also did not +earn admission. Several phase controls were exact, but an object-box X witness +changed 192 pixels at maximum channel delta 1, a filtered-source X witness +changed 356 at delta 1, and discriminating Y mirrors changed 96 and 576 at +delta 1. The boundary is therefore content-dependent, not a tolerance case. + +A focused phase patrol now refuses a non-empty valid pattern whose resolved X +or Y tile coordinate is fractional. It runs only after tile validity and source +compilation, so an invalid server still selects its authored fallback and a +valid empty server still suppresses fallback as transparent. Integer phase +remains admitted. The same stable name protects strict and best-effort paths. + +The endpoint-number crux found two silent parser departures. The valid decimal +`1.000000059604644775390625000000000000000000000001` resolves to `1` through +Chromium's animation-value route while the former direct binary32 parse chose +the next value; an exact transform amplifier changed 48 pixels at maximum +channel delta 238. Chromium also rejects trailing-dot spellings (`1.`, `1.e0`) +and Unicode NBSP or ideographic-space padding that Rust's broader float parser +formerly admitted; the amplifier changed 3,072 pixels at delta 238. ASCII SVG +whitespace, leading plus, and a leading fractional dot remain admitted +controls. One-way provenance and SVG-number-syntax patrols now reject the two +unsafe classes by stable name without substituting a guessed value (measured, +not celled). + +Gate sensitivity was proved twice. Temporarily accepting only zero as an +integer tile phase made `just gate` fail on `svg-pattern-negative-origin` with +the new phase refusal; restoring the classifier returned all 936 primitive +cells to green. Temporarily treating the `` child as static source +geometry made the exact-time oracle test fail on +`svg-pattern-client-animation` with the filtered-pattern coverage refusal; +restoring dynamic ownership returned all sixteen sampled frames to green. + +The primitive corpus remains 936 cells and the filter estate remains 451 +cells. The sampled corpus moves from 10 to 16 frames. Three focused rows join +the refusal register, moving it from 169 to 172. No conformance score was +produced, and no FLIP record, rule, or baseline changed. diff --git a/docs/wg/consolidation/web-checklist.md b/docs/wg/consolidation/web-checklist.md index d0ead31c..4c38d224 100644 --- a/docs/wg/consolidation/web-checklist.md +++ b/docs/wg/consolidation/web-checklist.md @@ -1731,6 +1731,18 @@ for attributes the platform ships ahead of the SVG 2 indexes. > every P1 source-coverage/effect/nesting boundary above also remains. External > I/O, unsupported source descendants, and pattern dynamics are unchanged. > No checklist row closes in this composition rung. +> +> **2026-08-28 exact-time split:** a top-level rectangle may now move through +> the admitted `x` animation while carrying a templated pattern, a filter +> inside that pattern, and a source-derived filter around the client. Base and +> exact samples at 0, 0.25, 1, 2, and 3 seconds are committed Chromium oracle +> frames. Pattern-source and tile-geometry dynamics remain outside the slice. +> Fractional X and Y tile phase is a content-dependent picture-shader boundary +> and now refuses by stable name; measured witnesses differ by 96–576 pixels +> at maximum channel delta 1. Animation endpoint numbers also gained stable +> SVG-syntax and Chromium-normalization patrols after measured silent +> divergences. Neither `` nor `` closes, and no other +> checklist row changes. - [x] `` - [x] `` diff --git a/fixtures/web-first/README.md b/fixtures/web-first/README.md index 722a18af..615bb31f 100644 --- a/fixtures/web-first/README.md +++ b/fixtures/web-first/README.md @@ -257,6 +257,8 @@ is exactly what the engine renders pixel-for-pixel. | `svg-pattern-target-filter-{blur,offset,composite,merge,drop-shadow,color-matrix,component-transfer,blend,morphology,displacement,convolve,diffuse}.svg` · `svg-pattern-filter-profile-*.svg` | Twelve pattern-as-SourceGraphic operation cells plus six source-profile cells. The measured direct sharp-rectangle profile includes fill/stroke paint opacity, transparent and alpha-bearing tiles, gradients, masks, strokes, and nested patterns. All are byte-exact. | | *(measured, not celled — retained pattern splits)* | Twice-deterministic Chromium 149 probes and both actual CLI admissions establish the conservative picture-shader envelope. General rotation is content-dependent: sampled layouts were exact, while two grids changed 2px/Δ1 and 1px/Δ3. Shear/skew changed 147–222px/Δ2; fractional final tile extents changed 164–407px up to Δ28; curved source geometry changed 189–315px up to Δ32; isolated multi-draw source opacity changed 1,152–1,728px/Δ2; a circular source clip changed 216px/Δ9; and another draw beside a nested pattern changed 108px/Δ1. The used-range witnesses changed 768–2,112px at Δ205 before their patrol. CSS comments around a valid tile length changed all 2,304 target pixels at Δ202, and a CSS percentage transform using the former tile-width basis changed 1,008px at Δ205. Stable refusal fixtures guard each class. The `57384.267578125007%` and midpoint-adjacent controls remain pixel-identical at 64×64, so no second mismatch is claimed for that conservative provenance patrol. | | *(measured, not celled — pattern/filter split)* | The context-aware filter audit found that an authored context keyword hid the eventual gradient from old source-profile patrols: component transfer changed 738px/Δ1, convolution 2,277px/Δ7, morphology 1,262px/Δ1, and native drop shadow 684px/Δ1. Those routes now reach their existing stable names. Pattern profiles were exact across 154 strict/best comparisons over transparent, alpha, opaque, gradient-bearing, masked, stroked, and nested tiles, and another 112 comparisons covered fill/stroke, both context crossings, and destination paint opacity. Follow-ups made all twelve admitted families visibly exercise a sole nested-pattern draw (26 exact strict/best comparisons including the control), then crossed the six remaining families with direct/context pattern strokes at opaque and partial paint opacity (56 exact comparisons). A blanket exemption was rejected: a pattern-filled cubic path differed under color matrix/component transfer (1px/Δ1), offset (1px/Δ2), and merge (2px/Δ2); a rounded rect differed under blur (15px/Δ1), morphology (1px/Δ7), and drop shadow (4px/Δ4). The new filtered-pattern coverage refusal admits only the complete direct sharp-rect profile. Misclassifying a pattern as an unadmitted server made `just gate` fail on the context/color-matrix cell; restoration returned all 936 cells green. The corpus is now 936 cells plus 10 sampled frames, the separately tracked filter estate remains 451 cells, and the named register has 169 rows. | +| `animation/svg-pattern-client-{base,animation}.svg` | Six committed Chromium frames carry the admitted exact-time client through a templated pattern, a source color-matrix filter, and a target color-matrix filter: Base plus samples at 0, 0.25, 1, 2, and 3 seconds. Every strict and best-effort render is exact decoded RGBA, including the frozen endpoint and shuffled retained seeks. | +| *(measured, not celled — pattern dynamics split)* | Moving the pattern-painted client is exact; moving pattern source geometry or tile phase is not admitted. Fractional phase is content-dependent: object-box and filtered-source X witnesses changed 192px/Δ1 and 356px/Δ1, while discriminating Y mirrors changed 96px/Δ1 and 576px/Δ1. A valid midpoint-adjacent animation endpoint selected opposite binary32 neighbours in Chromium and the former direct parser, changing 48px/Δ238. Rust-only trailing-dot and Unicode-whitespace spellings changed 3,072px/Δ238 before the SVG-number syntax patrol. Three focused refusal rows guard these classes. The primitive corpus remains 936 cells, the sampled corpus moves from 10 to 16 frames, the filter estate remains 451 cells, and the named register moves from 169 to 172 rows. No checklist row closes. | | `svg-path-cubic-fill.svg` · `svg-path-smooth-cubic.svg` · `svg-path-quadratic.svg` | Curved path cells: a cubic, an `S` continuation, and a `Q`+`T` pair. All three bake **byte-exact** — see the note below. | | `svg-path-fill-rule-nonzero.svg` · `svg-path-fill-rule-evenodd.svg` · `svg-path-fill-rule-inherited.svg` | One self-intersecting star under each fill rule (core filled vs hollow), and the rule inherited from a `` through the one cascade. | | `svg-path-two-subpaths.svg` · `svg-path-in-scaled-group.svg` | Two closed contours in one `d`, and a path carried by a group's `scale(2)`. | diff --git a/fixtures/web-first/STATUS.md b/fixtures/web-first/STATUS.md index f82cabc5..323c4ac7 100644 --- a/fixtures/web-first/STATUS.md +++ b/fixtures/web-first/STATUS.md @@ -965,7 +965,7 @@ to its fixture source. No new image is committed for this view. svg-visibility-rule-beats-attribute svg-visibility-unhide -## The refusal register (169) +## The refusal register (172) What the slice refuses, by name, in the compiler's own words — **both refuse** is a document-level contract; **declared** renders @@ -1107,6 +1107,7 @@ its row into the cells above. | `svg-pattern-source-coverage-precision` | declared | skipped svg/rect[2]: unsupported fill value "url(#p): pattern #p source carries curved/vector geometry at the pinned-backend picture-shader source-coverage precision boundary" | | `svg-pattern-source-effect-precision` | declared | skipped svg/rect[2]: unsupported fill value "url(#p): pattern #p source carries an isolated opacity or geometric clip at the pinned-backend picture-shader source-effect precision boundary" | | `svg-pattern-source-unsupported` | declared | skipped svg/rect[2]: unsupported fill value "url(#p): pattern #p source cannot compile completely: unsupported element " | +| `svg-pattern-tile-phase-precision` | declared | skipped svg/rect[2]: unsupported fill value "url(#p): pattern #p tile phase resolves to a fractional coordinate at the pinned-backend picture-shader phase precision boundary" | | `svg-pattern-tile-sampling-precision` | declared | skipped svg/rect[2]: unsupported fill value "tile has a fractional final device extent at the pinned-backend picture-shader sampling precision boundary" | | `svg-pattern-transform-none-provenance` | declared | skipped svg/rect[2]: unsupported fill value "url(#p): an author stylesheet may set transform:none on a derived pattern; the empty computed value loses the provenance needed to decide template inheritance" | | `svg-points-odd-coordinate` | declared | skipped svg/polygon[1]: points on is invalid at byte 17 (near "") | @@ -1114,6 +1115,8 @@ its row into the cells above. | `svg-preserve-aspect-ratio-defer` | **both refuse** | preserveAspectRatio "defer xMidYMid meet" is invalid | | `svg-preserve-aspect-ratio-invalid-align` | **both refuse** | preserveAspectRatio "xMidYMiddle meet" is invalid | | `svg-smil-animate-transform` | declared | skipped svg/g[1]: its authored state is overridden at document load by the unsupported animation at svg/g[1]/animateTransform[1]: animation element is outside the rect-x proving slice | +| `svg-smil-number-precision-alias` | declared | skipped svg/rect[2]: its authored state is overridden at document load by the unsupported animation at svg/rect[2]/animate[1]: from source number loses Chromium used-value provenance at the binary32 normalization boundary | +| `svg-smil-number-source-syntax` | declared | skipped svg/rect[2]: its authored state is overridden at document load by the unsupported animation at svg/rect[2]/animate[1]: from="1." is not a unitless SVG number | | `svg-smil-retarget-href` | **both refuse** | SVG animation at svg/rect[2]/set[1] is unsupported: animation element is outside the rect-x proving slice; it carries href, so its target cannot be attributed to one element without id resolution; it is active at document load, so the authored state it overrides cannot render as the Base view | | `svg-smil-set-load-active` | declared | skipped svg/rect[2]: its authored state is overridden at document load by the unsupported animation at svg/rect[2]/set[1]: animation element is outside the rect-x proving slice | | `svg-stroke-dasharray-escape` | declared | skipped svg/path[1]: unsupported stroke value "a stroke-dasharray on carries a CSS escape this patrol cannot read" | diff --git a/fixtures/web-first/animation/README.md b/fixtures/web-first/animation/README.md index 47eb8d5e..7eb0048c 100644 --- a/fixtures/web-first/animation/README.md +++ b/fixtures/web-first/animation/README.md @@ -6,14 +6,18 @@ authored scene — captured from Chromium at a paused timeline, so the engine's Base view and its exact-nanosecond samples are both gated against browser pixels. -The admitted animation slice is deliberately one thing: a single `` on -a top-level ``'s `x`, with `from`/`to`/`dur`/`fill="freeze"`. Everything -else in a fixture is ordinary static vocabulary. +The admitted animation slice is deliberately one target and value vocabulary: +a single `` on a top-level ``'s `x`, with +`from`/`to`/`dur`/`fill="freeze"`. Everything else in a fixture is ordinary +static vocabulary. The rectangle may use the admitted repeating-pattern paint +and pattern/filter composition profile; the pattern source and tile geometry +remain static. | Fixture | What it is | | --- | --- | | `svg-rect-x-animation` | The minimal case: one black rect on a white backdrop. Authored `x` is `4`; the animation moves it from `20` to `44` over two seconds and freezes there. | | `svg-scene-cub` | The same slice over a **whole composition** — see below. Authored `x` is `12`; the block slides from `6` to `38` over two seconds and freezes. | +| `svg-pattern-client-animation` | The same animated client carrying a templated repeating pattern, a color-matrix filter inside the tile, and another color-matrix filter around the target. Its quarter-second sample proves a non-endpoint exact time as the rect moves from `8` to `24`. | - `-base.svg` is the static Base projection: the same authored scene with the animation element removed. @@ -26,9 +30,16 @@ else in a fixture is ordinary static vocabulary. - `bake_chromium.ts` verifies the DOM animation value and bounding box before each capture. It double-captures every case on fresh pages, then seeks a retained document in shuffled order and requires exact decoded RGBA equality. +- The baker and primitive-cell harness import the same hash-pinned + `../chromium_capture.ts`; the animation manifest records that module's hash + as well as the baker and suite hashes. - `oracle-bake.json` records the Chromium version, environment, inputs, outputs, hashes, and capture policy. +The three fixtures contain 16 committed oracle frames: three Base projections +and thirteen exact-time samples. They do not change the separately counted +primitive-cell corpus. + Chromium does not expose the engine's Base policy for an animated document. The Base oracle is therefore deliberately a static authoring projection, not an observation at time zero. `Sample(0ns)` is separate, and each fixture's diff --git a/fixtures/web-first/animation/bake_chromium.ts b/fixtures/web-first/animation/bake_chromium.ts index c14fafdd..838ebbbd 100644 --- a/fixtures/web-first/animation/bake_chromium.ts +++ b/fixtures/web-first/animation/bake_chromium.ts @@ -20,9 +20,14 @@ import { dirname, join } from "node:path"; import { exit } from "node:process"; import { fileURLToPath } from "node:url"; -import { chromium, type BrowserContext, type Page } from "@playwright/test"; +import { type BrowserContext, type Page } from "@playwright/test"; import { PNG } from "pngjs"; +import { + deterministicContext, + launchDeterministicChromium, +} from "../chromium_capture"; + interface BaseCase { source: string; oracle: string; @@ -80,6 +85,7 @@ const SCRIPT_PATH = fileURLToPath(import.meta.url); const DIR = dirname(SCRIPT_PATH); const SUITE_PATH = join(DIR, "cases.json"); const MANIFEST_PATH = join(DIR, "oracle-bake.json"); +const CAPTURE_MODULE_PATH = join(DIR, "../chromium_capture.ts"); function sha256(bytes: Uint8Array): string { return createHash("sha256").update(bytes).digest("hex"); @@ -442,33 +448,23 @@ async function bakeFixture( } async function main(): Promise { - const [scriptBytes, suiteBytes] = await Promise.all([ + const [scriptBytes, suiteBytes, captureModuleBytes] = await Promise.all([ readFile(SCRIPT_PATH), readFile(SUITE_PATH), + readFile(CAPTURE_MODULE_PATH), ]); const suite = JSON.parse(suiteBytes.toString("utf8")) as CaseSuite; validateSuite(suite); - const browser = await chromium.launch({ - args: ["--no-sandbox", "--disable-setuid-sandbox"], - }); + const browser = await launchDeterministicChromium(); const browserVersion = browser.version(); - const context = await browser.newContext({ - javaScriptEnabled: true, - viewport: { width: suite.fixtures[0].width, height: suite.fixtures[0].height }, - deviceScaleFactor: 1, - colorScheme: "light", - locale: "en-US", - timezoneId: "UTC", - }); + const context = await deterministicContext(browser); const networkAttempts: string[] = []; - await context.route("**/*", (route) => { - const url = route.request().url(); + context.on("request", (request) => { + const url = request.url(); if (url.startsWith("http://") || url.startsWith("https://")) { networkAttempts.push(url); - return route.abort(); } - return route.continue(); }); try { @@ -494,6 +490,7 @@ async function main(): Promise { platform: `${process.platform}-${process.arch}`, node_version: process.version, bake_script_sha256: sha256(scriptBytes), + capture_module_sha256: sha256(captureModuleBytes), suite: "cases.json", suite_sha256: sha256(suiteBytes), capture: { @@ -504,7 +501,7 @@ async function main(): Promise { timezone: "UTC", omit_background: true, source_transport: "data-url-from-exact-file-bytes", - javascript_enabled: true, + javascript_enabled: false, network: "all http(s) requests aborted; zero attempted", target: "root-svg-element", timeline_control: "pauseAnimations() then setCurrentTime(ns / 1e9)", diff --git a/fixtures/web-first/animation/cases.json b/fixtures/web-first/animation/cases.json index c32bc441..f2fe386e 100644 --- a/fixtures/web-first/animation/cases.json +++ b/fixtures/web-first/animation/cases.json @@ -102,6 +102,64 @@ 3000000000 ] } + }, + { + "id": "svg-pattern-client-animation", + "width": 64, + "height": 64, + "probe": "#probe", + "authored_base_x": 4, + "frame": { + "node_count": 2, + "animated_node_index": 1 + }, + "base": { + "source": "svg-pattern-client-base.svg", + "oracle": "chromium/svg-pattern-client-animation/base.png", + "expected_x": 4 + }, + "animation": { + "source": "svg-pattern-client-animation.svg", + "samples": [ + { + "time_ns": 0, + "oracle": "chromium/svg-pattern-client-animation/sample-0ns.png", + "expected_x": 8 + }, + { + "time_ns": 250000000, + "oracle": "chromium/svg-pattern-client-animation/sample-250000000ns.png", + "expected_x": 10 + }, + { + "time_ns": 1000000000, + "oracle": "chromium/svg-pattern-client-animation/sample-1000000000ns.png", + "expected_x": 16 + }, + { + "time_ns": 2000000000, + "oracle": "chromium/svg-pattern-client-animation/sample-2000000000ns.png", + "expected_x": 24 + }, + { + "time_ns": 3000000000, + "oracle": "chromium/svg-pattern-client-animation/sample-3000000000ns.png", + "expected_x": 24 + } + ], + "retained_seek_order_ns": [ + 2000000000, + 250000000, + 0, + 3000000000, + 1000000000, + 250000000, + 2000000000, + 0, + 1000000000, + 3000000000 + ] + } } ] } diff --git a/fixtures/web-first/animation/chromium/svg-pattern-client-animation/base.png b/fixtures/web-first/animation/chromium/svg-pattern-client-animation/base.png new file mode 100644 index 0000000000000000000000000000000000000000..de09491f5f9c2bcb4b11e0e7d708b42dd3eb447b GIT binary patch literal 197 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1SD0tpLGJMDo+>3kcv5PZY|_$br5g}ynncU zn}lxN*IPMaj(XhBm!zu4&aikbkv#Ke{meNHvw-l{FB6M%kL%vw{`c#DcGwEF1;j(M y+5_kBX81Vx0^wVrBmV#Ic;u}FbOjs870e9T>HKx-KiX`8A`G6celF{r5}E)n2uWiA literal 0 HcmV?d00001 diff --git a/fixtures/web-first/animation/chromium/svg-pattern-client-animation/sample-0ns.png b/fixtures/web-first/animation/chromium/svg-pattern-client-animation/sample-0ns.png new file mode 100644 index 0000000000000000000000000000000000000000..de09491f5f9c2bcb4b11e0e7d708b42dd3eb447b GIT binary patch literal 197 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1SD0tpLGJMDo+>3kcv5PZY|_$br5g}ynncU zn}lxN*IPMaj(XhBm!zu4&aikbkv#Ke{meNHvw-l{FB6M%kL%vw{`c#DcGwEF1;j(M y+5_kBX81Vx0^wVrBmV#Ic;u}FbOjs870e9T>HKx-KiX`8A`G6celF{r5}E)n2uWiA literal 0 HcmV?d00001 diff --git a/fixtures/web-first/animation/chromium/svg-pattern-client-animation/sample-1000000000ns.png b/fixtures/web-first/animation/chromium/svg-pattern-client-animation/sample-1000000000ns.png new file mode 100644 index 0000000000000000000000000000000000000000..d9ee9458066b805fa04066871c5aa383cef32d97 GIT binary patch literal 197 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1SD0tpLGJMDo+>3kcv5PZW(eNcHm(+=>1{- z3kcv5PZW(eNcHm(+=>1{- zv!5 w?f$tXGi8CGI?(d}|79F+Tm~}OK(1hB*zL(*XIvlR2NYrOboFyt=akR{0FBT)e*gdg literal 0 HcmV?d00001 diff --git a/fixtures/web-first/animation/chromium/svg-pattern-client-animation/sample-250000000ns.png b/fixtures/web-first/animation/chromium/svg-pattern-client-animation/sample-250000000ns.png new file mode 100644 index 0000000000000000000000000000000000000000..3c10c8fcd1f1a1dcafa39285cced2fa7564a05c5 GIT binary patch literal 205 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1SD0tpLGJMCQlc~kcv5PZY|_GY{1}pG4IR# zbgk@fS8pbyg-q!>q*~C|Q4uJvw7Ji4+DzN&CplMu;M(Wce^>vXcRSl!$Nu{F|4-xQ z1^7A;4|9JtwAU{-WHbfBt^7bo{Qtki@YqfugAL>wW`;+*gzC;t+4>nM!r3kcv5PZW(eNcHm(+=>1{- zv!5 w?f$tXGi8CGI?(d}|79F+Tm~}OK(1hB*zL(*XIvlR2NYrOboFyt=akR{0FBT)e*gdg literal 0 HcmV?d00001 diff --git a/fixtures/web-first/animation/oracle-bake.json b/fixtures/web-first/animation/oracle-bake.json index 816af223..d48e735f 100644 --- a/fixtures/web-first/animation/oracle-bake.json +++ b/fixtures/web-first/animation/oracle-bake.json @@ -4,10 +4,11 @@ "note": "Independent exact-RGBA oracle; no score or conformance pass claim.", "browser_version": "149.0.7827.55", "platform": "darwin-arm64", - "node_version": "v25.9.0", - "bake_script_sha256": "3583d8693630f9262912cbe6f5013fce23e08aefddabd91b0c2b5616ce8cd20b", + "node_version": "v24.19.0", + "bake_script_sha256": "379bf2870d5a48e9f25d9c2d7cbab05c36d7ff6b46afcaa43ef25ae548e8dde1", + "capture_module_sha256": "15ba5c3156f3ed0bfd0f32b6ad773e1d228c0f678396db08c8de1d972cf5bced", "suite": "cases.json", - "suite_sha256": "bc68405be33a29c192d9fab6ac0ebe96acabacde88fd2a64a4deaed524260cb6", + "suite_sha256": "746a803ebef3848ca06c591b3f74c9657fd83af8f355249cbce0bdba55d080ef", "capture": { "viewport": "per-fixture declared dims (the initial viewport)", "device_scale_factor": 1, @@ -16,7 +17,7 @@ "timezone": "UTC", "omit_background": true, "source_transport": "data-url-from-exact-file-bytes", - "javascript_enabled": true, + "javascript_enabled": false, "network": "all http(s) requests aborted; zero attempted", "target": "root-svg-element", "timeline_control": "pauseAnimations() then setCurrentTime(ns / 1e9)", @@ -264,6 +265,148 @@ "fresh_capture_count": 2 } ] + }, + { + "id": "svg-pattern-client-animation", + "width": 64, + "height": 64, + "probe": "#probe", + "authored_base_x": 4, + "retained_seek_order_ns": [ + 2000000000, + 250000000, + 0, + 3000000000, + 1000000000, + 250000000, + 2000000000, + 0, + 1000000000, + 3000000000 + ], + "retained_seek_count": 10, + "cases": [ + { + "id": "svg-pattern-client-animation/base", + "policy": "base-static-projection", + "time_ns": null, + "source": "svg-pattern-client-base.svg", + "source_sha256": "5e3fdf88545ee659d574c2393984c0f5cd3ee6ce0c5f82aca9d420248dd051de", + "oracle": "chromium/svg-pattern-client-animation/base.png", + "oracle_sha256": "b0a4b1a243ab099ac9f7b50718ba0a4d3e5eab669be708a90e9b9a80ad0df821", + "rgba_sha256": "8c6a7472d85864a7340a7d0585d67ce528074764044e25ac97e362eb911184a4", + "expected_x": 4, + "observed": { + "base_x": 4, + "anim_x": 4, + "bbox_x": 4, + "current_time_seconds": 0, + "animation_element_count": 0, + "animations_paused": true + }, + "fresh_capture_count": 2 + }, + { + "id": "svg-pattern-client-animation/sample-0ns", + "policy": "sample", + "time_ns": 0, + "source": "svg-pattern-client-animation.svg", + "source_sha256": "01e4e7ab527f8e2a4802be60a9085fcc395cd17be5c9b6219686b86418eac759", + "oracle": "chromium/svg-pattern-client-animation/sample-0ns.png", + "oracle_sha256": "b0a4b1a243ab099ac9f7b50718ba0a4d3e5eab669be708a90e9b9a80ad0df821", + "rgba_sha256": "8c6a7472d85864a7340a7d0585d67ce528074764044e25ac97e362eb911184a4", + "expected_x": 8, + "observed": { + "base_x": 4, + "anim_x": 8, + "bbox_x": 8, + "current_time_seconds": 0, + "animation_element_count": 1, + "animations_paused": true + }, + "fresh_capture_count": 2 + }, + { + "id": "svg-pattern-client-animation/sample-250000000ns", + "policy": "sample", + "time_ns": 250000000, + "source": "svg-pattern-client-animation.svg", + "source_sha256": "01e4e7ab527f8e2a4802be60a9085fcc395cd17be5c9b6219686b86418eac759", + "oracle": "chromium/svg-pattern-client-animation/sample-250000000ns.png", + "oracle_sha256": "eea939c5828466b6218678a7ab1452f5a9f9c738b8f869855e922179418e2b7a", + "rgba_sha256": "729812f7c21ea462f28f9c21774d2e3781e1d25e33d941ece95c9ca238fd413f", + "expected_x": 10, + "observed": { + "base_x": 4, + "anim_x": 10, + "bbox_x": 10, + "current_time_seconds": 0.25, + "animation_element_count": 1, + "animations_paused": true + }, + "fresh_capture_count": 2 + }, + { + "id": "svg-pattern-client-animation/sample-1000000000ns", + "policy": "sample", + "time_ns": 1000000000, + "source": "svg-pattern-client-animation.svg", + "source_sha256": "01e4e7ab527f8e2a4802be60a9085fcc395cd17be5c9b6219686b86418eac759", + "oracle": "chromium/svg-pattern-client-animation/sample-1000000000ns.png", + "oracle_sha256": "d1aecfa11e7a5dc4462b7ac35036e6701b0924b611674d2a947d7f493e08c1fb", + "rgba_sha256": "4d682083b0db0b57f7af712a0bebfefbbc62f44c4bc86bb667dfdc957e51c61e", + "expected_x": 16, + "observed": { + "base_x": 4, + "anim_x": 16, + "bbox_x": 16, + "current_time_seconds": 1, + "animation_element_count": 1, + "animations_paused": true + }, + "fresh_capture_count": 2 + }, + { + "id": "svg-pattern-client-animation/sample-2000000000ns", + "policy": "sample", + "time_ns": 2000000000, + "source": "svg-pattern-client-animation.svg", + "source_sha256": "01e4e7ab527f8e2a4802be60a9085fcc395cd17be5c9b6219686b86418eac759", + "oracle": "chromium/svg-pattern-client-animation/sample-2000000000ns.png", + "oracle_sha256": "5cac6aec0b14ddba7c8c01f742523afbdb09748d1a0a7c15e8fe3f4bd65d2119", + "rgba_sha256": "ed77b4b3725b71e2d2918157e5e8908d8c230a1426e8dd7d0cfc9aca98e13ec4", + "expected_x": 24, + "observed": { + "base_x": 4, + "anim_x": 24, + "bbox_x": 24, + "current_time_seconds": 2, + "animation_element_count": 1, + "animations_paused": true + }, + "fresh_capture_count": 2 + }, + { + "id": "svg-pattern-client-animation/sample-3000000000ns", + "policy": "sample", + "time_ns": 3000000000, + "source": "svg-pattern-client-animation.svg", + "source_sha256": "01e4e7ab527f8e2a4802be60a9085fcc395cd17be5c9b6219686b86418eac759", + "oracle": "chromium/svg-pattern-client-animation/sample-3000000000ns.png", + "oracle_sha256": "5cac6aec0b14ddba7c8c01f742523afbdb09748d1a0a7c15e8fe3f4bd65d2119", + "rgba_sha256": "ed77b4b3725b71e2d2918157e5e8908d8c230a1426e8dd7d0cfc9aca98e13ec4", + "expected_x": 24, + "observed": { + "base_x": 4, + "anim_x": 24, + "bbox_x": 24, + "current_time_seconds": 3, + "animation_element_count": 1, + "animations_paused": true + }, + "fresh_capture_count": 2 + } + ] } ] } diff --git a/fixtures/web-first/animation/svg-pattern-client-animation.svg b/fixtures/web-first/animation/svg-pattern-client-animation.svg new file mode 100644 index 00000000..eef2aa97 --- /dev/null +++ b/fixtures/web-first/animation/svg-pattern-client-animation.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/fixtures/web-first/animation/svg-pattern-client-base.svg b/fixtures/web-first/animation/svg-pattern-client-base.svg new file mode 100644 index 00000000..87e133b1 --- /dev/null +++ b/fixtures/web-first/animation/svg-pattern-client-base.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/fixtures/web-first/unsupported/README.md b/fixtures/web-first/unsupported/README.md index 441a6d40..a3553b96 100644 --- a/fixtures/web-first/unsupported/README.md +++ b/fixtures/web-first/unsupported/README.md @@ -47,6 +47,8 @@ The scannable, generated view of this register (beside the baked cells) is | `svg-stroke-width-percentage-precision-alias.svg` | Refuse a computed percentage bucket that can represent distinct Chromium used widths. On a 64×32 user space, `100.00000762939453%` and its adjacent `100.00001525878906%` collapse to the same pinned-Stylo bucket (`0x3f800001`), but their amplified Chromium rasters differ by 16 pixels; the last-finite/first-overflow pair at the extreme exposes the same loss. A non-f32 decimal witness around `57384.265625%` differs by 864 pixels, and non-identity percentage math produces further results after the cascade erases its operation history — even when authored length terms cancel to zero before the pure computed percentage arrives. Choosing one result would therefore be silently wrong for another valid source. Direct ambiguous values and folded percentage math are guarded through presentation attributes, inline style, stylesheets, and inheritance. This valid standard-track class has no independent checklist row, so both `stroke-width` twins remain open under the gridaco/nothing#81 split precedent. General direct positive percentage saturation is separately celled. | | `svg-smil-set-load-active.svg` | A `` on a consumed attribute of an admitted rect. SMIL defaults `begin` to offset `0s`, so Chromium paints the overridden fill at load — the target's authored state never honestly renders. Strict refuses at construction; best-effort skips the target and declares it at the target's stable path. Before this row landed, a Base render painted the authored fill with exit 0 and zero declarations in both admissions — the silent wrong pixel recorded open in the D-N register at the paths rung. | | `svg-smil-animate-transform.svg` | An `` on a ``: the override targets the container, so the whole subtree is the declared hole (best-effort) and strict refuses at construction. | +| `svg-smil-number-precision-alias.svg` | Refuse a valid endpoint decimal whose direct binary32 parse selects the other neighbour from Chromium's CSS-number route. Chromium samples the witness as `1`; the former route sampled the next binary32 value, and an exact transform amplifier changed 48 pixels at maximum channel delta 238. The f64 shadow is only a one-way classifier and never supplies a rendered value (measured, not celled). | +| `svg-smil-number-source-syntax.svg` | Refuse endpoint spellings outside the SVG number grammar before Rust's broader float syntax can admit them. Chromium leaves the authored target at `x=2` for `1.` and `1.e0`, while the former route animated it to `1`; Unicode NBSP and ideographic-space padding exposed the same broad-whitespace bug. Each changed 3,072 pixels at maximum channel delta 238. ASCII SVG whitespace, leading plus, and a leading fractional dot remain admitted controls (measured, not celled). | | `svg-smil-retarget-href.svg` | A `` retarget. href resolves by id, which this slice does not own, so the override cannot be attributed to one skippable element — document-level, both admissions refuse, exactly as `