Skip to content

Latest commit

 

History

History
697 lines (607 loc) · 45.4 KB

File metadata and controls

697 lines (607 loc) · 45.4 KB

n0 ("nothing")

n0 (pronounced "nothing") is a 2D graphics engine — the pipeline (document + immutable effective values) → resolve → drawlist → paint, plus the read tier (query), time-as-data (journal/replay), and the sockets every future optimization plugs into (damage, ident, oracle). It consumes n0-model as a library; host chrome (winit/egui/GL) lives in consumers (n0_dev), never here. The contracts it encodes are catalogued in ../../archive/model-v2/anchor/ENGINE.md (ENG-0…ENG-5, S-1…S-7, archived); each module names the contract it serves.

This is a day-1 skeleton: every contract has a code socket and a guarding test, and the dev shell (../n0_dev) is re-hosted onto it — painting, hit-testing, gestures, and damage all flow through the engine. Growth (incremental resolve, tiles, a broadphase index, and a pinned production text oracle) is deferred to named studies; the sockets are here so that growth is additive.

Run

# the engine's own tests (drawlist, query, journal, replay, damage, ident)
cd crates/n0 && cargo test

# the trace arm must keep compiling
cargo check --features trace

# the re-host gate: replay determinism, differential/cache checks, and the
# host-owned screenshot/timing baselines (the spike owns the golden pixels)
# CI owns the committed baselines; other hosts run the portable checks and
# same-host shot determinism without comparing incomparable environments.
cargo build --release -p n0_dev
cargo run --release --bin gate

# visual proof of the pre-animation effective-value seam
cargo run --bin effective_values_demo -- target/grida-effective-values-demo.png

# Profile 0 source through the latest cumulative renderer
cargo run --release --bin svg_animation_render -- \
  ../../fixtures/test-svg/animation/svg-animation-profile0-demo.svg \
  target/svg-animation-profile0 50 4000

# Profile 1 keyframes and easing (100 fps, 4.5 seconds)
cargo run --release --bin svg_animation_render -- \
  ../../fixtures/test-svg/animation/svg-animation-profile1-keyframes.svg \
  target/svg-animation-profile1-keyframes 100 4500

# Profile 2 replacement sandwich (50 fps, 6.5 seconds)
cargo run --release --bin svg_animation_render -- \
  ../../fixtures/test-svg/animation/svg-animation-profile2-replacement-sandwich.svg \
  target/svg-animation-profile2-replacement-sandwich 50 6500

# Profile 3 additive/accumulative motion mixer (2 fps, 7.5 seconds)
cargo run --release --bin svg_animation_render -- \
  ../../fixtures/test-svg/animation/svg-animation-profile3-motion-mixer.svg \
  target/svg-animation-profile3-motion-mixer 2 7500

# Profile 4 live-underlying effects and typed transforms (20 fps, 6 seconds)
cargo run --release --bin svg_animation_render -- \
  ../../fixtures/test-svg/animation/svg-animation-profile4-transform-showcase.svg \
  target/svg-animation-profile4-transform-showcase 20 6000

# Profile 5 solid-fill color effects (20 fps, 6 seconds)
cargo run --release --bin svg_animation_render -- \
  ../../fixtures/test-svg/animation/svg-animation-profile5-solid-fill-showcase.svg \
  target/svg-animation-profile5-solid-fill-showcase 20 6000

# Profile 6 smooth/discrete path geometry (20 fps, 6 seconds)
cargo run --release --bin svg_animation_render -- \
  ../../fixtures/test-svg/animation/svg-animation-profile6-path-morph-showcase.svg \
  target/svg-animation-profile6-path-morph-showcase 20 6000

SVG Animation Profiles 0–6

The first animation reference path is implemented without adding animation state to the document or painter. A retained SVG source compiles once into an immutable, document-bound AnimationProgram; each explicit SampleTime produces the existing validated PropertyValues, then joins the ordinary resolve, draw-list, query, damage, cache, and checked-paint path. Base and Sample are explicit FrameRequest variants. The cache keys sampled values, not time.

Diagnostic hosts that deliberately track the latest cumulative proving profile share svg_animation_frame: it retains and compiles source once, then exposes separate explicit authored-Base and exact-SampleTime entries. Both reject ignored or erroneous resolver intent before checked paint and return the ordinary FrameProduct. The caller still owns canvas allocation, clearing, I/O, cadence, and encoding. This adapter remains bounded by the direct-shape proving materializer; it is not general SVG import.

The dated status matrix, dependency direction, and per-module responsibility boundaries live in ANIMATION.md. That document is the implementation checkpoint; the WG profile pages remain source-language contracts and intentionally do not describe this crate topology.

