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.
# 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 6000The 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 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.svgIts 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.
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.
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;
PaintCtxis 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_renderdoes so and reports authored plus resolved locations. Its opaque checkedPaintEnvironmentKeylets 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"
doneThe 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.
| 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 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.
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).