Profile 0 is the two-keyframe linear baseline. The cumulative Profile 1 adds ordered values, exact rational keyTimes, and per-segment cubic Bézier keySplines. They lower to one format-neutral scalar-curve primitive; the engine has no SVG-shaped keyframe representation and no parallel endpoint sampler. Exact keyframe and frozen boundaries preserve authored binary32 bits. Cubic inversion is a pinned exact-rational operation, so direct seeks do not depend on playback history or platform easing libraries.

The cumulative Profile 2 admits multiple replacement effects for one target. The SVG frontend orders them by interval begin and then document order; the format-neutral program stores target-grouped low-to-high-priority tracks and samples only the highest active or frozen contributor. remove falls through, freeze remains, and the authored base appears when no effect contributes.

Profile 3 adds independent effect and iteration composition. SVG additive="sum" adds an effect over the lower sandwich; accumulate="sum" adds the curve's terminal keyframe once per completed repeat before sandwich composition. A replacement still cuts lower layers, while higher additions remain. The kernel reads only compatible authored scalar bases, rounds each ordered scalar operation once, fails non-finite geometry atomically, and clamps opacity only after the complete sandwich. It still emits one ordinary PropertyValue per target rather than introducing an animated value model.

Profile 4 expands that kernel with a closed typed effect sum. Scalar lone-to interpolates from the live lower sandwich on every sample and is never a replacement cutoff. Typed translate, scale, and rotate curves interpolate and accumulate their parameters before projecting one sampled affine operation. Transform replacement creates one ordered operation; addition appends to the existing LensOps aggregate. The same effective property then feeds ordinary resolve, query, damage, cache, and paint.

Profile 5 adds straight legacy-sRGB solid-fill color curves. It targets the existing complete ordered Fills: Paints property: replacement emits one solid paint, while additive and lone-to effects require a compatible singleton solid lower value. RGBA channels remain exact and unbounded through easing, repeat accumulation, and the full effect sandwich, then clamp and quantize once to the existing RGBA8 Color. No renderer, draw-list, cache, or document paint model is duplicated for animation.

Profile 6 adds complete PathGeometry replacement without a string mutation channel or animation-only renderer. Compatible non-arc command topology lowers to a typed PathCurve; explicit calcMode="discrete" selects arbitrary valid path values including arcs; an incompatible non-arc from/to pair uses one eased whole-value switch. Source command families remain distinct even after render normalization (H is not L, S is not C, and T is not Q). The sampled artifact passes through the existing path property, resolver, tight bounds, damage, draw-list, cache, and raster path.

Playback clock harness

playback_clock is a compact host-time adapter, not a product player. A caller-owned PlaybackClock maps explicit monotonic HostTime values to the same SampleTime accepted by FrameRequest::Sample. It uses one stable anchor, an explicit closed range, exact rational rate, and separate direction; pause, seek, rate changes, reverse playback, and terminal stopping are deterministic under irregular frame cadence. Playing again at the active terminal is a paused no-op rather than an implicit rewind or loop.

The module depends on no source format, animation program, document, frame, Skia, callback, scheduler, or system clock. A host performs the only composition:

let time = playback.sample_time(host_now)?;
let request = FrameRequest::Sample { program, time };

Looping, autoplay, UI, media synchronization, and continuous-redraw policy are outside this harness. tests/playback_clock.rs tests its pure state machine; tests/animation.rs proves that its output is identical to a direct seek through resolved geometry, query, damage, pixels, and effective-value cache identity.

The native spike supplies one deliberately disposable live host around that boundary:

cd ../n0_dev
cargo run --release -- \
  --play-svg ../n0/../../fixtures/test-svg/animation/svg-animation-profile6-path-morph-showcase.svg

Its separate AnimationApp owns Instant, redraw pacing, controls, resize, GPU presentation, and terminal quiescence. These remain host policy rather than an engine runtime abstraction. Because the proving stack has no compositor yet, its redraw timer temporarily stands in for the display pacing ENG-2.4 assigns to the eventual compositor; only explicit time mapping and animation demand are migration-worthy.

The proving static SVG materializer is deliberately narrow: one SVG-namespace root with positive unitless dimensions, direct rectangles or viewport-bounded paths, solid hexadecimal fills, shape opacity, rectangle radii, no viewBox, and a bounded Profiles 4–6 static transform-list seam. This keeps scalar, solid fill, transform-list, and path-geometry targets identity-mapped into the current model. Unsupported scene structure fails instead of being normalized behind the profile's endpoint contract. Retained dynamic side channels such as <set>, <script>, <style>, and event attributes may still expose the ordinary Base document, but make Sample compilation fail. The production SVG importer is unchanged.

fixtures/test-svg/animation/svg-animation-profile0-boundaries.svg is the exact nanosecond repeat-boundary oracle. ../../fixtures/test-svg/animation/svg-animation-profile0-demo.svg is the 960×540 visual specimen and exercises every admitted property, delayed begins, repeats, remove/freeze, parent targeting, and root-level href targeting.

fixtures/test-svg/animation/svg-animation-profile1-keyframe-boundaries.svg adds uneven key-time, repeat, and freeze boundaries. Its visual sibling ../../fixtures/test-svg/animation/svg-animation-profile1-keyframes.svg aligns linear, shared spline, and independently eased segments against the same stations.

fixtures/test-svg/animation/svg-animation-profile2-sandwich-boundaries.svg isolates base, lower contribution, later-begin replacement, exact-end fallthrough, and frozen lower state on one target. Its three-rail visual sibling ../../fixtures/test-svg/animation/svg-animation-profile2-replacement-sandwich.svg separates the lower effect, temporary higher effect, and composed result.

fixtures/test-svg/animation/svg-animation-profile3-additive-boundaries.svg isolates typed addition and accumulation across repeat, replacement, remove, and freeze boundaries. Its four-lane visual sibling ../../fixtures/test-svg/animation/svg-animation-profile3-motion-mixer.svg separates the cumulative foundation, delayed replacement, persistent/temporary offsets, and result.

fixtures/test-svg/animation/svg-animation-profile4-effects-and-transforms.svg isolates the live lower-sandwich dependency, typed transform interpolation, parameter-level accumulation, and ordered postmultiplication. Its visual sibling ../../fixtures/test-svg/animation/svg-animation-profile4-transform-showcase.svg presents those laws as separate lanes through the same latest-profile renderer and player.

fixtures/test-svg/animation/svg-animation-profile5-solid-fill-boundaries.svg isolates whole-fill replacement, straight alpha/color interpolation, exact additive and repeat-accumulative channels, and live-underlying color. Its visual sibling ../../fixtures/test-svg/animation/svg-animation-profile5-solid-fill-showcase.svg presents uneven spline color keyframes, an additive RGB mixer, and a live transition.

fixtures/test-svg/animation/svg-animation-profile6-path-boundaries.svg isolates absolute/relative compatibility inside one command family, structured spline morphing, explicit discrete arc-bearing geometry, and the H/V versus L automatic fallback boundary. Its visual sibling ../../fixtures/test-svg/animation/svg-animation-profile6-path-morph-showcase.svg presents a circle-to-sparkle cubic motion mark, whole-icon state switches, and a family-mismatch switch through the same latest-profile renderer and player.

The renderer publishes a bounded, zero-padded PNG sequence through a normal-error transaction, plus frames/manifest.json and frames/frames.csv. The JSON records source and compiler identity, viewport, exact cadence, and every frame's SHA-256. A returned failure preserves the prior complete report; the two-rename directory swap does not claim process-crash atomicity. Video assembly stays host tooling:

ffmpeg -hide_banner -loglevel error -y \
  -framerate 50 -start_number 0 \
  -i 'target/svg-animation-profile0/frames/frame-%04d.png' \
  -map_metadata -1 -an -c:v libx264 -preset slow -crf 18 \
  -pix_fmt yuv420p -movflags +faststart \
  -fflags +bitexact -flags:v +bitexact -threads 1 \
  'target/svg-animation-profile0/svg-animation-profile0.mp4'

ffmpeg -hide_banner -loglevel error -y \
  -framerate 50 -start_number 0 \
  -i 'target/svg-animation-profile0/frames/frame-%04d.png' \
  -filter_complex \
  '[0:v]fps=25,scale=640:-2:flags=lanczos,split[g0][g1];[g0]palettegen=max_colors=256:stats_mode=full[p];[g1][p]paletteuse=dither=bayer:bayer_scale=3' \
  -loop 0 -an \
  'target/svg-animation-profile0/svg-animation-profile0.gif'

The PNG sequence is the deterministic sample artifact. MP4/GIF output is a presentation derivative. Explicit browser seeking and the applicable WPT differential corpus remain the open half of the visual/conformance chunk.

Source-neutral glyphless frames

glyphless::compile admits an already-resolved rframe::Frame without constructing an authored n0 document. The backend-free contract retains its opaque identity and provenance in a product-private projection, while n0 owns the compiler, generic-owner drawlist, damage policy, and painter. Glyphless items therefore never borrow or fabricate n0 node identities. The dependency edge consumes the contract-only, backend-free rframe crate (it carries no features and no painter), and n0 has no dependency on the Web producer.

The returned glyphless::FrameProduct is intentionally separate from the ordinary frame::FrameProduct: the ordinary product owns n0-specific resolved and query state that a foreign producer cannot supply honestly. The admitted solid/geometry material is resource-free: compilation takes no PaintCtx, and execution accepts any context after converting its public neutral math2::AffineTransform view inside n0. Ordinary and other resource-bearing products continue to require the exact captured paint environment. Both paths reach the same private painter.

The admitted production slice is frame clipping plus rectangles, ellipses and paths — filled, stroked, or both — whose ordered paint stack has already been normalized by rframe to visible, Normal solid cg paints. A stroke is centred on its geometry and carries width, cap, join and miter limit; where a contour is closed the cap is normalized away, because it is inert there and Skia's thin stroke does not agree. Compilation validates finite geometry and requires each published node bound to equal math2::rect_transform exactly. Compiled products expose an exact frame diff through n0's existing generic damage policy. Preview-cache integration is not admitted or implemented yet. This is a source-neutral chassis seam, not a general SVG importer; source parsing, cascade, animation interpretation, I/O, and clocks remain outside n0.

A resolved ScopeEffect::Blend compiles to one empty-start group layer with the checked blend and optional opacity applied in one restoration. Unit Normal retains isolation. The native authored BeginOpacity operation continues to copy its backdrop and restore arithmetically; it has a different meaning. Blend scopes use the existing opaque owner projection, child coverage union, exact drawlist equality, and complete-frame damage policy, including inside repeating programs. An unchanged group must still replay against a changed earlier backdrop; retained glyphless products reuse immutable commands, never the previously blended pixels. tests/group_blending.rs pins these consumer laws with hand-built frames and exact pixel probes. Those tests do not claim Chromium parity or measured performance.

An optional checked BlendSourceDomain on that operation is complete, already-enclosed source material, not a geometry/damage box or an allocation hint. The first glyphless consumer accepts identity-mapped declarations over untransformed solid/linear rectangles and simple local strokes; it rejects under-enclosing domains, nested effects within the declaration, nonidentity declaration maps, enclosing filter/mask programs, and declarations inside repeating programs. An identity-mapped rectangle with an empty paint stack and no stroke retains its geometry and identity without contributing a draw, coverage, or source extent, so its geometry need not lie inside the declaration. Ordinary finite geometry, exact transformed bounds, and unique-owner validation still apply, including to zero-sized paintless rectangles. This exemption does not admit mapped or nonrectangular paintless geometry; every painted rectangle, including a stroke-only rectangle, must still be enclosed. The glyphless module tests pin these rules and byte-identical changed-view replay. ScopeOpacityGroup can carry the same checked IsolatedSourceDomain fact without attaching an enclosure to the reusable numeric opacity factor. Its glyphless profile additionally accepts co-mapped rectangles and ellipses, including radial and repeating paints, with simple local strokes. A declared opacity source rejects under-enclosure, independently mapped contributors, paths, complex strokes, nested effects, enclosing filter/mask programs and repeating-program placement with an owner-bearing error. Completed groups may still sit inside ordinary opacity or geometric clipping. Both native opacity restoration and the byte-255 Normal promotion consume the declaration; neither widens geometry or reuses a previous source image. tests/opacity_source_domain.rs independently tests this contract, changed-view replay and rejection before destination mutation. These are consumer laws, not browser pixel assertions. Ordinary undeclared scopes keep their existing profile. The declaration targets the containing stream, independently of a scope item's drawing map. Current-view mapping and device enclosure are recalculated for every execution. Unsupported device bounds return an owner-bearing FrameExecutionError::SourceDomain before touching the canvas. There is no source-image cache. Consumer tests distinguish raster identity from painted geometry and prove changed-view replay equals a fresh product.

Multiply and partial-opacity Screen restoration explicitly round byte opacity before scaling source bytes and applying the byte-domain blend. This avoids pinned Skia's differing ARM/x86 low-precision arithmetic and runtime-blender opacity ordering. Construction is preflighted without drawing; failure returns an owner-bearing glyphless::BuildError::Blend. A thread-local cache holds one compiled effect per mode and at most 256 immutable opacity bindings per mode, never destination pixels. Tests execute every opacity byte against integer arithmetic and prove binding reuse equals fresh construction. Byte-255 Normal restoration also uses exact byte source-over to avoid the x86 sprite blitter's separate approximation at partial-alpha edges. This includes accepted near-unit opacity values whose backend byte is 255. An existing ScopeEffect::Opacity in that bucket lowers to the same checked Normal-blend command, retaining its original opacity, owner and one source layer. Lower-byte Normal retains the native isolated opacity path, and unit-opacity Screen remains native. The trace counters include these promoted opacity layers; promotion changes their restore operation, not how many source layers exist.

With the trace feature, n0::trace::sink::drain_blend_layers() drains typed BlendLayerMetrics, separate from duration samples: one aggregate per outermost drawlist execution, including recursive resource recording. It counts blend save calls, observed raster pixel-span bytes and area, peak live observed blend bytes, inaccessible observations, and empty-clip saves. The latter never count the parent surface as a new layer. Skia-internal allocations and later picture playback are not counted; recording/GPU storage may be inaccessible. Preflight recording can produce separate execute aggregates: one frame operation may yield more than one record. Drain before and after the diagnostic frame. The pinned raster accessor reads existing storage but calls notifyPixelsChanged; use an untimed trace-enabled frame, not these instrumented observations as default-build allocation or performance claims. No layer-bounds optimization is applied.

Versioned .n0.xml ingestion

There is deliberately no XML-specific engine API. Draft 0 still has the model crate's pure n0_model::n0_xml::parse(&str) boundary. The retained source-program boundary additionally parses and links Version 1–4 source units, specializes Version 2–4 scalar props, projects Version 3/4 named render slots, retains Version 4 durable occurrence addresses, and materializes the same ordinary Document used everywhere else. A host supplies immutable source snapshots, then passes only that concrete document to frame::render. The engine library performs no filesystem I/O and never reparses a document in the frame loop.

The canonical frame seam assumes that a document reached it through source parsing or the model's shared renderability validation. Its gradient and image preflights deliberately close later facts—resolved paint boxes, pinned-backend arithmetic and shader construction, loaded resources, and the final view—but are not an exhaustive validator for arbitrary hand-built invalid stroke, corner, path, or image-model state.

The local-file host resolves component dependencies from each containing source's canonical base, retains node/use/specialization/slot-projection provenance, and preflights an origin-aware resource manifest. Equal relative image strings from different source units receive different runtime keys without changing the node, layout, paint, or renderer model.

The source boundary materializes the Draft 0 XML-facing property registry: versioned envelope, direct node taxonomy, responsive bindings and constraints, free/flex layout, nested primitive-local children, canonical compact and structured fills, all existing gradient variants, RID-backed image paints, per-paint visibility/opacity/blend modes, and repeatable authored strokes whose independent geometries each own ordered Paints. Their width projects the production StrokeWidth union: one uniform value or four concrete top/right/bottom/left values on containers and rectangles. Containers and rectangles also carry the production-shaped per-corner elliptical radius record and normalized corner smoothing. Text uses canonical font-size plus flat direct-child tspan runs. Those runs materialize as complete UTF-8 byte ranges with numeric weight, normal/italic style, and optional ordered Paints overrides; omission still means node-fill fallback.

path uses the complete SVG path-data grammar in a fixed unit reference box and retains nonzero or evenodd fill identity. Resolution maps its analyzed command stream into the final declared box exactly once. Tight bounds, damage, drawlist fills, and every repeated stroke share that box-mapped artifact; gradients and image paints still use the full declared paint box. Path children remain ordinary box-local content between the parent fill and strokes.

The drawlist and painter execute the same topology: node fill, clipped children, then repeated parent strokes. Lines have no fill or implicit ink; containers receive no invented border. Stroke alignment, caps, joins, miter limits, dashes, rich gradient/image paints, conservative visual bounds, subtree opacity, and descendant-only container clips all remain explicit model and display-list state. Per-side box strokes materialize as independently normalized outer-minus-inner rounded rings; overconsumed inner extents saturate instead of inverting, and dashed rings keep one continuous contour phase. Each edge contributes its own conservative visual outset. A rounded or smoothed container/rectangle projects one shared outline into fill coverage, descendant clipping, and every repeated stroke while non-solid paint coordinates retain the full rectangular paint box. Degenerate paint-box axes use Draft 0's centered one-pixel coordinate fallback. With a host font, resolution uses Skia Paragraph to produce one immutable Arc<TextLayout> after the text node's final width is known. That artifact records its oracle and environment, input width constraint, final assigned box, line-break kinds, UTF-8 cluster starts, semantic font identities, glyph IDs and positions, and logical and ink bounds. Empty source owns one terminal line with default-font metrics but no invented source or ink. The drawlist shares the same Arc with the fill and every stroke and owns the exact per-resolution fonts behind the artifact's local replay keys; paint never reshapes or reconstructs them. Text world bounds start from glyph ink, and damage compares the complete text artifact even when its box is unchanged. Every run paint still uses the resolved full text-node box. Variable fonts receive the authored wght axis; single-face hosts use documented synthetic fallbacks only when needed.

This remains a proving engine rather than a claim that every future RFD area is complete. Current limits are:

  • SVG Animation Profiles 0–6 currently share only the narrow identity-preserving shape materializer described above; Profile 4 adds its bounded static-transform owner seam and Profile 6 adds direct viewport-bounded paths, but general static SVG import, broad browser/WPT differential coverage, product playback and frame scheduling, additional SVG animation elements and value families, and production migration remain separate work;
  • the host-font path is a Skia Paragraph bridge, not the deterministic oracle still open in DEC-4: its font environment identity is process-local, its constraint input is width-only, font fallback is deliberately disabled, and paragraph direction is fixed to LTR. Unresolved glyphs produce an explicit resolver report, but this proving resolver still returns the diagnostic artifact rather than the RFD's final typed-failure API. Complete bidi, source/cluster/caret mapping, paragraph controls, and cross-platform identity remain open. Fontless probes explicitly use stub@lab-0; it emits line metrics but no glyph runs or text pixels;
  • derived group/lens flex-slot growth still needs an explicit slot-versus-geometry model rule;
  • image-paint free transforms, tiling, filters, and quarter-turns are outside Draft 0 XML; default/fallback/required slot policies, durable nested paint/stroke/stop/run identity, full-library validation, and canonical multi-file writing remain future work;
  • the production smooth-corner construction is circular-only; Draft 0 rejects smoothed elliptical radii rather than silently changing their authored geometry, and defines production's per-corner half-short-side cap separately from ordinary rounded-box overlap normalization;
  • production's per-side ring path has no corner-smoothing input and does not honor non-miter join state; Draft 0 therefore rejects nonuniform widths with nonzero smoothing or nondefault join/miter geometry instead of silently dropping authored intent;
  • PaintCtx is the host resource environment for text resolution and image paint. The low-level infallible painter emits no pixels for an unregistered image RID. Checked frame execution instead rejects missing or unsupported image paints and view-dependent noninvertible image sampling matrices before touching the destination canvas; a singular geometry transform remains valid collapsed coverage. A host claiming strict materialization must also preflight filesystem/decode resources; n0_xml_render does so and reports authored plus resolved locations. Its opaque checked PaintEnvironmentKey lets damage and cache observe readiness, font changes, and same-RID byte replacement;
  • the lab's ordered Vec<Stroke> implements the accepted extension, while the production scene/archive model still has one stroke geometry per node.
  • production scene/archive path contracts still expose incompatible raw and canonical box-mapped forms, do not uniformly preserve fill rule, and do not yet round-trip this Draft 0 path contract.

Checked-in Draft 0 files use the canonical grammar; Version 1–4 fixtures use the selected proving grammar of their open RFDs. The minimal consumer fixture and pixel probes live at fixtures/test-n0-xml/cases/nested-rects.n0.xml and tests/n0_xml.rs. ../../fixtures/test-n0-xml/examples/dynamic-slide.n0.xml demonstrates flex, a direct ellipse used as a circle, and primitive/text composition. ../../fixtures/test-n0-xml/examples/rich-fills.n0.xml demonstrates ordered paint stacks, while ../../fixtures/test-n0-xml/examples/rich-strokes.n0.xml demonstrates independent repeated stroke geometry. ../../fixtures/test-n0-xml/examples/source-becomes-surface.n0.xml is the complete editorial showcase: every Draft 0 element and property family, all four gradient variants, image paints, clipping, responsive bindings, and native multi-stroke composition in one scene. ../../fixtures/test-n0-xml/examples/rounded-surfaces.n0.xml concentrates the rounded-box slice: asymmetric circular corners, elliptical axes, continuous smoothing, clipped descendants, rich fills, and repeated strokes. ../../fixtures/test-n0-xml/examples/per-side-strokes.n0.xml demonstrates asymmetric and zero side widths, ordinary elliptical corners, continuous dashes, repeated rings, and rich paints without duplicating scene geometry. ../../fixtures/test-n0-xml/examples/rich-text.n0.xml is the attributed-text specimen: mixed sizes, weights, italic style, solid and gradient run fills, exact whitespace, and derived UTF-8 ranges in one inspectable string. fixtures/test-n0-xml/cases/unit-path.n0.xml is the focused path oracle: an even-odd unit path with ordered fills and repeated strokes. The complete editorial showcase also includes a nested, rich-painted path specimen. fixtures/test-n0-xml/cases/component-program/entry.n0.xml and its sibling component library are the focused Version 2 source-program oracle: one external boxed component, two independently specialized uses, ordinary-scene lowering, and component-blind pixel output. fixtures/test-n0-xml/cases/slot-program/entry.n0.xml and its sibling Version 3 component library are the focused named-slot oracle: definition-owned header/footer order, caller-owned projected roots, one empty projection, ordinary-scene lowering, and component-blind interior pixel probes. fixtures/test-n0-xml/cases/durable-addressing.n0.xml is the Version 4 identity oracle: every authored ordinary node has one owner/member/use-occurrence address, and the engine integration compiles one occurrence to an arena-scoped typed property target before evaluating it through the ordinary frame pipeline. fixtures/test-n0-xml/examples/social-feed/entry.n0.xml and post-card.n0.xml form the real-world Version 3 showcase: one viewport-spanning, breakpoint-free scene uses center/end/span bindings for its rail, stories, timeline, suggestions, and message dock. Reusable stories and suggestions plus two post instances share one complete fixed-size social-post shell while caller-owned media trees project original checked-in image paints through the same named slot. Compact viewports therefore demonstrate continuous anchor response and honest clipping, not an unimplemented breakpoint system.

The thin host binary renders a file to PNG. It defaults to a 1280x720 viewport; pass explicit positive dimensions for responsive inputs:

cargo run --bin n0_xml_render -- \
  ../../fixtures/test-n0-xml/examples/dynamic-slide.n0.xml target/n0-xml-dynamic-slide.png

cargo run --bin n0_xml_render -- \
  ../../fixtures/test-n0-xml/examples/rich-fills.n0.xml target/n0-xml-rich-fills.png 720 300

cargo run --bin n0_xml_render -- \
  ../../fixtures/test-n0-xml/examples/rich-strokes.n0.xml target/n0-xml-rich-strokes.png 720 320

cargo run --bin n0_xml_render -- \
  ../../fixtures/test-n0-xml/examples/source-becomes-surface.n0.xml \
  target/n0-xml-source-becomes-surface.png 1600 1000

cargo run --bin n0_xml_render -- \
  ../../fixtures/test-n0-xml/examples/rounded-surfaces.n0.xml \
  target/n0-xml-rounded-surfaces.png 1440 900

cargo run --bin n0_xml_render -- \
  ../../fixtures/test-n0-xml/examples/per-side-strokes.n0.xml \
  target/n0-xml-per-side-strokes.png 1200 760

cargo run --bin n0_xml_render -- \
  ../../fixtures/test-n0-xml/examples/rich-text.n0.xml \
  target/n0-xml-rich-text.png 1280 800

cargo run --bin n0_xml_render -- \
  ../../fixtures/test-n0-xml/cases/nested-rects.n0.xml target/n0-xml-nested-rects.png 96 80

cargo run --bin n0_xml_render -- \
  ../../fixtures/test-n0-xml/cases/unit-path.n0.xml target/n0-xml-unit-path.png 96 80

cargo run --bin n0_xml_render -- \
  ../../fixtures/test-n0-xml/cases/component-program/entry.n0.xml \
  target/n0-xml-component-program.png 96 40

cargo run --bin n0_xml_render -- \
  ../../fixtures/test-n0-xml/cases/slot-program/entry.n0.xml \
  target/n0-xml-slot-program.png 112 48

cargo run --bin n0_xml_render -- \
  ../../fixtures/test-n0-xml/examples/social-feed/entry.n0.xml \
  target/n0-xml-social-feed-desktop.png 1920 1080

# Diagnostic viewport sweep (these renders are not reftest or golden oracles).
mkdir -p target/social-feed-responsive
for size in 1920x1080 1440x900 1280x800 1024x768 768x1024 390x844; do
  width=${size%x*}
  height=${size#*x}
  cargo run --quiet --bin n0_xml_render -- \
    ../../fixtures/test-n0-xml/examples/social-feed/entry.n0.xml \
    "target/social-feed-responsive/social-feed@${size}.png" \
    "$width" "$height"
done

The CLI owns filesystem access, resource bases, image decoding, a platform-default typeface, the white raster background, and PNG encoding. Relative component references and image RIDs resolve against the source unit that authored them. Every visible image referenced by a node fill, run fill, or stroke is decoded before the first frame; missing or invalid resources fail with authored source and resolved location. The host materializes the source program exactly once and renders its ordinary document through frame::render. It refuses resolver error/ignored reports instead of writing a fallback image; replay remains on its existing wire contract.

This proving binary is a trusted local-file host, not a sandbox. It follows absolute paths and .. segments supplied by the source. Locations are decoded XML path strings rather than file URIs, so percent escapes are not decoded. Any application or server host must supply its own capability root, symlink and network policy, and byte/decode limits.

How the engine proves it is fast (the four measurement axes — automated work & correctness, the auxiliary human-in-the-loop feel channel, and the software-unmeasurable input→photon limit) is its own doctrine: MEASURE.md.

How the engine stores its data — each memory/data-layout detail decided against verified browser prior art (cc/Blink/Stylo/Skia), validity-tagged ALIGNED / ADOPTED / SOCKET — is DATA-MODEL.md.

The implemented reference sampler contract—explicit time into existing PropertyValues without mutating authored state—is: ANIMATION.md.

The implemented, strictly pre-animation identity/value/frame/query/damage/cache contract is EFFECTIVE-VALUES.md.

Contract → module → guarding test

concern module contract guarding test
stage purity + the oracle law (whole pipeline) ENG-0 the gate's differential + determinism runs
versioned source consumer seam link → frame ENG-0 / S-2 tests/n0_xml.rs, tests/n0_xml_source.rs, tests/n0_xml_slots.rs, tests/n0_xml_social_feed.rs, tests/paints.rs, tests/strokes.rs, tests/rectangular_strokes.rs, tests/text.rs, tests/corners.rs, tests/paths.rs
effective property values model → frame ENG-0/2/3 tests/values.rs (empty equivalence · layout/transform · paint · bounds · visibility · query · pixels)
explicit-time animation SVG → values → frame/cache ANIMATION ../n0-model/tests/animation.rs, ../n0-model/tests/svg_animation.rs, tests/animation.rs, tests/svg_animation_frame.rs (time · exact interpolation · strict compile · Base/Sample · strict host adapter · query · damage · cache · pixels)
host-to-document time mapping playback_clock.rs ANIMATION tests/playback_clock.rs, tests/animation.rs (virtual time · controls · cadence independence · endpoint behavior · direct-seek equality)
drawlist (pure, diffable projection) drawlist.rs ENG-2.1 tests/drawlist.rs (order · pruning · color · verbatim world · determinism)
text shaping + shared glyph layout text_layout.rs ENG-4.1/4.5 ../n0-model/tests/text_layout.rs, tests/text.rs
raster executor paint.rs ENG-2.1 tests/paints.rs, tests/strokes.rs, tests/rectangular_strokes.rs, tests/text.rs, tests/corners.rs, tests/paths.rs (pixel probes)
one frame entry frame.rs ENG-2.4 tests/frame.rs (checked paint environment) · spike live loop · gate
source-neutral glyphless frame entry glyphless.rs vector join module tests (independent input · exact private material/raster · resource-free execution · exact bounds · shared damage policy · explicit refusal)
damage as data damage.rs ENG-2.2 tests/damage.rs, tests/values.rs, tests/cache.rs (geometry · paint-only · opacity · painter order · environment · covering bounds)
spatial read tier query.rs ENG-3 tests/query.rs (hit_point ≡ pick · retained traversal/clip snapshot)
journal (op-log) journal.rs ENG-5.1 tests/journal.rs
replay (corpus, determinism) replay.rs ENG-5.2/5.3 tests/replay.rs + fixtures/n0-replay/*.replay via the gate
cache identity ident.rs ENG-2.3/1.4 tests/ident.rs, tests/cache.rs (arena + slot + generation · exact values · paint environment · document replacement)
oracle version tags oracle.rs ENG-4.2 the .replay header
gated observability trace.rs S-6 cargo check --features trace
the rig bin/gate.rs ENG-0.2 / S-5 it is the gate

The model-crate side of the setup lives in ../n0-model: the typed Op + apply dispatcher + DirtyClass (ops.rs), the arena-scoped per-slot generation identity (model.rs), the closed property registry and immutable ValueView (properties.rs), the non-panicking Resolved opt accessors (resolve.rs), and the optional serde feature (the op-log wire) — each additive, with the full lab suite green throughout.

The re-host, concretely

The spike's scene painter is deleted; it calls frame::resolve_and_build then checked FrameProduct::execute. The lower-level drawlist::build_glyphless_unchecked, paint::execute_unchecked, and paint::raster_to_bytes_unchecked entries are reserved for deterministic structural probes and internal retained-list replay. Live pick, hover, handles, and gestures read the frame's resolved traversal and effective clip snapshot; spatial queries cannot accept a second document or value view. All gesture ops go through apply and are recorded in the journal (undo stays document snapshots — ENG-5.5). --record writes .replay corpus files; the panel shows the per-frame damage count.

The screenshot oracle was explicitly rebaselined after accepting three historical semantic corrections: frames no longer receive invented ink, authored parent strokes paint after children, and text uses shaped metrics and positioned-glyph replay. The shot paint context loads the repository's bundled Inter face, so host font configuration cannot perturb its four outputs. Raster-stack and timing variance still make those outputs and budgets host-owned: the committed owner is named in rig/baselines.json, and CI requires that identity before it may compare or bless them. A non-owner local run renders every shot twice to prove same-host determinism and skips timing comparison; replay and the byte-level differential run everywhere. A baseline replacement is an explicit CI-produced change with etiology, never an automatic local update.

Scope fence (named, not silent)

Skia stays the rasterizer (the engine is the architecture above it). Not an ECS — the arena/SOA is a storage layout, not a component model. Deferred to studies, each behind a socket that is already here: incremental resolve (OS-1a/1b — DirtyClass exists, the engine ignores it and full-resolves) · tiles / partial repaint (OS-2a — damage is data only) · layer promotion (OS-2b — re-measure the legacy finding) · broadphase BVH (OS-3a/3b — behind query) · pinned text oracle and complete fallback/bidi/caret mapping (OS-4a / DEC-4) · pathops-in-measure (OS-4b / DEC-6) · CRDT / cross-session replay (OS-5b/5c — walled on stable ids, a.md §12).