Noon architecture and roadmap#
Status#
This document is the single authoritative architecture and roadmap for Noon.
Noon is a greenfield project. There is no requirement to preserve legacy Noon APIs, internal scene models, wire formats, compatibility aliases, migration adapters, or crate boundaries. If an old abstraction conflicts with this document, remove it rather than adapting around it. Git history is the archive.
Detailed subsystem documents may explain an implementation, test strategy, or compatibility behavior, but they do not define a second architecture or roadmap.
Read this document in three modes:
- Normative architecture defines ownership, interfaces, ordering, publication, locality, and lifetime rules.
- Current implementation callouts describe where those responsibilities live today without creating a second target architecture.
- Roadmap status is intentionally coarse; mutable task inventories and final qualification evidence live in the owning phase and follow-up issues linked below.
1. Product target#
Noon is a high-performance animation and interactive graphics system with:
- a first-class idiomatic Rust authoring API that can run end-to-end in a native Rust process;
- the same Rust-authored scene semantics runnable as WASM in a browser without changing the engine architecture;
- a built-in renderer/runtime shared by native and web targets;
- Manim-compatible Python authoring for supported common 2D behavior as a wrapper over shared Rust semantics;
- optional future JavaScript/TypeScript authoring as another wrapper over shared Rust semantics;
- a live-scene programming model in which script commands, deterministic playback, native input/reactive behavior, editor actions and explicit host callbacks operate on the same semantic scene rather than separate interactive/script scene engines;
- deterministic offline and realtime execution wherever semantics are deterministic;
- native reactive interaction without requiring Python on the frame path;
- explicit support for arbitrary host-language callbacks when user code genuinely requires them;
- retained, incremental GPU rendering that scales with changed and visible work rather than total scene size;
- aggressive data-oriented execution/render specialization below the semantic layer without exposing a general-purpose ECS, game-engine scheduler or renderer scene graph as authored truth.
The authoring experience should feel like one continuously live scene. The implementation is deliberately not one continuously traversed host-language object graph: static, timeline, native-reactive and host-dynamic behavior are separated and specialized below the semantic boundary so large mostly-static scenes do not inherit the cost of their most dynamic feature.
Host-language execution invariant#
Noon unifies scene semantics and engine execution, not source-language execution.
Rust, Python and future frontends are allowed to have fundamentally different host execution models:
A Rust application is ahead-of-time compiled. It may create, query and mutate a live Noon scene at runtime through code paths that were compiled ahead of time, but the live-scene model does not imply that arbitrary new Rust source can be interpreted inside the running process. Arbitrary Rust source changes require an explicit mechanism such as recompilation/hot reload, a plugin boundary, or a separate runtime-editable declarative language.
Python may remain an interpreter-driven sequential authoring continuation. It may issue semantic operations, wait for a segment-completion barrier, inspect the resulting live state, execute more arbitrary Python, and then issue additional semantic operations. That is a host-language behavior, not a second scene/runtime architecture.
Editor, hot-reload and future live-design inputs may be declarative or command-oriented. They converge on the same semantic mutation vocabulary rather than pretending to execute as Rust source.
The shared contract is therefore:
host-language execution
|
v
shared Rust semantic operations
|
v
Semantic Scene
|
incremental lowering
|
v
Execution Plan revision
|
v
Runtime -> Renderer
The Semantic Scene remains alive and authoritative across execution-plan revisions. An Execution Plan is a derived, replaceable specialization of the currently published semantics; it is not a compiled representation of the entire future host program.
Direct Rust execution invariant#
Native Rust and Rust compiled to WASM use the same typed engine path; only the platform shell changes:
The shared path is Rust API -> Semantic Scene -> Execution Plan -> Runtime -> Renderer. When those layers share one process or one WASM execution context, every boundary is typed and in memory.
Native host#
A native Rust application can author, lower, execute, and render without Python, JavaScript, WASM, a browser runtime, JSON, serialization/deserialization, or a host-language bridge between engine layers. noon-native owns window/event-loop, surface, input, acquire/submit/present, and recovery mechanics at the platform edge.
An explicit finite ordinary Rust source may instead use noon-export's blocking
host. It runs the source once through the existing BlockingContinuation relay,
starting bounded retention before the first source edit. Engine-owned segment
endpoints and completion admit retained publications; no display receipt or host
clock is invented. Required callbacks/input and unsupported replay domains are
checked before headless advancement. Only the finished original Scene/runtime
owner may transfer to sealed-history frame sampling and the native file sink.
Source-operation/boundary budgets and cooperative cancellation bound host calls;
arbitrary Rust computation between calls is not preemptible. Retiring the source
capability precedes handoff, failure or unwind; playback never reexecutes source.
Browser/WASM host#
A Rust application compiled to WASM uses the same semantic, compiler, runtime, and renderer responsibilities in one WASM context. JavaScript may bootstrap the module, supply the canvas, and provide browser lifecycle glue, but it does not receive and re-send scene/runtime state between Rust layers. A browser target by itself is not a transport boundary.
Serialized transport is justified only at a real external or cross-context boundary, for example the current Python authoring context sending derived resources/deltas to a separate render owner; see section 11.
Python and JavaScript/TypeScript are optional language adapters over the same Rust semantic operations. They are not required components of the Rust engine, runtime, or renderer.
The optional noon-python CPython extension shares the Python facade with Pyodide
through a thin host selector. Native calls and callback values remain typed in
process. Deterministic source preparation may retain the existing bounded Rust
history and transfer its exact scene/session pair to the native viewer or GPU
capture host; playback never reruns Python source. Required Python callbacks use
the existing transactional sampled driver and cannot be promoted to prepared
viewer playback without recorded admission. Finite native CPython MP4 export
consumes that same sealed owner through the shared ExportFramePolicy, native
capture and FFmpeg destination transaction; retained-history audio and unrecorded
callbacks/input remain rejected. Finite 3D inputs, camera profiles,
world motion and spatial domains use the same Rust operations as WASM. This native
profile does not qualify the complete gallery, audio bindings, or physical performance;
remaining frontend breadth stays with #2015 and #1874's conformance contract.
Manim Community v0.21.x is the compatibility oracle for supported common 2D Python behavior. Compatibility is a semantic/API goal, not an implementation constraint. Noon does not copy Manim's renderer, Python scene engine, internal point representation, or per-frame execution model.
2. Architecture in one picture#
D2 source · Domain projections · Revision/lifetime model · Locality propagation · Callback transaction · Current crate ownership.
Reading the view. Solid arrows follow scene/execution data from left to right; dashed arrows show platform services. The two lower boxes are responsibilities of each native/browser host, not additional engine stages or two new crates. This is a component/data view, not a Cargo dependency graph, worker deployment map or a complete call graph. Live-mutation feedback and callback ordering are expanded in the focused views below instead of adding long return arrows to this overview.
| Component | Composition and interface |
|---|---|
| Authoring | Rust Scene/Mobject APIs and Python/WASM handles invoke shared semantic operations. Frontends provide language syntax and argument conversion. |
| Semantic scene | SemanticStore holds object/family identity, membership, authored transforms/paint, animation trees, signals/updaters and content-resource references. Lowering reads these declarations without replacing their authored identity. |
| Lowering | noon-compile derives the execution projection, slot mappings, tracks and resource lookups, and prepares local publication changes. CompiledScene labels the main compiled-data handoff; it is not the entire future host program. |
| Runtime + session | ExecutionSession coordinates a SceneInstance, identity mappings, spatial indexing and segment/callback gates. The instance contains compiled data, evaluation machinery and effective FrameState. |
| Renderer | noon-render-wgpu consumes published state and changes while retaining instance buffers, geometry meshes, glyph resources and painter-ordered draw work. |
The publication interface. FrameEpoch + changes abbreviates the
RendererPublication boundary: a borrowed frame, accumulated changes, painter
order and immutable resource lookups in direct Rust execution. FrameEpoch
identifies a coherent published effective state and references its compatible
SceneRevision/ExecutionRevision; it is neither the delta payload itself nor an
extra processing stage. A genuine Python worker boundary transports derived
output instead. The direct native and single-context WASM paths remain typed
in-process.
Platform integration. The host loop supplies normalized input and frame
wake/tick delivery; the runtime retains authored-time and completion policy. The
surface adapter supplies device/surface access and handles acquisition,
submission, presentation and recovery. These are two aspects of the same host
integration (noon-native, or noon-web plus browser glue). Their placement
beneath runtime and renderer expresses the service connection, not ownership of
engine state. Actual worker placement is shown in Browser topology.
Why these boundaries exist. Authored structure preserves identities and relationships while lowering selects an execution representation. Active drivers can update effective values without rewriting authored declarations every frame; rendering can reuse geometry/text residency when only properties change. Persistent edits still pass through staged semantic preparation and coherent publication. This separates language ergonomics, execution dependencies and GPU lifetime without turning each representation into another authoring model.
There are four engine layers and exactly one authority at each layer:
- Semantic Scene — what the program means.
- Execution Plan — the cheapest representation that preserves that meaning.
- Runtime — the current execution state.
- Renderer — a projection of runtime state into retained GPU resources and draw work.
Platform hosts are integration shells around those layers. They own window/canvas/surface/event-loop/presentation mechanics, not semantic or runtime truth.
The Rust public API is the first-class native authoring API for these layers. Python and future JS/TS adapt language syntax and host callbacks onto the same Rust semantic operations; they do not define separate engine layers. This shared semantic boundary does not require the host languages above it to share a source-code execution mechanism.
Serialization is not a fifth scene model. It is an optional codec around one of these representations for explicit external boundaries such as export/import, debugging, tests, persistence, or unavoidable cross-context transport.
Normal in-process engine boundaries are typed Rust boundaries. Rust API -> Semantic Scene -> Execution Plan -> Runtime -> Renderer must not serialize to JSON or any other wire representation as part of ordinary native authoring, lowering, execution, mutation, or rendering. The same rule applies when the Rust engine is compiled to WASM and all layers execute in one browser context.
Common Rust input and interaction seam#
Platform input and authored interaction declarations converge on a common typed Rust seam before interaction policy or engine execution. Browser DOM events, native window-system events, embedded/platform adapters, tests, and replay are collectors only; Python, JavaScript/TypeScript, and other language frontends are authoring adapters only. Neither platform nor authoring language owns interaction semantics.
The canonical flow is:
platform collectors
browser DOM / native host / embedded / test-replay
|
v
typed Rust InputIngress
|
+--> sampled state ------> native reactive inputs
|
+--> ordered occurrences -> InteractiveSession
|
+--> spatial candidates
+--> precise hit testing
+--> trigger synthesis
+--> hover/selection/capture
|
v
interaction binding
|
v
action
+-----------+-----------+
| | |
runtime driver signal host callback
| | when required
+-----------+-----------+
|
v
ExecutionSession
The normalized Rust input occurrence must carry the information needed to interpret the occurrence coherently, including source/pointer identity where applicable, position for pointer occurrences, button/modifier context, ingress sequence, and compatible scene/view publication context. Sampled latest-value state may still coalesce by exact source. Coalescing must never retroactively change the position or identity of an already ordered press/release/click occurrence.
The platform adapter may perform platform-only mechanics such as DOM pointer capture, coordinate conversion, focus handling, and event-listener lifetime, but it does not choose scene targets or actions. Browser and native adapters must lower equivalent physical input to equivalent Rust input semantics.
Interaction declarations are language-neutral authored semantics. Rust, Python and future JS/TS wrappers may expose different syntax for declarations such as click -> highlight, but they lower to the same semantic trigger/action binding. Common native actions execute entirely in Rust after authoring; arbitrary host-language callbacks remain an explicit slow/compatibility path rather than the implementation of ordinary click, hover or drag behavior.
Keep these domains distinct:
- Input occurrence — normalized platform-independent Rust input.
- Trigger — scene interpretation such as click, hover enter/leave, drag start/update/end, or key activation.
- Interaction binding — authored mapping from a target/trigger to an action; this belongs to Semantic Scene truth.
- Action — canonical engine behavior such as session highlight, effective property write, signal update, animation start, or explicit host callback.
- InteractiveSession state — transient hovered/selected/captured targets, active gesture/tool, driver ownership, drag references and overlays; this is not authored scene content.
Hit testing uses the execution-owned spatial index as broad phase and performs precise tests only on returned candidates in painter-correct order. No frontend, platform adapter, or renderer may create a second authoritative picking index. Picking observes one coherent effective publication so moving/animated objects are targeted where the user sees them.
Selection/editor highlighting is a session overlay and must not mutate the authored object, family order, compatibility output or serialized scene. A scene-authored click action that changes style, signals or animation is different: it is an authored interaction binding whose action executes through the normal Rust runtime/semantic contracts.
The initial authored click -> Indicate action keeps one optional declaration on its object and projects it into the session's existing pointer lane. It reuses ordinary Rust Indicate lowering and interpolation, with at most one active invocation per target; additional clicks during that invocation do not restart or queue another effect. A platform monotonic timestamp advances only these effective values; it does not advance authored time or append replay tracks. Completion restores only the captured channels owned by the effect. Unrelated scripted animation may continue, including disjoint channels on the same object. A conflicting authored/native channel prevents activation, or supersedes an effect already running; the click effect never takes over that channel or later restores a stale value. Seek or a superseding authored publication retires the invocation. The first supported targets are filled analytic circles and rectangles without signal drivers. Pending host callbacks, effective-driver targets and family animation domains have conservative admission barriers. These barriers suppress the effect for that occurrence rather than queueing it for later. Platforms schedule a wake from Rust-owned activity and return to idle when it settles.
on_click(target, GlowPulse(...)) uses that same object-owned action, input admission, invocation table and monotonic interaction clock. Registration stores parameters without creating a glow. Activation captures the current effective intensity and exact canonical attachment generation; an absent attachment is enrolled with zero intensity in the existing prepared semantic/runtime publication before the input is acknowledged. The invocation owns only intensity. An explicit nonempty persistent edit adopts its temporary attachment, including a value-equal write; intensity edits supersede the invocation while color/radius edits preserve its current intensity. Completion or an admitted seek removes only an unadopted exact temporary generation. Preparation failure leaves input metadata, ownership and attachment state unchanged. A private invocation revision rejects stale prepared work even when a neutral pulse has produced no visible frame change. No authored replay track or frontend-owned lifecycle is introduced.
Static native and direct Rust/WASM sources consume SceneExecution, which retains the original Scene and its installed ExecutionSession. Mutation-bearing input, ticks and seek borrow that canonical store/session pair; the execution session does not mirror or own a second semantic arena. The worker adapter consumes the same checked pair across its existing language/context boundary. A strictly validated neutral-enrollment/exact-retirement publication can preserve an already immutable plan's playback admission as its revision advances; it cannot restore admission lost to an ordinary edit. Unrecorded structural input invalidates open replay retention, and the existing sealed-history, audio and scene-control mutation gates remain explicit. Raw storeless seek refuses to discard an invocation whose semantic attachment still needs retirement.
Genuine source release retires input-owned state through that same paired session before releasing its owner. Semantic handles may retain the arena after a source ends, so dropping a runtime alone cannot clean up a temporary attachment. SceneExecution and LiveProgram expose paired retire_execution_source; native host exits call it, and direct browser hosts call retireExecutionSource before freeing the renderer. Renderer recovery returns and re-leases the same player without retiring its invocation. The worker's retireExecutionSource boundary runs before a released context leaves the existing registry. Aborting an incomplete continuation instead uses discardExecutionPlayer: it validates the outstanding lease, retires input state, and only then drops the player and marks its canonical owner unstarted. It does not return or complete the abandoned continuation. Pending callbacks must first use their existing interruption path; retirement does not bypass that barrier. A rejected consuming exit retains the exact player in its existing takePlayer recovery capability, and the endpoint keeps its ports and context for an explicit retry while halting progression. A stale returned owner with active input cannot be silently replaced. No repair seek, store-only cleanup, implicit Drop mutation, or additional clock is introduced.
Scene playback controls use that same optional object-owned click action, semantic identity, picking and input transaction. A successful input publication may carry one typed viewer command; it does not seek or start audio inside the transaction. The host applies the request afterward through its existing admitted play/pause, section-seek and viewer-audio operations. These controls do not create authored replay tracks or change authored gains. Finite immutable direct Rust/WASM and native Rust execution support this dispatch; live publication, retained source history and hosts without control dispatch reject it explicitly. The shared execution owner admits native immutable playback separately from its finite-duration observation, excluding external native input routes and effective-driver owners. A browser device unlock may retain one pending request qualified by its existing input sequence and publication; superseding controls and lifecycle cancellation invalidate it. This is platform completion bookkeeping, not another input queue or playback clock. The temporary profile gates are owned by #2263.
Python authored audio is supported for complete immutable result = Scene() handoffs and for deterministic sources admitted by the prepared-prefix proof and history contract. Both profiles keep the authoring worker as the sole runtime and playback-clock owner. The prefix reader retains the original immutable audio schedule and PCM resources; the Window receives only a bounded resource bundle and Rust-authored clock/buffering/authorization observations. The disposable Web Audio output may schedule only through the currently admitted prefix boundary and extends or resumes from later Rust observations. It contains no mutable scene or playhead. Accepted controls wait for their matching retained picture publication, then carry one worker clock sample and host timestamp. The output uses the shared Rust clip-phase/envelope query, including transport age, without driving execution from device time. Preparation is silent, explicit browser activation enables sound, and superseded commands or platform interruption retire output. The retained schedule is fixed for the execution session: later audio additions, replacements or removals remain rejected. Host playback controls and viewer mute/volume are available for these profiles; opaque or callback-driven source continuation and scene-authored worker click-control dispatch retain their explicit gates. This playback support does not expand finite file export beyond the existing completed immutable worker session or add completed-history export; those remain under #1896/#2263.
Finite native Rust audio retains the compiled immutable clip/resource closure through a typed shared mixer preparation. This derived output object owns bounded scratch and shared PCM references, never a Runtime, scene identity, or playback clock. Device callbacks reuse the same mixing math as export without allocating, decoding, locking, or evaluating the scene. Native device dependencies remain optional in noon-native. The first completed retained picture admits output; the device's first predicted playback observation supplies the wall anchor used by the existing native RealtimeClock. A contiguous integer PCM offset is output-buffer bookkeeping. Timestamp observations detect discontinuities but do not advance authored execution. Viewer pause observes elapsed time even during a sleeping authored wait; seek uses the existing session evaluator and preserves play/pause intent. A replacement output stays silent until its target picture and the preceding bounded release complete. At most one current and one releasing stream exist; repeated seeks cannot extend the original release or accumulate tails. Transport and viewer gain ramps are disposable device state, separate from authored gains and export. Suspension holds authored time and retires both streams; resume prepares a new stream behind the same picture barrier. Device loss, underrun, or excessive timing disagreement stops the run explicitly. Completion retains output through predicted playback of the final samples. Physical display/DAC timing remains a platform estimate, not a sample-accurate hardware guarantee. Source continuations, retained history, native CPython, and broader input/effective-driver profiles remain gated by #2263.
Finite Python file export borrows that same paused immutable worker session. Shared ExportFramePolicy owns prefix, output, completion and frozen-hold samples; the browser host waits for matching renderer presentation and copies only selected RGBA output. The shared mixer supplies bounded PCM blocks on the exact rational output grid. Across the genuine browser/process boundary, a prepared media capture carries pixels, PCM and publication receipts, never a serialized scene or a reconstructed execution owner. The native importer validates complete policy receipts, lengths, revisions, PCM and endpoint before the existing MP4 sink publishes output. It uses the same encoder, timing finalization and destination owner as native Rust export. This does not establish an in-process native CPython binding or native device playback.
Inspection navigation is a constant-size session adjustment composed with the current effective camera in shared Rust. Rendering, visibility and picking consume that same composed view, qualified by a monotonic inspection revision and the existing displayed-frame receipt. A wheel action changes neither the authored camera nor authored time; it can coexist with an independent object animation. Hosts opt in and normalize platform wheel units, while stale receipts and excess outstanding input are rejected explicitly. New sessions reset inspection; same-session seeks preserve it. This viewer adjustment is excluded from ordinary authored export and replay.
Pointer capture has split ownership: the platform shell owns OS/DOM capture mechanics, while InteractiveSession owns the semantic captured target and gesture lifecycle. Release, cancellation, lost platform capture, target retirement/replacement, seek/reload policy and stale generations must terminate or reconcile the gesture deterministically.
Live-session control plane#
A continuously live authoring experience requires coordination, but coordination is not another state authority. ExecutionSession/LiveProgram may order script continuations, wake/sleep, input delivery, callback barriers, revision checks, and publication receipts while semantic truth stays in the Semantic Scene and effective time-varying truth stays in the Runtime.
The host-continuation view shows how different host execution models converge on the same logical completion contract. The live-publication view shows how staged authored/effective work becomes one coherent publication, and the callback transaction view shows the ordered host barrier. These focused views replace a separate control-plane diagram that otherwise duplicated the same relationships.
play()/wait()-class operations are logical segment-completion barriers, not necessarily blocking function calls and not exclusive interaction modes. Python may suspend its authoring worker; native Rust may drive/await compiled control flow; browser/WASM code yields rather than blocking the event loop.
3. Semantic Scene#
The Semantic Scene is the only authored scene model.
It is mutable, hierarchical, language-neutral, and expressive enough to represent author intent before optimization.
It owns:
- stable generational node identity;
- detached versus scene-owned objects;
- source identity for hot reload/reconciliation;
- content: geometry, text, images, future meshes and other resources;
- high-precision semantic transforms and styles;
- family/group membership and ordering, including aliasing where Manim semantics require it;
- bounds and layout semantics;
- object lifecycle and scene membership;
- target-state animation semantics;
- animation trees, options, defaults, composition and scheduling intent;
- signals, trackers, bindings and derived expressions;
- updater and event registrations;
- host callback slots;
- camera semantics;
- mutations that change authored structure or properties.
Language wrappers hold stable handles into this scene. They do not own a parallel scene representation.
Required semantic identity model#
Use one scene-global generational NodeId (or equivalent) for semantic objects/families. Execution indices, GPU indices, resource handles and transport IDs are derived identities and must never become semantic authority.
Required value model#
Semantic values must not be constrained by the current 2D/f32 renderer. The semantic layer should use the representation needed by the product direction, including 3D-capable/high-precision transforms where appropriate, and lower explicitly to compact execution/render values.
Do not maintain permanent parallel legacy and semantic transform/style/object models.
Authored/base state and effective runtime state#
The Semantic Scene owns authored/base state: the persistent authored value, declaration, relationship or resource reference from which execution is derived.
The Runtime owns the effective state at a published frame epoch: the current value after timeline interpolation, native reactive evaluation, host/updater effects and other active drivers have been applied according to their semantics.
Language compatibility getters that ask for the current live value normally observe the latest coherent effective state, not a stale authored/base value. An explicit authored/base-state inspection API may exist when useful, but it is a different query.
Active timeline/reactive/host/editor drivers normally write effective runtime state, not authored/base state. Such writes may advance FrameEpoch and renderer/spatial state without creating a new SceneRevision or requiring semantic relowering. A driver write becomes an authored semantic mutation only when the semantic operation itself is persistent/structural or when compatibility/product semantics explicitly reconcile a resulting effective value back into authored state.
When animation/updater completion semantics require an effective final value to persist after the driver is released, that persistent result is reconciled into authored/base state through the normal mutation contract and coherent publication path. A conforming frontend must therefore not disagree about a current getter merely because authored and effective representations live in different layers.
Semantic structure is not execution structure#
Semantic families, object hierarchy and aliases exist because they are observable authoring semantics. They must not be forced to double as the optimal execution or render hierarchy.
The compiler may factor compatible high-fanout behavior into derived execution structures such as shared transform/style domains, instance groups or other compact representations when observable semantics are preserved. A semantic family and a derived execution/render group are not required to be one-to-one. Aliasing, conflicting drivers, ordering or other observable semantics may prevent factoring and require explicit member work.
Compact fan-out does not imply that every downstream operation becomes O(1). If an observable consumer genuinely needs per-leaf materialization, for example world-space bounds or precise hit-test records, work may scale with the affected leaves that must be materialized. Domain split/deoptimization may scale with the size of the affected domain, but it must not silently expand to unrelated scene content.
This is a one-way specialization rule: execution/render structure may be derived from semantic structure, but derived execution/render grouping never becomes authored truth.
4. Language frontends#
High-level behavior is implemented once in shared Rust semantic code.
The frontend contract is semantic, not an assertion that all languages execute source code the same way. Frontends converge when they invoke the same semantic operations and observe the same resulting behavior.
Rust public API#
Rust authoring is first-class, not a wrapper around Python, JavaScript, WASM, JSON, or a transport model.
The idiomatic Rust API calls shared semantic operations directly and must support normal Scene, Mobject, animation, signal/reactive, query, mutation, lowering, execution, and rendering workflows entirely inside the Rust environment.
A native Rust application must be able to build and render a Noon scene without initializing any language host or serialization subsystem.
A Rust application compiled to WASM must be able to build and render the same Noon scene semantics to a browser canvas without serializing between in-process engine layers. Browser bootstrap code is platform integration, not an authoring or engine dependency.
Rust runtime mutability does not imply runtime interpretation of arbitrary Rust source. New behavior introduced by source changes requires ordinary recompilation/hot reload or another explicit dynamic mechanism. Editor/live-scene mutations should therefore target semantic operations rather than inventing a Rust-source interpreter requirement.
Python may own#
- Manim-compatible class hierarchy and method signatures;
- Python argument normalization and iterable/vector conversion;
- Python callable identity and callback invocation;
- Scene subclass discovery and sequential authoring control flow;
- Python-appropriate exceptions and wrapper metadata.
Python must not own#
- object ID or painter-order allocation;
- semantic object state;
- scene membership/lifecycle;
- family traversal or ordering semantics;
- layout/bounds calculations;
- target-state evaluation;
- animation timing/scheduling/interpolation;
- signal evaluation;
- transaction rollback state;
- retained execution or renderer state;
- a second retained-text scene;
- a second renderer/runtime model.
The desired shape is a thin proxy API:
class Mobject:
def shift(self, value):
self._handle.shift(value)
return self
class Scene:
def add(self, *objects):
self._handle.add([x._handle for x in objects])
The Rust public API calls the same semantic operations directly, without any wrapper or serialization hop.
Thin does not mean one cross-language call per scalar property access. Frontends may batch construction/mutation/query operations and host-callback read/write sets where that preserves observable semantics. Large immutable content should cross a real language/context boundary as a resource payload/handle once, not be repeatedly reconstructed as scene-shaped messages.
Script continuation semantics#
Sequential authoring is allowed to depend on completed runtime state. play()/wait()-class operations therefore establish a logical segment-completion barrier:
See the host-continuation diagram (D2 source): author intent, stage/publish a segment, advance the same runtime, then resume only after logical completion.
The host mechanism used to wait is target-specific: a Python worker may block/suspend an interpreter continuation, native Rust may drive/await through compiled control flow, and browser/WASM must yield/await without blocking browser frame/input servicing. These mechanisms need semantic equivalence at the barrier; they do not need identical host-language execution machinery.
A segment is complete when:
- the requested authored-time boundary has been reached;
- every required ordered callback/updater dependency through that boundary has completed;
- resulting authored semantic mutations and effective driver writes, together with any required incremental lowering/preparation, runtime/spatial changes and renderer-facing delta, have been coherently published;
- the resulting effective state is queryable by the resumed authoring continuation.
Completion normally does not require waiting for GPU retirement/fence completion or for an old resource version to be physically freed. After the barrier, live-value queries observe the coherent effective state defined above.
This continuation protocol must not require a host interpreter while a deterministic segment is running unless host-dynamic behavior is actually scheduled.
Paired executable examples#
For significant supported common semantics, maintain equivalent executable Rust and Python examples.
The examples have different jobs:
- the Rust example is the direct first-class product proof;
- the Python example proves the language wrapper reaches equivalent shared Rust semantics;
- both should exercise the normal lowering/runtime/renderer path appropriate to their target;
- target bootstrap and host control flow may differ, but scene semantics should be equivalent and reusable;
- cross-language qualification must not depend on serializing migration-era scene documents as the ordinary comparison mechanism.
When a feature is supported in both Rust and Python, implementation is not complete merely because one frontend can demonstrate it.
5. Analysis and lowering#
The compiler lowers the Semantic Scene into an Execution Plan. This is scene lowering/specialization, not Rust source compilation and not execution of the future host program.
For Rust, whether native or WASM, this is an ordinary typed in-memory Rust transformation when the compiler and runtime live in the same execution context. The Semantic Scene is not serialized into a wire document and reparsed by the compiler.
Lowering is allowed to discard or refactor authoring structure whenever doing so preserves observable behavior.
Domain projections#
Each row follows a data domain across lowering and its consumers. These are
projections, not four independent pipelines or a function-call sequence: timeline
and reactive results contribute to the same effective frame; the frame also
references content and participates in spatial/painter-order queries. Geometry
meshes and glyph residency are prepared downstream by rendering, not baked into
SemanticStore. CompiledResources retains the typed resource projection.
Arbitrary host callback slots use the ordered barrier protocol in section 9 rather
than being assumed to execute inside the native reactive graph.
The optimizer classifies dependencies/properties, not whole scenes, into these execution classes:
Immutable#
No runtime change is possible.
Examples: static geometry, constant styles, immutable resource topology.
Compile/cache/pack these completely.
Timeline#
The value is predetermined by authored time.
Examples: ordinary transforms, fades, reveals, deterministic composition.
Lower these to explicit tracks, prepared morph/reveal data, or GPU-evaluable parameters.
Native reactive#
The value changes at runtime, but Noon understands the dependency graph.
Examples: ValueTracker, pointer position, viewport size, property bindings, built-in constraints and derived expressions.
Evaluate only the dirty dependency closure.
Optional host-expression capture#
Capture is an optional frontend translation/lowering optimization, not another execution class, runtime, or Phase A exit requirement. Its result belongs to the existing immutable, timeline, or native-reactive class according to its dependencies. Reuse shared typed semantic expressions and reactive lowering rather than introducing a frontend-owned expression graph or interpreter.
A frontend may offer a constrained host-expression subset that can be captured or traced into Noon-owned expression/reactive IR. Once successfully lowered, that behavior is native execution and does not require the host interpreter on the frame path.
Examples may include pure arithmetic over trackers/signals, supported vector operations, property bindings, and other expressions whose dependencies and side-effect semantics Noon can represent explicitly. Captured closure values and guards require explicit validity/invalidation rules; mutable host state cannot silently become a frozen constant.
Tracing/capture is a frontend translation mechanism, not a new semantic authority. It must preserve observable semantics and must not speculatively execute arbitrary host code in a way that duplicates or hides external side effects. An implementation may require an explicit traceable API/subset where transparent tracing would be unsafe.
If a host expression cannot be represented faithfully, it remains Host dynamic rather than being silently approximated. Unsupported capture must be detected before committing semantic mutations or running effectful host code; falling back must not replay an arbitrary callback or duplicate its side effects. An explicit capture-only API may reject unsupported input, while a callback API may retain the original host-dynamic behavior and ordering. A failed trace must not poison unrelated static/timeline/native-reactive work.
Host dynamic#
Correct behavior requires arbitrary host-language execution.
Examples: an updater containing arbitrary Python control flow or an event handler that calls user Python code with effects Noon cannot model.
Represent this explicitly as host callback slots. A few host-dynamic dependencies must not make unrelated static/timeline/native-reactive content dynamic.
Execution classification is an implementation choice, not permission to reorder observable semantic updater execution. If authored/compatibility updater order is native A -> host B -> native C, the execution plan must preserve that order, for example as native regions separated by a host-callback barrier. Independent work may be fused, parallelized or reordered only when the compiler/runtime can prove that the difference is unobservable.
Data-oriented specialization rule#
Noon may borrow ECS/data-oriented techniques below lowering without adopting ECS as the authored scene model.
Execution may use dense arrays, structure-of-arrays layouts, sparse identity maps, compact change revisions, specialized queues and content-specific immutable resource arenas. The layout is chosen for evaluation/locality, not for authoring ergonomics.
A derived execution slot may therefore expose transform/style/bounds/resource/timeline/reactive fields independently even though those values came from one semantic object. Conversely, compatible semantic fan-out may be represented by one shared execution domain when that avoids materializing thousands of equivalent child updates.
There is no public requirement that a semantic Mobject map to one ECS entity, one GPU object, one draw call or one renderer record.
6. Execution Plan#
The Execution Plan is renderer-independent, compact, validated execution data.
It may contain:
- stable execution slots mapped from semantic identities;
- dense/specialized mutable-property storage and change-revision metadata;
- immutable content/resource references;
- resolved timeline channels and event schedules;
- precomputed geometry/morph/reveal plans;
- native reactive bytecode/graphs, including successfully captured/traceable host expressions;
- mutable property slots;
- ordered updater/evaluation regions with explicit host-callback barriers where needed;
- host callback descriptors;
- event subscriptions;
- derived transform/style/instance domains for compact high-fanout execution where semantics permit;
- invalidation metadata;
- bounds/spatial-index data needed by execution.
It is not required to preserve the Semantic Scene's ergonomic hierarchy.
Execution grouping is derived and disposable. It is legal for the compiler to split one semantic family across execution groups or combine compatible semantic leaves under one execution transform/instance domain. Such grouping must preserve identity mapping, ordering, hit-testing and mutation semantics and must be invalidated/reformed locally when the assumptions enabling the specialization stop holding.
The Execution Plan is not a frozen representation of the entire host-language program. Sequential Python authoring, editor actions, compiled Rust event handlers, hot reload and other producers may publish later semantic revisions that require a new or incrementally changed execution projection.
Replaceable does not mean rebuilt for each operation. Ordinary changes use impact-local preparation and atomic semantic/execution publication, preserving compatible semantic identity, runtime time/state, active drivers and retained resources. Revision transitions occur at an atomic safe point and must preserve, or explicitly reject before publication, any active execution segment, pending callback token and pending publication receipt. The previous coherent revision remains usable if preparation fails; effective-only driver updates continue to advance frame state without semantic relowering. Plan replacement does not authorize resetting the live runtime or reconstructing unrelated scene state.
Execution-plan ownership remains below the semantic store: noon-core owns shared semantic identity/store and renderer-independent contracts, while noon-compile owns CompiledScene and lowering/specialization. Authoring compatibility helpers belong in the public/frontend layers, not in either shared engine contract merely for convenience.
7. Runtime#
A runtime instance owns current execution state:
- playhead and timeline cursors;
- current effective mutable property values;
- signal/input values;
- dirty/invalidation sets and/or per-slot change revisions;
- lifecycle/presence state;
- spatial-index state;
- host callback requests/results;
- the current
SceneRevision/ExecutionRevisionrelationship; FrameEpochand other sequencing state needed for coherent publication;- renderer-facing change sets.
Static clean content should disappear from ordinary per-frame CPU work.
Fixed semantic phases, specialized implementation#
Noon has a small fixed execution protocol rather than a public arbitrary system scheduler. A representative frame/publication cycle is:
1. ingress
sampled pointer/viewport/control state + ordered discrete events
2. timeline evaluation
evaluate authored-time progress and deterministic active channels
3. ordered dynamic evaluation
preserve semantic updater/effect order
native regions may be separated by required host-callback barriers
4. collect a StagedUpdateBatch
effective driver writes remain runtime/effective changes
authored semantic mutations remain persistent/structural changes
both remain unpublished until the same coherent barrier
5. validate + prepare
validate authored semantic mutations and perform impact analysis
incrementally lower/prepare only when semantic/execution structure changes
prepare effective runtime/spatial/resource changes without semantic relowering when not required
6. atomic publication barrier
publish FrameEpoch F referencing:
SceneRevision S
ExecutionRevision E
plus the coherent effective Runtime state,
spatial/index changes and renderer-facing delta
7. visibility + renderer preparation
consume the published FrameEpoch and produce candidate-sized/dirty work
8. GPU upload/encode/submit/present
The implementation may fuse, skip, parallelize or pipeline stages when observable ordering and revision/coherence rules remain identical. The phase model is a semantic ordering/publication contract, not permission to expose a general-purpose game-engine scheduler.
There is no global rule that all native-reactive work runs before all host-dynamic work. Classification controls how an ordered region executes; semantic dependency/updater order controls when it executes. A successfully traceable host expression belongs to a native region after lowering; an opaque callback remains a host barrier.
FrameEpoch is the unit of coherent effective publication. Each published frame epoch references the exact semantic and execution revisions from which its effective state is derived:
FrameEpoch N
SceneRevision S
ExecutionRevision E
FrameEpoch N+1
SceneRevision S // may be unchanged
ExecutionRevision E // may be unchanged
Only a committed authored semantic mutation creates a new SceneRevision. Only a changed derived execution projection creates a new ExecutionRevision. Ordinary timeline interpolation, native reactive evaluation and host updater driver writes may therefore advance FrameEpoch while both revisions remain unchanged. This distinction is required so per-frame effective behavior does not imply semantic commits or relowering.
When a staged batch does contain authored semantic mutation, that mutation and its required derived lowering/preparation are one logical publication. If validation/lowering/resource preparation required for the operation fails, no new live SceneRevision/compatible ExecutionRevision is published. Async preparation normally remains staged until coherent publication is possible. A feature may explicitly expose a semantic pending state when that state itself is part of the authored/product semantics, but the previous executable/resource version remains active until the pending operation can publish coherently.
Timeline and interaction are not mutually exclusive modes. Native input/reactive changes may be processed while a timeline segment is running. A host play() call/await merely waits for the logical segment completion contract before its sequential authoring continuation resumes.
Settling and wakeup#
A runtime with no active timeline work, no dirty reactive dependency, no pending event, no required host callback and no pending renderer publication should settle/sleep rather than continuously tick.
Relevant input, a timeline boundary, a host result, an async resource result or an explicit authoring/editor mutation wakes it. Paused interaction may therefore be responsive without advancing authored timeline time or burning continuous CPU when nothing changes.
Time and determinism#
Noon distinguishes clocks/sequences that must not be conflated:
- authored/simulation time — the time used by authored timelines, deterministic playback and compatibility updater
dt; - wall/presentation time — host elapsed time used to schedule realtime presentation, not semantic truth by default;
- input sequence/timestamps — ordered external event identity/timing used for interaction and optional replay;
- GPU submission/presentation time — backend scheduling/lifetime information, not authored scene time.
Compatibility updater dt is defined from authored/simulation-time advancement. A paused scene may process pointer/keyboard/editor input while authored dt == 0. Behavior that explicitly reads wall time, network time or another external clock is externally timed/non-deterministic unless the source is recorded and replayed by an explicit policy.
Native translation dragging is session policy over the same typed pointer ingress. Configure semantic targets once, then deliver typed pointer records without choosing a runtime object per move. LiveSession and LiveProgram accept ordinary Mobject handles, retain the existing store gate, and route each occurrence to the same session policy:
program.set_translation_drag_targets([&circle])?;
let token = program.configure_native_pointer_input(pointer, view_revision)?;
let press = NativePointerInput::new(1, token.pointer(), token.context(), modifiers, press_kind);
program.submit_native_pointer_input(&token, press)?;
Press and move publish scoped effective Position. Release prepares its native occurrence and one authored translation reconciliation together; cancellation restores the authored position without changing SceneRevision. Obtain a fresh token after each publication. Position/Transform timelines, reactive Position bindings, and target-local transient Position effects reject acquisition. Acquisition also checks the shared persistent-publication barriers before taking a lease. An unrelated nonstructural segment may continue and complete while a target is held; release reconciles only that target and advances the pending segment’s semantic revision proof after publication succeeds. Segments that touch the target or change structure still reject acquisition. Starting a new segment while a target is held remains explicitly gated. Stale or failed releases retain the lease without acknowledging the occurrence. The receipt-returning submit_translation_drag_input entry exposes the single release undo action to editor hosts.
The drag entry remains the ordinary pointer publication lane: misses and non-captured occurrences keep normal native-signal, hover, and click behavior. See crates/noon-native/examples/native_translation_drag.rs for an executable scene using the ordinary native host.
Python authoring declares the same source policy with scene.set_drag_targets(circle). It passes the original typed Mobject handles through the inert canonical membership batch; the browser never picks a drag target or calculates a translation. Rebinding or invalidating a browser view cancels an active capture through that same Rust ingress before its receipt is retired. A successful current-presentation press pins one constant-size mapping for its acquired drag. Captured moves and primary release keep their collection-time receipt and use that mapping even when newer motion acknowledgements overtake delivery on the independent worker channel. Receipts must remain within the acquired-to-latest acknowledged session/view interval; runtime, source, view and camera must still match. This retains no frame history, performs no new pick and never relabels a receipt. A fresh press and ordinary picking retain exact current-presentation admission. Configured unrecorded drag input rejects replay explicitly; Run recreates the authored scene.
Replay classification applies to all externally supplied behavior that can affect results, including pointer/keyboard input, editor manipulation, host callbacks, async/network results and other external data. Recorded native input/event streams may be replayable even though their original occurrence was nondeterministic.
Historical execution and object lifetime#
One authored scene owner does not make a latest-state execution projection a history of that scene. Object existence, current scene membership, membership at an earlier authored time, and physical execution/GPU retirement are different questions. In particular, completing a FadeOut may detach a still-valid semantic object; it must not erase earlier execution data while that data is promised to replay. Reusing the same object identity on FadeIn does not restore deleted tracks.
For a declared deterministic replay range, the core invariant is:
first execution at t == reconstructed replay at t == rewind then forward to t
Equality covers effective content, transform/style, presence, painter order and resource versions throughout the range, not merely object counts or the final frame. Completing an earlier animation must not substitute the latest authored base for that animation's historical endpoint. Prepared morph coordinate frames must release correctly to later affine drivers at the same time-qualified execution revision. Optimizations are subordinate to this invariant.
Finite source execution may explicitly retain sparse compiler-owned reversible revisions under its existing Runtime, then seal that range for read-only replay. Only successfully published changes enter that history. It must reuse the normal evaluator, immutable resource handles and semantic-derived identities; no second mutable Semantic Scene, frontend state mirror, per-frame scene snapshots or re-execution of arbitrary host source is permitted. The authored scene remains at its current frontier. Historical projection changes advance execution/frame publication identity, not authored semantic revisions.
History and necessary execution identity/resource pins have an explicit scope and retention budget. Removal from current membership cannot reclaim a pin still needed by that scope. Discarding the scope restores the authored frontier before releasing pins and permitting further edits. Ordinary indefinitely editable live sessions do not accumulate historical revisions by default. A capacity limit or unsupported execution domain must leave first execution coherent and reject the replay capability, never silently reconstruct incorrect past frames. Required callbacks, native input and scalar/family-specific execution need their own qualified participation in the same contract; source completion alone is not proof of seekability.
Playback admission is an engine decision. The host must enable seek/restart/loop controls only after the Rust execution owner validates the advertised capability. A non-replayable completed execution may retain its final presentation and explain the limitation, but may not fall back to seeking a destructively updated plan. An unfinished live segment, including a pure wait with no animation tracks, cannot be sealed as completed history. A sealed snapshot with no elapsed interval has no loop cadence; paused playback observations remain at the committed runtime time.
Replay capability and forward execution admission are distinct. Denying backward replay must not reject a paused, explicit forward authored-time observation through the existing callback-aware Runtime path. Rust validates its monotonic target and orders required callbacks; the host still waits for the matching publication and renderer observation. This does not authorize rewind, looping, callback re-execution or takeover of an active source continuation.
A related observation distinction applies to static waits: the timestamp of the last evaluated/rendered frame is not necessarily current elapsed playback time. An authored wait advances playback time at the selected rate while unchanged rendering may sleep. The existing Rust clock and runtime deadlines bound that observation; pause and required dependency barriers remain authoritative. Unknown future source duration does not justify a frozen elapsed-time counter or a second JavaScript animation clock. Clock observations must also remain independent of renderer telemetry latency. A ready engine time sample may not wait for GPU metrics or readbacks, and a slow telemetry request must not stall subsequent time observations. Hosts retain only bounded in-flight requests and reject replies from superseded runs.
Implementation finding, September 18, 2026: #1660 records premature missing columns and absent pulse objects in Dynamic Load Stress replay, caused by current membership retirement deleting historical channels and by later base/render-frame state leaking backward. #1658 qualified a narrower morph/hold sequence, not the full lifecycle contract. #1661 separately addresses wait-time observation. These issues carry dated evidence and repair status; their existence is not a claim that every history domain has been implemented or qualified.
Completed-prefix delivery#
The prepared-prefix feature keeps the producer's sole Semantic Scene
and uses the existing runtime evaluator in an independent derived history reader.
After one explicit execution bootstrap, completed extensions transfer only new
compiler-owned row/track revisions. Appending future history does not advance the
reader or replace its displayed frame. Source-owner segment/callback admission
precedes extraction; a tokenless wait needs its actual continuation boundary too.
The advertised interval is half-open [start, prepared_frontier), since further
source statements may still mutate the frontier's exact timestamp. Only one
ordered, scope-bound stream may extend a reader; budgets and failures preserve
already-published history. New suffix conversion uses a sparse reverse overlay,
not a copy of the source scene or a rewind of either execution cursor.
The 2D suffix profile admits sparse creation, removal/re-entry, order/content
changes and ordinary property tracks. New physical rows are installed as inactive
slots; they become visible only through historical revision selection. Source
resource closures carry only not-yet-delivered immutable handles and dependencies.
A new-slot capacity reservation is an explicit bounded maintenance barrier, never
an implicit whole-scene relocation during append. It may be O(existing storage);
ordinary suffix extraction/installation remains proportional to newly touched
payloads and ordering neighbours. Entry limits coexist with byte admission for
immutable resource closures, the reserved history index, saved revision vectors,
compiled/runtime object vectors and both-side logical inline path payloads. Other
shared allocations, path spare capacity, allocator overhead, source work and GPU
residency remain outside these charges; they are not total memory bounds.
An empty reactive runtime installed by semantic lowering is admissible; actual
signals or reactive bindings, including later enrollment, require their own history
policy. Validated 2D geometry transforms/morphs and one resource-free Glow attachment
share the sparse revision path, including generation-scoped Glow color, radius
and intensity tracks. Append-only glyph/reveal plans transfer only new immutable
member descriptors and composition timing, preserving existing plan/channel
indices and the shared scheduler. Their logical headers, member data and time
maps join resource-byte admission before cloning; spare capacity and scheduler
metadata remain outside that charge. An explicit storage reservation precedes
adoption, which neither evaluates future glyphs nor changes the displayed frame.
Family transforms, path-motion/spatial plans, ordered effects/other effect
animation, reactive/causal native input and host callbacks
remain outside suffix admission. The opt-in cooperative PreparedSource may consume an
ordinary Rust LiveContinuation through its existing LiveProgram. Each preparation
call invokes at most one continuation and completes its actual admitted segment
headlessly using the shared evaluator/completion path. One pending reader/batch
applies backpressure; no further source invocation occurs until it is taken.
Retention admits the producer publication, not a displayed-frame receipt.
Callback, effective-display, unretained signal/input, unsupported suffix and
invalid retained-history requirements instead yield ExecutionRequired before
headless advancement. Publication-time profile indices make preflight constant
time, including repeated zero-duration yields; this does not clone history or
guarantee later byte/capacity admission. The inset-history flag also catches
same-call add/remove. Staged external resources retain their original program
and rendering-host publication handshake. A one-use typed transfer retains
the original program, runtime and actual pending segment, releasing only its open
source-history scope; already-delivered readers stay valid. It invokes no source
or callback and acknowledges no display. This does not admit callback replay in
the reader. Other preparation failure is terminal and never retries user code; cancellation releases only source-owned
state while already-delivered history remains usable. An unwinding panic leaves
that source owner terminally failed before propagating its original payload;
source-owned unpublished output is released even if final cleanup panics. This
is not rollback of external side effects or in-process recovery from abort/trap
configurations, which require host-context teardown. Zero-duration waits yield;
this is a work-count bound, not preemption of a long user call or resource operation.
An open reader still requires a positive readable interval. After explicit
source completion, a sealed static scene may bootstrap the same historical reader
at the exact closed range [0, 0], without an artificial segment or source retry.
Its history index reserves only occupied entries because no future suffix exists.
Explicitly sealed
source completion can publish a terminal batch; only reader adoption changes the
ready interval to the inclusive final endpoint, without seeking the display.
Viewer selection does not broaden this admission profile or establish physical-device
performance. Further qualification remains in #2211; this is not another scene or rendering authority.
The native continuation entry point uses the typed PlaybackProgram owner through
the existing prefix feature. One cooperative call prepares and adopts at most one
source suffix; host demand comes from its existing clock, including sleeping waits.
The reader remains selected until an actual request reaches a required execution
frontier. Handoff preserves inspection, releases cached history, retires the old
physical input receipt/contact and rebuilds previously retained renderer residency,
including text and capture state. This explicit maintenance can reupload the scene;
it preserves the device and immutable program cache, not old projection bindings.
The original program then uses the shared forward-sample callback/resource fences;
seeking becomes unavailable rather than replaying effectful work. Missing-prefix
buffering retains the last sample, preserves pause intent and reanchors only the
existing host clock after delivery. Closed-range adoption is separate from source
completion. This does not classify arbitrary host I/O or wall-time reads as pure,
preempt long calls, or establish browser/default-cutover or total-memory qualification.
The Python viewer requests bounded incremental playback by default. Shared Rust
admission separates scene-history compatibility from source effects: continuations
are opaque unless admitted as scene-only, with Python supplying a conservative
proof over its trusted API. Arbitrary host I/O, wall-time reads and opaque callbacks
must not run ahead merely because their scene is replayable. Unsupported source
profiles keep their original forward execution and report replay as unavailable;
they are never rerun or reconstructed to obtain playback.
The opt-in direct Rust/WASM canvas can own this reader instead of a live source,
using the same retained renderer, presentation receipts, pointer normalization and
wake driver. Incremental delivery is typed and in-process; the producer remains
outside the canvas. User pause and missing-prefix buffering suppress only authored
time wakes, not independent effects. At an unavailable frontier the host retains
its last valid sample and sleeps until explicit delivery, without an epsilon seek
or a busy timer. Resume reanchors the existing host wake clock so buffered wall
time is not charged to playback. This does not install the reader in the Python
playground, admit arbitrary source continuations or change the default viewer.
A browser task driver may schedule one cooperative Rust preparation call per
macrotask and wake the existing presentation driver after typed delivery. It owns
only task handles/cancellation, never authored time, source semantics or batches.
The native path calls the same producer directly without browser infrastructure.
The producer's prepare_until demand gate admits another continuation only below
an absolute requested frontier, while preserving pending-output backpressure. An
unknown segment may overshoot that target once; this does not bound resource bytes
or discard history required for rewind. The canvas derives a positive look-ahead
from its reader, not from a JavaScript timeline clock. Satisfied demand sleeps with
no preparation timer until the existing playback/control driver reports progress.
A missing open frontier admits one refill even when the last rendered sample still
predates a long static wait, preventing a demand/buffering deadlock. Rejected
nonfinite demand performs no user work. Terminal delivery and explicit cancellation
remain distinct from temporary demand satisfaction.
The optional canonical interpreter context can complete its actual pending segment
headlessly through the same player-owned receipt, admission and semantic completion
path. A thin Python LiveExecution.prepare() request does not supply a guessed
endpoint or authorize presentation. The existing context retains the sole source
runtime; typed Rust prefix extraction supplies a separate historical reader without
leasing the source player to it. Source callbacks/causal state are rejected before
headless advancement, and pending or transferred source ownership cannot publish a
prefix. Explicit source sealing remains distinct from finishing an individual
segment. Host orchestration must retain/deliver each extracted batch, yield interpreter
work and stop on errors; this bridge provides no retry, source scheduler or new wire
format. A frame/debug observation is never fed back as engine authority.
An opt-in interpreter suspension driver may hold the existing canonical Python
continuation after its segment has been completed and adopted. A typed Rust canvas
query decides whether to resume source under the same look-ahead/open-frontier
policy; JavaScript receives a boolean, not an authored cursor or history payload.
One macrotask yields between continuation releases. Satisfied demand keeps no
scheduled preparation task. The driver is single-use, rejects overlapping barriers
and latches failure/cancellation even when source catches the rejected await. It
reuses the existing complete control event and never automatically retries user
statements. Asynchronous host bootstrap/cleanup must observe its abort signal
before publishing after a wait; neither arbitrary Python execution nor such host
work is preempted. The admitted ordinary construct path uses the existing portable
source compiler, with no second rewrite or source-language engine. This same-context
host integration remains opt-in and does not change the default viewer.
Hard byte budgets, preemption of heavy source work and restart/recovery policy
remain separate qualifications.
The canonical interpreter context can construct that prepared worker host after its actual first segment completes, without leasing away its source runtime. The host may retain one source-bound publisher cursor and one undelivered suffix; these are delivery metadata, not another semantic scene or source evaluator. Source identity/scope and pending-output admission are checked before completing or sealing the source. Completion extracts at most one suffix; adoption is a separate typed Rust operation. Capacity rejection keeps the same suffix for explicit reservation and retry, never repeating Python or completion. Zero-duration completion invents no readable range. Before reader bootstrap, the interpreter may continue one zero-duration barrier per worker turn until Rust reports a readable interval or source cleanup finishes. Early registration keeps that loading phase cancellable. A zero-only source seals its final same-time edits into the same completed reader; only explicit completion exposes its endpoint. Cancellation drops unpublished delivery state without revoking earlier history; the caller still cancels/releases the interpreter. A failure after source completion begins closes that delivery binding rather than automatically retrying partially completed work. None of these operations is a presentation acknowledgment.
An explicitly prepared Python run uses the same production authoring worker, canonical context and source generation. Its single-use interpreter driver wraps the whole source invocation and yields at ordinary play/wait barriers. The existing execution endpoint owns the independent prepared reader in that WASM context; the source runtime remains with the interpreter. Rust look-ahead demand resumes the suspended stack, and one retained suffix must be adopted before another segment completes. Prefix delivery neither waits for nor fabricates presentation. Only selected frame output uses the existing bounded render transport. The first actual presentation receipt admits the Rust playback clock; pure typed status observations do not start it. Pausing commits elapsed interior static-wait time through the same clock and reader, without selecting an unavailable open frontier. Source failure retires preparation and preserves already-adopted history; explicit cancellation retires the endpoint and releases its reader. Late attachment results cannot revive a cancelled invocation. Terminal adoption keeps that same reader, rather than reconciling it into the source's latest-state player. This bounded profile rejects causal input, host callbacks and looping. An unfinished source requires an initial readable interval; completed static sources use that same reader with the exact zero endpoint after interpreter cleanup succeeds. A source that finishes without suspension is classified after cleanup by Rust. Static profiles keep their exact-zero history/input path; declared future work, media and playback controls retain the original canonical runtime and ordinary finite-source admission, without another source invocation. A receiver restart retains the same reader, original source and delivery cursor in a newer receiver namespace. The replacement must actually present before its clock or source handoff is admitted. Failed renderer setup preserves the reader for retry; explicit source retirement releases it. This recovery does not restart user code. Receiver failure commits elapsed static-wait time through Rust before freezing the clock, preserving playback intent without publishing to the failed renderer. After acknowledged retirement, a new source Run can reuse the render worker and canvas with a newer transport session and fresh controls. An active historical reader still cannot be replaced by reconciliation.
Before a reader exists, Rust can instead admit the original pending interpreter segment to observed execution. This retires prefix publishers while preserving the original bounded journal, its failure marker and valid identity pins, and leases the same canonical player to the existing continuation endpoint. Required callback events return to the original suspended Python stack; neither admission nor source completion reruns its body. The endpoint reports observed execution and unavailable replay while source owns execution. New suffixes also use the shared source preflight before headless advancement. Observed source samples and returned leases preserve play/pause intent in the same Rust player clock. Manual authored-time samples remain paused. Pause commits elapsed interior wait time; resume reanchors the existing source wall conversion without charging paused time or restarting the interpreter. Source controls expose pause/resume without a seek range or invented duration. Paused transient input keeps its independent interaction timing within existing input/callback admission gates. After interpreter cleanup, a checked transfer returns that same completed player to the endpoint for input. Its encoder, runtime, semantic store and contacts stay intact; no source invocation or initial snapshot is repeated. Final authored output still waits for actual presentation. Receipts delivered while the context holds the player are retained until it returns, after any required pointer cancellation. After cleanup, Rust may seal that original journal for ordinary replay. Successful admission clears the completed live receipt and exposes its actual authored range; failure preserves explicit replay denial and native input. Completed lesson cues refresh on the same player. Python lessons with section navigation stay observed because the prefix profile does not carry that metadata; their completed replay still uses ordinary admission. Existing resource preparation on an observed source keeps its original GPU handshake and suspended callback. Authored publication during an active translation drag and native input with required callbacks retain their existing explicit gates. These transitions neither rerun source nor create another history or clock.
At the genuine execution-to-render-worker boundary, an opt-in prepared player may own the independent reader in the producing execution context. It encodes only that reader's selected RendererPublication with the existing retained codec, resource residency and input presentation receipts. The rendering worker remains a derived GPU/presentation consumer; prefix batches and the source runtime do not become a second cross-context history protocol. Initial resource bootstrap is empty and selected immutable closures accompany the initial delta. Future append alone sends no render publication. A drained delta is owned by the host for ordered, backpressured delivery; neither consumption nor extraction acknowledges display. Rebootstrapping that stream with an incomplete inventory is rejected. Full worker restart/rehydration still requires its own lifecycle qualification. Single-context/native hosts continue to borrow publications directly without this transport.
The reader distinguishes monotonic forward playback from an explicit seek. Direct, typed presentation effects use the existing Runtime's bounded Indicate admission against the displayed historical target, not the producer's current membership. Effects advance on session time while authored time may stay paused; they do not modify retained history or source semantics. Appending future history preserves the displayed effect. Forward playback within one execution revision preserves disjoint effects, while authored channel conflicts, a crossed execution revision, or an explicit seek supersede them under the existing runtime policy.
Authored click-Indicate declarations are lowered into their existing compiled object rows and retained by ordinary reversible execution history. Preparing a bound action reads the declaration in the displayed revision, including its absence, replacement or removal. Binding-only publications retain one touched row without dirtying renderer-facing values. Shared session dispatch reads that same execution projection, with a maintained live-binding count for constant-time input interest; it keeps no producer-current binding map. Future appended rows remain inactive until their creation revision is displayed.
Live GlowPulse and playback-control declarations remain with their canonical semantic owner and are rejected by prepared-source admission. The historical reader admits only compiled Indicate presentation effects: it reuses shared pointer preparation and metadata commit, rejecting causal writes or a semantic lifecycle action before acknowledging input. It never invents or borrows a mutable SemanticStore. Ordinary live sources continue to retire temporary glow attachments through their original SceneExecution/LiveProgram store-session pair.
The public prefix reader owns an ordinary ExecutionSession, not a second input
engine. Its runtime-layer PrefixReceiver owns only stream/readiness metadata,
bound to that runtime incarnation and sealed history scope. Bootstrap moves the
one derived runtime into the session; it does not clone the producer session or
retain its mutable semantic identity/reachability maps. Initial and newly delivered
physical identities are pinned in the existing derived slot table. Those pins
are not live membership: picking still observes the selected historical revision.
New-slot map/storage reservation is explicit, and append registers only new rows.
Pointer admission, picking, click synthesis, hover/selection, inspection and bound Indicate dispatch delegate to the existing session APIs. The public reader exposes neither mutable session/runtime access nor authoring/causal input operations. Readiness and monotonic advance are checked before ordinary session evaluation; explicit seeking cancels transient interaction through the same session policy. Future or terminal delivery preserves the displayed publication and pending pointer gesture. Frame snapshots retain the existing runtime/view/inspection qualification and do not assert that a host actually presented a frame. Hosts must keep that association themselves. General injected animations and causal edits require separate admission; browser/native shell adoption and end-to-end latency qualification are still outstanding in #2211.
Identity generations, revisions, versions and sequences#
Identity validity, authored/execution revisioning, effective publication, external event ordering, and GPU lifetime are distinct domains. They must not collapse into one ambiguous global generation counter:
| Domain | Meaning |
|---|---|
NodeId / ExecutionSlotId generation |
Identity validity across slot reuse; stale handles cannot alias replacements. |
SceneRevision |
One coherently committed authored semantic-scene revision. |
ExecutionRevision |
One compatible derived execution projection. |
FrameEpoch |
One coherent effective runtime/presentation publication referencing a scene/execution revision pair. |
ResourceVersion |
Immutable content/resource replacement version. |
InputSequence |
Ordered external event sequence. |
CallbackEpoch |
Ordered callback/evaluation request/result context. |
SubmissionSerial |
GPU submission/fence/retirement ordering. |
Values from different domains are not directly comparable merely because they are integers. Async/callback/resource results carry the exact identity/revision/version context needed to prove applicability. Consumers may remember the last relevant revision/version rather than relying on global dirty-bit clearing; late results must be rejected or reconciled deterministically instead of overwriting newer state.
Runtime complexity contract#
clean paused/static frame ~ O(0) meaningful CPU work
timeline work ~ O(events crossed + active CPU channels)
ordered updater/reactive work ~ O(affected ordered regions + required host barriers)
effective driver write ~ O(affected runtime slots/domains + observable consumers)
authored property edit ~ O(affected semantic/execution slots/domains)
structural edit ~ O(local dependencies + required relowering)
transaction preflight ~ O(transaction + affected dependency/relationship closure)
preflight temporary memory ~ O(transaction + affected dependency/relationship closure)
high-fanout domain work ~ O(changed domains + affected leaves actually materialized)
visibility query ~ O(index query + candidates)
render publication/preparation ~ O(dirty resident state + visible projection)
GPU upload ~ O(changed ranges/resources)
draw submission ~ O(visible batches/instances)
host bridge calls/bytes ~ O(host working set + returned staged updates/resource payloads)
resource regeneration ~ O(actual resource changes)
resource retirement ~ O(actual retired changed resources)
The locality contract covers reads, validation/preflight, temporary allocations, writes, lowering, spatial work, renderer preparation and transport—not only the final committed write count. No feature may hide an O(total-scene), O(total-family) or O(total-resource) scan behind an operation whose final mutation is local unless the semantics genuinely require that wider work.
A deliberate maintenance barrier is a separate, explicit exception to ordinary local-edit complexity. Controlled compaction/repacking/rebuild may be O(live state) or O(size of an explicitly affected domain) when scheduled as a named maintenance/generation barrier with instrumentation and coherent handle/revision rules. It must not occur unpredictably on an ordinary local edit and must not be used to justify routine whole-scene fallback.
Arbitrary source-language re-execution is another explicit exception: Noon cannot promise sublinear execution of arbitrary Python/Rust/JS program logic. Hot-reload reconciliation must still ensure that unchanged semantic/execution/runtime/renderer state is preserved and that re-executed authoring work does not imply whole-scene lowering or GPU replacement.
The expected propagation of a local change is therefore explicit:
D2 source. Effective-only driver writes bypass authored relowering and join at affected runtime state. Authored structural/resource changes perform only the required local preparation. Unrelated semantic identities, execution slots, retained resources, and GPU ranges remain untouched unless observable semantics genuinely require wider materialization.
8. Mutations#
Persistent authored/live structural and property changes use one semantic mutation vocabulary and atomic transaction model.
Conceptually:
MutationTransaction
SetProperty
SetSignal
ReplaceContent
AddNode
RemoveNode
AddMember / RemoveMember / ReorderMember
AddAnimation / RemoveAnimation
ChangeSubscription
A semantic transaction is validated before commit.
Each operation has an impact class so the compiler/runtime can perform the minimum required work:
persistent color/translation change -> semantic property slot/domain update
signal declaration/base change -> semantic/reactive impact
path/content replacement -> prepare + replace affected resource
add/remove/reparent -> local structural update + bounded relowering
large semantic rewrite -> wider relowering only when genuinely required
Host callbacks, editor actions, graph topology updates and hot reload should reuse this machinery for authored semantic changes rather than inventing separate semantic patch systems.
Effective driver writes vs authored semantic mutations#
Ordered runtime evaluation may produce two different classes of staged output and the architecture must not conflate them:
This is the publication contract for supported operations, not a claim that every mutation kind in the roadmap is implemented. Today noon::ExecutionSession coordinates preparation and commit; noon-compile owns local lowering and root-relative membership/painter-order preparation, and noon-runtime owns effective execution. A failed preparation leaves the previous coherent publication live.
Both classes participate in the same semantic updater order, transactional overlay and coherent FrameEpoch publication barrier, but they have different ownership and invalidation costs.
For example, m.shift(...) executed by an active updater normally writes that updater/driver's effective transform for the current execution state; it does not automatically rewrite the authored/base transform or relower the semantic scene every frame. By contrast, structural operations such as scene.add(...), durable membership/subscription topology changes and other explicitly persistent semantic operations use the semantic mutation path. A driver release/completion may reconcile its effective value into authored/base state when compatibility semantics require persistence.
The exact classification is semantic, not purely syntactic: the same frontend method name may represent ordinary authoring outside active execution and an effective driver write when invoked inside an updater. The shared Rust semantic/runtime layer owns that distinction so frontends cannot diverge.
Atomic semantic/execution publication#
For a staged batch containing authored semantic mutations, transaction atomicity extends through the derived work required to make the transaction executable. Validation, impact analysis, incremental lowering and any required synchronous preparation occur against staged state before the new semantic revision becomes live.
The publication barrier always publishes one coherent FrameEpoch. That epoch references the compatible SceneRevision/ExecutionRevision and includes the resulting effective runtime state, spatial/index changes and renderer-facing delta. Effective-only batches may publish a new FrameEpoch with unchanged semantic/execution revisions. A failed semantic preparation publishes no new semantic/execution revision and cannot leave the live frame referencing an incompatible pair.
Async content/resource work either remains staged until coherent publication is possible, or exposes an explicit semantic pending state whose old executable/resource version remains live. There is no accidental half-published Semantic Scene paired with an older incompatible Execution Plan.
Structural publication barrier#
Structural mutation must not invalidate the execution revision currently being traversed. Host callbacks, native/editor producers and hot reload may enqueue structural changes during an evaluation epoch, but those changes become visible only through the validated atomic publication barrier with deterministic ordering.
Property/value changes may have specialized effective-driver fast paths when their ownership/ordering semantics make that safe. Structural add/remove/reparent/reorder, subscription topology and other changes that alter traversal/dependency shape use the shared semantic transaction/publication contract rather than mutating containers beneath an active iterator.
Transaction-local/provisional identity#
Arbitrary structural callbacks require read-after-write behavior for objects that do not yet exist in the published Semantic Scene. A staged transaction must therefore support an equivalent of transaction-local/provisional identity.
Conceptually:
reserve provisional handle / identity
AddNode(provisional)
SetProperty(provisional, ...)
AddMember(..., provisional, ...)
read provisional state through callback overlay
...
validate + lower/prepare
atomic publish or abort
The exact representation may be a reserved generational NodeId, a transaction-local token that is promoted at commit, or another implementation with equivalent semantics. Before publication it is visible only through the staging/callback context, cannot alias a published node, and cannot leak into unrelated runtime/renderer state. On successful commit it resolves to a stable published semantic identity without changing the host-visible logical object; on abort, any purely provisional identity/resource is invalidated and cannot later become observable as a live node.
This capability also lets a callback construct, mutate, inspect and then attach a new object coherently without half-publishing it between operations.
A failed transaction publishes nothing. Provisional handles/resources created only for a failed transaction must not remain observable as live semantic state.
9. Host callbacks and interaction#
Native interaction is preferred when semantics are known. Traceable host expressions should lower to native execution when their semantics can be captured faithfully. Arbitrary host behavior remains a supported first-class execution class and explicit fallback.
Native interaction path#
Ordinary pointer/keyboard/viewport/control behavior should be expressible as runtime inputs feeding the same native reactive graph as other signals:
platform input
|
normalized sampled state / ordered events
|
native signals + ordered reactive/updater evaluation
|
mutable execution slots/domains
|
retained renderer
When behavior is fully native, including behavior successfully captured into native expression/reactive IR, no Python/JS host callback phase is required after authoring.
Ordered transactional host callback overlay#
Arbitrary host callbacks need imperative read-after-write behavior without forcing one cross-language commit per getter/setter. Host execution participates in the same semantic updater/effect order as native regions.
The ordered callback barrier is a transaction, not a sequence of scalar bridge calls:
D2 source. The callback invocation is pinned to one coherent scene/execution/frame context. Reads consult pending overlay writes first and then the revision-pinned read view; writes accumulate in one StagedUpdateBatch as effective driver writes and/or authored semantic mutations. Fallible semantic/resource preparation completes before the next publication becomes visible.
The shared session can commit a required callback's effective writes and a semantic transaction together through commit_required_callback_transaction. Provisional construction and reads use the existing prepared semantic transaction; validation or lowering failure leaves both the published frame and the pending callback token unchanged. Scoped translation, rotation, scale and style writes retain their order and ownership, so completion does not overwrite unrelated animation channels. Python returns these scoped effective writes and may stage Scene.add, Scene.remove, and Scene.clear for existing typed handles in the same required callback. One token-pinned Rust collector owns the ordered semantic transaction; staged membership reads consult its prepared overlay, and Python wrapper attachments finalize only after publication succeeds. A rejected individual staging operation preserves the accepted prefix. Bounded Python analytic and retained-path construction uses transaction-local tokens; immutable path payloads enter the existing resource admission scope only for the final publication. Ordered native/host regions share that one phase transaction and expose earlier regions’ effective writes to later reads. Wrapper finalization is acknowledged before the player advances or returns to its authoring context. Provisional text/family construction and arbitrary effective-content replacement remain follow-up work. Active animation segments retain their structural-publication gate.
If compatibility semantics require updater order native A -> Python B -> native C, callback B observes A's effective writes and C observes B's overlay writes regardless of whether B produced effective driver writes, authored semantic mutations or both. Structural changes still do not mutate the traversal currently being enumerated; their structural visibility follows the staged publication rule.
Do not cross Python/WASM once per property getter/setter. Callback requests should contain or bulk/lazily obtain the host working set needed for the callback phase, and the returned result should be one bounded staged-update/resource transaction. Arbitrary host code must not silently degrade into thousands of synchronous scalar bridge calls per frame.
Cross-context callback read view#
At a genuine worker/process/context boundary, Noon may maintain a versioned, non-authoritative callback read mirror/cache containing callback-readable effective state pinned to the relevant SceneRevision/ExecutionRevision/FrameEpoch. It exists only to make coherent host reads efficient; it is never scene or runtime authority.
Callback writes remain in the local staged overlay and are returned as one transaction/update batch. A complete mirror may contain all callback-readable state judged appropriate for that boundary; a partial mirror may cache only likely/read-set state.
A read miss from a partial mirror has explicit semantics:
- if the host/runtime can suspend the current callback invocation at the missing read, it fetches the missing revision-pinned value and resumes that same invocation;
- if suspension is unavailable, the callback fails deterministically rather than receiving stale/undefined data;
- automatic restart/replay of the callback is permitted only when that callback is explicitly classified replay-safe/idempotent for the relevant host side effects;
- Noon transaction rollback/atomicity applies to Noon semantic/runtime/resource state, not to arbitrary external host side effects such as file/network I/O, printing or unrelated host-global mutation.
The implementation must therefore never treat arbitrary callback restart as an invisible cache-miss mechanism. Declared read sets, observed-read caching and batched prefetch remain optimizations around the same revision-pinned read semantics.
If no host callback slots exist, playback must not require the host interpreter.
Input/event semantics#
Input semantics distinguish:
- sampled state, where latest value wins for the exact source (pointer position, viewport size, continuous controls);
- discrete ordered events, where occurrences must not collapse (press/release/click/key and similar events).
Paused scenes may react to input without advancing authored timeline time. Input events carry explicit InputSequence ordering and, where relevant, timestamps in a defined clock domain.
Ordered discrete-event storage is bounded. A required host-callback stall must not imply unbounded memory growth while discrete events continue arriving. Producers/transports that can be throttled should apply backpressure before the bound is exceeded. For sources that cannot be backpressured, overflow must produce an explicit surfaced overflow/session-fault policy; the implementation must not silently coalesce, reorder or discard required discrete events while claiming preserved semantics. Event classes may be loss-tolerant only when that property is explicit in their semantic definition.
Driver arbitration#
Timeline, native reactive, host callbacks and editor/direct manipulation are distinct property drivers. They must not silently compete through unspecified global last-writer-wins behavior.
The runtime must define acquisition/release/suspension/rebase/cancel rules for conflicting drivers. Exact policy may differ by semantic operation, but ownership and conflict behavior must be explicit, testable and compatible with supported Manim updater/animation behavior.
Animation activation must also define when start/target state is captured. Live mutations before activation cannot be ignored merely because an animation declaration was lowered earlier. Authored animation intent, activation-time state and active execution channels are distinct concepts where semantics require late binding.
Source reload scope#
The normal Python Run action restarts the scene. Source hot reload is a separate live-authoring feature owned by #64; it is not required for native input, callbacks, direct manipulation or local runtime/content changes. A future live-reload implementation must define stable source identity, driver conflicts and preservation rules before wiring source reruns to the active session. Temporary source candidates may compare declarations, but cannot become another live scene or replay arbitrary Python callbacks to recover from a failed edit. Keep the current explicit restart behavior until that separate contract and its qualification are ready.
Replay and host latency#
Host-dynamic behavior must be classifiable as replayable/deterministic, history-dependent, or externally/non-replayable as needed for seek/offline semantics. The broader replay policy also covers native input/event streams, editor manipulation and external async/network results as described in the time/determinism contract.
Required ordered compatibility callbacks are non-coalescible by default. Authored/simulation time cannot advance past a dependency barrier whose required callback result has not completed, because later effective state would otherwise be semantically undefined. Slow required Python therefore causes poor simulation/frame rate, not reordered or missing updater semantics.
While a required host callback is outstanding, presentation may continue showing the last coherent published frame and sampled input may continue coalescing to its latest state. Discrete events remain ordered within the bounded queue/backpressure policy above. Only host work explicitly classified with latest-value/coalescible semantics may be superseded or dropped.
Callback results carry the exact callback/revision context required to prove applicability. A stale result cannot overwrite newer state. Offline execution may wait for required host behavior; realtime presentation may apply the defined stall/backpressure policy, but it cannot advance authored state through a required ordered barrier.
10. Geometry, text and resources#
Semantic content remains high-level where useful; compilation chooses the cheapest retained representation.
Examples:
- simple shapes: analytic/instanced where profitable;
- arbitrary static paths: cached tessellation;
- morphs/reveals: precomputed compatible geometry/arc-length data;
- text: shaped glyph runs and atlas rendering by default;
- text outlines: lazy, only when path-level semantics require them;
- images/meshes: immutable retained resources with localized replacement.
Transform/style/visibility-only changes must not regenerate immutable content.
Text defaults use bundled fonts consistently across native and browser hosts; host font discovery is not an implicit compatibility rule. Controlled Manim appearance comparisons must select an explicit matching font and retain its file hashes and shaping/raster backend. An unchanged example that omits a font remains covered, but its machine-selected reference appearance is recorded separately from qualification with the explicit font profile. Missing fonts fail qualification rather than silently selecting a fallback.
Renderer-local fill coverage may specialize a convex triangle or quadrilateral as one polygon, computing its pixel-box intersection once to avoid internal tessellation seams and multisample edge loss. Morph eligibility must prove convexity throughout the interpolated interval, including any removed collinear subdivisions; endpoint checks alone are insufficient. Eligible paths retain immutable endpoint vertices and GPU interpolation. Other paths keep ordinary tessellation or the sampled-mesh specialization below. This coverage choice does not create semantic geometry or a second source of animation state.
Glyph masks are disposable renderer resources derived from shared shaped runs. Device density and camera origin may select a bounded raster-size and subpixel-phase cache key without reshaping text or rewriting semantic resources. An eligible translated text object replaces its glyph masks in reusable object-local instance ranges; unrelated objects and uploads remain unchanged. A phase change may make a glyph empty or nonempty or change its atlas batches. Stable glyph-run submission entries resolve the current batch slots, so these transitions preserve painter order without rebuilding unrelated text. Vacated ranges and batch slots are reused under repeated phase churn. Atlas exhaustion still replaces the atlas generation and rebuilds text packing; that resource-pressure fallback remains a C4 locality limitation and is counted separately from ordinary object-local updates. Rotated, reflected, skewed or nonuniform bitmap transforms retain the filtered path. Cache limits remain independent of animation duration, and camera changes invalidate device-dependent preparation coherently.
Immutable versioned resource rule#
Expensive geometry/text/image/mesh realization should be represented as immutable/versioned resources wherever practical. Mutable object state references those resources rather than embedding repeatedly rewritten heavy geometry in ordinary frame state.
Morph lowering may prepare immutable endpoint geometry in a fixed rendering coordinate frame, with a derived render transform separate from the effective semantic transform. This specialization must preserve world-space point correspondence, stroke behavior, channel precedence, and spatial queries. The current screen-space specialization requires finite, safe endpoint geometry and an invertible interpolated scale; unsupported cases retain current-relative evaluation. Independent affine drivers release the fixed frame before taking ownership, and exact morph endpoints restore source/target content without a stale render override. Native execution shares the compiled geometry directly; a genuine worker boundary installs its session-scoped resources once and sends references with per-frame progress, rather than reconstructing or retransmitting paths on each tick.
A filled path whose changing shape has no safe time-invariant triangulation uses a renderer-local sampled-mesh specialization. The same immutable ordered endpoint resource and scalar progress remain authoritative; canonical cubic correspondence is cached once on first use. Only the affected presentation row is sampled and tessellated, using ordinary fill rules. One disposable mesh per stable row or identity-free derived occurrence is reused across progress values, and local arena ranges and painter chunks are updated without rebuilding unrelated state. Such a mesh is never installed as an immutable resident prefix or shared between independently progressing occurrences. Safe fan and stroke-only morphs retain GPU endpoint interpolation, and an unchanged progress/style/geometry sample performs no tessellation. This correctness path has work proportional to the changed path complexity, not the total scene; it must not become a frontend per-frame path loop or a sequence of new cross-worker geometry resources.
Installed immutable geometry may carry one-shot preparation hints containing its actual tessellation style and render transform. The renderer resolves and deduplicates its full mesh keys, tessellates eligible paths, and uploads a resident geometry prefix before playback readiness; ordinary frames still use the same authoritative mesh-key lookup and lazy fallback for unsupported or changing inputs. Preparation must not advance or sample the timeline. Scene installation replaces this disposable residency coherently, and the browser suspends animation ticks through preparation and initial presentation, including reruns and renderer transitions.
Replacing persistent semantic content prepares a new ResourceVersion, validates it against the relevant semantic/source revision, then publishes the new reference only as part of a coherent semantic/execution publication. Failed or stale preparation leaves the previous live version intact.
Runtime/effective content drivers, where supported for compatibility such as redraw-style behavior, must use an explicit effective resource/publication path rather than implicitly rewriting authored semantic content every frame. They remain subject to resource-version, stale-result and retirement rules, but do not create a SceneRevision merely because the effective resource changed.
A held effective resource is resident state, not recurring frame work. Preparing an unrelated frame must not visit every held content lease or copy its path payload; only changed objects and their dependents may be prepared.
The initial runtime content lease publishes a validated replacement into one effective row without relowering the compiled scene. Initial acquisition pins the exact publication; later versions require the lease and matching authored/execution revisions, while unrelated effective frame advances may continue during preparation. The lease version rejects results after another content version commits; a producer whose output depends on authored time or other effective inputs must also reject obsolete source samples. Release reveals the latest authored content. Acquisition excludes numeric text, Graph-owned rows, and targets with Morph, Transform or family-animation tracks. If one of those authored owners is installed later, that publication retires the lease. Existing compiled text/image handles and inline geometry can be referenced. A producer-owned image arena may supply a new immutable image to a prepared lease result; runtime lookup and renderer publication see it only after commit, and the last referring lease retires it on replacement or release. New text and external geometry still need dependency-aware resource admission. Held leases do not enter the ordinary forward-frame loop, and a full seek may revisit them as part of its deliberate whole-scene rebuild.
Async preparation normally remains staged while the old resource/execution version remains active. If a feature deliberately publishes a semantic pending state, that state is explicit and queryable; it does not imply that incomplete resource data has become the active execution representation.
Renderer/GPU lifetime is decoupled from semantic publication: an old resource version may remain physically resident while already-submitted GPU work still references it. Resource retirement occurs only when the relevant SubmissionSerial/fence indicates it is safe. New content must not overwrite buffers/resources still referenced by in-flight frames.
For managed wgpu handles, renderer/cache eviction drops Noon references while wgpu and its backend track submitted use and ordered queue writes; this is logical eviction, not proof of immediate physical GPU destruction. Additional Noon fencing is needed only when Noon reuses or externally owns resources beyond those guarantees.
Semantic resource retention follows durable object references, including detached objects and numeric-text tokens. Each admitted text resource retains its font and vector dependencies even before attachment. Replacing content or removing the final semantic owner queues only the affected resources for reclamation after the complete transaction and any enclosing resource-admission scope succeed. Resource handles are versioned lookup capabilities; retaining a copied handle alone does not retain retired content. Compiled execution snapshots retain their own immutable dependency closure so reclaiming store entries cannot invalidate an older execution revision.
Long-running interactive/hot-reload churn must have a bounded reclamation/compaction strategy. Stable/tombstoned execution identities and retained caches are allowed, but memory usage must not grow indefinitely with historical mutations when the corresponding semantic/execution/resource state is no longer live or deliberately cached. Explicit maintenance compaction may use the deliberate maintenance-barrier exception defined above.
The current explicit execution maintenance barrier prunes superseded compiled resources even when no object slots need relocation. Resource-only pruning advances the execution/frame publication and requests one presentation refresh without rebuilding retained rows, so older prepared work and displayed input context cannot silently cross the barrier. Active effective-content leases are additional resource roots, so one held version does not pin unrelated stale entries; active interactions, drag captures and replay projections defer this barrier, while stable property drivers do not. Ordinary track geometry dependencies and every compiled numeric-text token dependency are traversed before pruning; family/Graph derivation still retains its full closure until its dependencies can be traversed safely. This is a partial reclamation path; general long-churn resource retirement still needs qualification.
Text, Graph, 3D and interaction are features of the same scene/runtime architecture, not separate scene engines.
11. Browser topology#
The browser must support two distinct integration shapes without confusing them.
Direct Rust/WASM authoring and execution#
For a Rust-authored application compiled to WASM, the preferred topology is one typed in-process Rust path:
See the direct Rust execution diagram (D2 source). The same semantic/compiler/runtime/renderer responsibilities execute in a single WASM context; only platform lifecycle crosses into JavaScript.
No transport representation exists between these layers merely because the target is the browser. JavaScript may load the WASM module, provide the canvas, and participate in browser-specific lifecycle glue, but it does not receive and re-send scene or runtime state between the Rust layers.
A play()/wait() barrier in this topology must not require synchronously blocking the browser main thread. The same logical segment-completion contract may be exposed through async/yielding host mechanics.
Host-language or multi-worker topology#
Arbitrary Python should stay away from the render frame loop. The current semantic Python path uses the following placement:
The current implementation keeps the semantic context and its existing compiler/runtime session together in the authoring worker. semantic-engine-endpoint.js leases the context's existing player through createExecutionPlayer / returnExecutionPlayer; it does not transfer a Rust player between WASM instances or create a second runtime. python-worker.source.js supplies Python continuation and required callback invocation.
authoring-render-controller.js owns render attachment, resource installation, delta consumption and presentation using RetainedExecutionCanvasRenderer. It runs in authoring-render-worker.js, with MainThreadRenderWorker as the platform fallback. The legacy execution-render-worker.js URL forwards to that render-owner implementation; its name does not establish another authoritative runtime. Resources and execution deltas cross a MessagePort/transferable-buffer or shared-mailbox boundary; ticks and readiness/presentation acknowledgements are host coordination, not timeline truth. The shared mailbox retains two fixed physical slots. A larger encoded delta may occupy successive chunks under the existing transport credits, with at most one retained oversized packet per producer/receiver and a 64 MiB encoded-packet cap. The renderer validates and applies only the complete original delta; partial chunks grant neither semantic admission nor presentation receipts. This bounds retained transport packets, not source JSON, encoder temporaries, total process memory or GPU storage.
Required Python callbacks can hold authored progress at the shared barrier while the last coherent frame remains presentable. Playback with no host callbacks does not require per-frame interpreter execution. Source reruns currently replace a session; this diagram does not claim incremental hot-reload identity preservation.
The finite deterministic viewer completes source preparation before advancing playback. Completed prefixes still use the existing bounded Rust history reader, but its initial sample and clock remain held until actual terminal adoption. The first playback therefore has the full validated timeline and uses the same completed-history path as subsequent replay, with no Python preparation competing for frame or control delivery. Preparation yields only keep loading cancellable; they do not pace animation. Pause intent survives preparation, and loading time never becomes elapsed animation time.
The authoring host's explicit positive preparationLookaheadSeconds retains its
cooperative prefix-delivery contract for bounded streaming/reference hosts.
The viewer default is null, meaning complete preparation before playback.
This choice neither changes source admission nor promises independent execution
of a Python producer and reader sharing one worker.
An actual callback/native-input requirement releases this preparation hold and uses the original source at its existing frontier-bound handoff. Recoverable source failure retains only the accepted prefix. A source that never completes remains visibly preparing and cancellable; neither a duration hint nor a timeout manufactures a terminal timeline or silently starts source-paced playback. Concurrent streaming across independent WASM contexts would require a separately qualified execution history transport; the renderer delta channel is not such a transport.
A WebAssembly.RuntimeError crossing the Python authoring adapter retires that
worker through its existing fatal-error channel. Rust borrows and publication
state cannot be recovered after a trap; queued requests and late results are
discarded, and the next Run creates a fresh host. Ordinary Python and typed
Noon errors remain recoverable within the existing host.
Exact worker placement remains an integration decision, not a semantic boundary. A future placement that separates semantic authoring from execution must still satisfy the same ownership, callback, revision and atomic-publication contracts.
A browser worker/process boundary may require a typed transport representation because it is a real cross-context boundary. That transport is derived from authoritative semantic/execution state and must not become another scene model.
The retained execution channel may send changed style, transform, and morph fields for an existing row in a dense incremental publication. The receiver validates the session sequence, slot generation, object identity, and the whole patch set before applying it to the same retained frame mirror used by full rows. Snapshots and changes to content, resources, structure, or family plans retain their complete transport form. This is a wire-size optimization at the worker boundary; it does not add semantic state or a second execution model.
Effect-bearing rows use the same full-row worker transport when their effective parameters change. Retained protocol v30 carries an ordered effects list with each original semantic attachment generation and its checked effect definition. No-effect rows omit the list. Snapshot and incremental receivers validate the complete bounded stack, identities, numeric values and supported planar source profile before installing anything or consuming a sequence number. Older protocol versions and the former singular effect field fail closed, so a worker cannot silently discard bindings or change their order. This is derived wire data at the existing genuine Python/render worker boundary, not an in-process scene representation. The validated worker mirror reborrows that same effective runtime frame and installed immutable resources for the retained renderer. Coherent capture and filtering execute on the existing encoder after ordinary retained GPU upload; they do not round-trip through a host effect store. Supported Python playback is qualified through the actual authoring/worker channel separately from transport fixtures.
Custom pointwise effects use that same boundary: an immutable WGSL source and checked schema transfer once as a resource; changed rows carry only its derived handle, typed values and seed. Copied attachments share the installed program. The receiver validates source and values through the shared Rust API before committing resources, frame or sequence. Final references retire the program; a later attachment needs a fresh resource key. Native and direct Rust/WASM execution keep their opaque in-process program values and use no such codec.
Composed worker rows reference immutable source plans in resource protocol v16. Their derived key is the existing session, full owner identity and originating envelope sequence. Parameter rows reuse that key without copying member lists. New plans must bind a row in that publication; retired keys cannot be rebound by a later resource addition. The receiver borrows installed rows and sparse final row/order overlays into the compiler's shared source graph validator before committing frame, resources or sequence. Contiguous unaliased planar inputs, nearest nested parents and complete final references are checked together, even when parents or members arrive later in the same delta. The initial qualified owner profile has identity transform, full presence and opacity one; attached scopes require drawable inputs. Existing resource residency accounts for plan replacement and exact retirement. Removed frame rows preserve their identities but release the source allocation. These are derived worker resources and dependency indices; native and direct single-context WASM retain their typed in-process plans. Python group authoring and browser pixel qualification require their own integration evidence.
JSON may exist for debugging/export/tests or an explicitly justified external boundary. It is not the normal typed Rust authoring API, not an internal Rust layer boundary, and not a per-frame mutation protocol.
A transport/mirror used for a real worker boundary must not become mandatory for a direct Rust/WASM scene executing in one context.
Cross-context host callbacks and authoring should batch operations/read views/resource payloads so bridge call count and transferred bytes scale with host-relevant work rather than with total scene size or scalar property count. A versioned callback read mirror/cache is permitted only at a genuine cross-context boundary and remains derived/non-authoritative.
12. Renderer and platform-host contract#
Renderers consume runtime/execution state; they do not own semantic truth.
Required renderer properties:
- retained GPU residency;
- stable resource versions;
- dirty-range uploads;
- painter-order correctness;
- visibility/culling driven by execution-owned bounds/spatial data;
- no retessellation for transform/style-only changes;
- derived render grouping/instancing allowed where semantics permit, without becoming semantic authority;
- versioned resource publication/retirement safe for frames already in flight;
- no synchronous GPU-to-CPU readback on the normal frame/input/host-callback path;
- WebGPU and supported fallback backends must agree semantically and visually within reviewed tolerances.
Zero-contribution geometry is suppressed only at shared GPU draw submission, using an exact derived index of effective packed paint. Semantic presence, resident slots and source painter anchors remain intact, including anchors for independently visible transient effects. Only dirty packed rows update the coalesced exclusion index; clean frames do not scan instances or rebuild painter order. Packed unique paths use index spans at existing mesh boundaries. Opacity restoration uses the current publication; no visibility epsilon or host-side camera workaround exists.
noon-render-wgpu owns reusable retained GPU rendering. It may own renderer camera uniforms, GPU viewport dimensions, retained GPU resources, preparation/upload logic, command encoding, and renderer-local caches.
It should not own platform/application lifecycle simply to make one target convenient. In particular, the reusable renderer is not the authority for:
- native OS windows or browser DOM/canvas ownership;
- application event loops;
- realtime frame scheduling policy;
- wgpu surface acquisition/recovery policy;
- platform resize/event translation;
- final queue submission/presentation ownership when those belong to the host shell.
Runtime-to-renderer publication#
The runtime should publish compact renderer-facing changes plus immutable resource references rather than reconstructing/copying a second complete render world every frame:
published FrameEpoch N+1 renderer/GPU FrameEpoch N
| |
+-- changed slots/ranges/resources -------> retained records
presence/order/camera revision |
v
GPU work
Each renderer-facing publication is attached to one FrameEpoch, and that frame epoch references its SceneRevision/ExecutionRevision. Renderer caches may therefore invalidate on the narrow revision/version actually relevant to them rather than treating every frame as a new semantic/execution generation.
For the supported planar glow profile, native, direct-WASM and native-capture hosts use the same renderer-owned visibility expansion and retained preparation. The sparse index records only existing runtime source slots, object identities and their current geometry-scratch correspondence; parameter values are always borrowed from the coherent runtime publication. Initialization or an explicit full rebuild examines the frame, while sparse updates inspect the published dirty rows. No-effect visibility retains the original input slice. When every glow source is already a candidate, inverse painter ranks and binary searches also preserve that input slice without a complete candidate scan. Only expansion for additional sources constructs and orders a union; its work depends on the candidate set plus the glow sources, not just the dirty rows. Effect source candidates are added in the runtime's painter order so a source outside ordinary semantic bounds can still contribute a visible halo; the padded tile planner performs the final contribution clipping. Semantic bounds, layout and picking are unchanged.
Automatic preparation proves membership in the actual retained geometry/text frame before capturing a source. Mixed text/geometry uses the existing source-to-scratch and painter-rank mappings rather than assuming that source and packed indices match. Clean publications issue no glow GPU work, and view changes reconsider only glow sources. A host that abandons encoded work invalidates the disposable capture/filter caches before retrying. The current renderer-owned texture budget includes source capture and filter textures, not driver metadata or resources retained by frames already in flight; queue submission, fences and device recreation remain host responsibilities. The retained render-worker host now uses this same preparation and painter path, with an explicit validated transport-frame borrowing boundary and a no-glow fast path. A new worker session retires the old GPU publication epoch; removal retires old scopes.
Planar geometry (including stroke-only paths), Text/MathTypst and immutable raster images use their canonical per-object draw ranges for isolated capture. Create/Write captures the currently revealed coverage through the ordinary geometry and glyph pipelines at the same publication epoch. Fully revealed filled circles/rectangles retain their existing single-sample analytic capture; general sources resolve four-sample capture into RGBA8. The texture budget charges that retained multisample target as well as source, optional silhouette, and filter images. Final scope opacity is removed from isolated instances and applied once after filtering. Painter placement replaces the entire source range once, and bounded path compaction stops at that anchor. Source capture contains no glow parameters and can serve future filters without another semantic store or renderer scene graph. Parallel Create/text reveal/Write and effect-only target edits share one admission; glyph-driver conflict checks permit only animations whose actually lowered channels are effect parameters. Competing introductions and geometry/pose edits retain atomic rejection. Insets, 3D, composed-group effects, and content-changing morphs remain outside this profile. Native/direct host correctness tests do not themselves qualify end-to-end Python worker playback, browser rendering, or physical-device performance, and public finite-profile Scene admission does not itself establish those results.
Ordinary planar sources admit an ordered stack of at most eight effect bindings. One immutable compiled column holds the ordered generational identities and definitions; unchanged topology publishes that complete column, while topology edits validate the previous identity sequence and retire only removed channels. Native and direct Rust/WASM borrow this column in process. Only the genuine worker boundary encodes its ordered values, with complete validation before publication. Runtime drivers address the exact binding generation and parameter lane, so two effects of the same kind can animate independently. The renderer borrows the coherent column and retains disposable per-pass caches: each painted-coverage pass consumes the preceding output, silhouette Glow retains the original geometric coverage, and cumulative capture padding preserves every intermediate footprint. Object opacity applies once at the final placement. Neutral stacks restore ordinary drawing, and clean publications perform no filtering work. This leaf-source profile does not admit composed-group isolation.
Compiler/runtime admission for composed scopes uses one non-drawable execution row for an effect-bearing unrendered family. Its immutable source plan names existing drawable execution identities in actual painter order and the nearest containing effect scope. The family retains its semantic identity; Rust's ordinary object-or-family target accepts the same named effect controls. Parameters use the ordinary exact-attachment channels and publication barrier. Initial admission rejects empty, aliased and externally interleaved sources before changing the execution index. Ordinary unrendered families allocate no execution row. Renderer publications borrow the publisher's existing object index to resolve source inputs locally.
Shared execution reachability includes these controller rows. First attachment admits a prepared row, last removal retires it, and family detach/re-add preserves semantic identity while retiring/restoring the complete execution subtree. The held semantic transaction exposes checked final parent adjacency alongside final member order. Local membership, order and attachment-boundary edits relower affected scope inputs and nearest parents; compatible parameter edits do not traverse members. Conservative Runtime preflight includes actual candidate entries, source-plan edits and painter changes before semantic commit. Native public Scene/LiveSession pixel tests qualify mixed images and geometry through nested attachment, neutral/removal, member addition/removal, detach/re-add and clean reuse. Python family controls invoke those same Rust operations through checked family handles and the canonical live player without frontend member traversal. Live family copying borrows the published attachment column and allocates independent semantic attachment generations. Equal-topology family transforms and method targets stage ordinary geometry leaves alongside effect-only semantic leaves for unrendered scope owners. Those leaves use the existing allocator, scheduler, exact attachment channels, activation captures and completion barrier; they do not fabricate geometry for controllers. Scope parameters share the family root clock while member lag remains within the geometry expansion. Explicit parameter writes reserve their precise attachment generation, and sequential prepared targets consume completed parameter endpoints. Matching effect-bearing families, unequal effect-bearing topology, attachment enrollment/removal and unit transitions remain outside this animation profile. The explicit reverse-rate flag on the internal family composition remains unsupported. Broader lifecycle/seek/host cohorts remain separate gates.
The compiled plan indexes only derived input and parent dependencies by full existing identity. Initial materialization and authored execution transactions validate live drawable references, contiguous input order and acyclic nearest-parent containment before publication. Transaction preflight borrows unchanged rows and stages only changed metadata and displaced painter positions, so a source plan and its input/order edits can commit together without cloning scene payloads. Compatible parameter edits do not walk source members. Replay exchanges and retired-row compaction preserve the same identity-based dependencies. Non-drawable controller rows stay after the drawable painter domain during native evaluation and seek; they cannot split a capture interval.
The composed renderer profile captures contiguous, fully present source intervals and replaces each outermost active interval at its first member's painter anchor. Nested scopes, including equal intervals, prepare from leaves to ancestors. Painted captures borrow completed child filter textures as integer-aligned draws through the existing mixed source encoder; silhouette captures retain the original declared coverage. Child opacity and external painter order survive both paths. An active ancestor can demand a wholly offscreen child output before final viewport clipping; neutral parents do not demand absent offscreen storage. Disposable reverse input and parent indices invalidate the affected capture closure. Controller parameter rows have no geometry scratch, and compatible parameter-only edits borrow the prepared source. Neutral parents release obsolete child-output snapshots and bindings, while unchanged warmed captures remain reusable. Native pixel oracles qualify filtered-child composition, mask provenance, offscreen contribution and neutral transitions; structural tests cover clean work, retirement and warmed storage reuse. Visibility expansion still depends on source members and candidates. Partial scope opacity and visibility-animation presence changes remain unqualified under #1897. Separate original-mask revisions, effective ordering drivers and broader lifecycle/performance cohorts still need qualification. The genuine worker boundary installs immutable composed plans through its existing resource transaction, validates full input/parent generations and final painter intervals, and commits plan bindings with the frame and publication sequence. Paired Rust/WASM and Pyodide-worker examples qualify finite nested mixed-source controls on WebGPU and WebGL2; their independent outer-filter reference shares source rasterization and inner-filter coverage.
Raster-image capture borrows the existing image texture and sampling pipeline with a disposable normalized instance binding. Intrinsic sampled texel alpha is image coverage in both modes; painted coverage also includes fill modulation, while silhouette coverage ignores that modulation. Final object opacity and appearance apply once after filtering. Immutable pixel resources, source painter anchors and ordinary no-effect instances remain shared and unchanged. Image source preparation remains separate from glow parameters and does not introduce image fills inside vector geometry or a general custom-shader API.
Filtered child captures expose their finite contribution bounds separately from rounded, retained texture capacity. Ancestor capture bounds consume only the contribution, so repeated nested parameter animation cannot feed transparent high-water padding back into source extents. Native animated mixed-source checks use literal premultiplied source fields and independent two-dimensional convolutions, require pixel-exact same-time seek, and check local forward invalidation and bounded warmed texture reuse. Absolute seek currently restores the runtime base frame and marks the restored frame dirty; its pixel determinism is qualified separately from forward-playback locality.
Continuous local gradient fills use that same isolated vector draw path for glow. Capture borrows the ordinary gradient program and retained paint binding, with only final scope opacity removed from its instance. Painted coverage includes stop alpha and fill opacity; silhouette coverage uses the unpainted geometry. The immutable paint definition participates in source invalidation even when its packed carrier row is unchanged. Parameter-only glow edits reuse the source and blur. Mixed-frame scratch identities are resolved through the canonical per-object draw range, rather than assuming they equal semantic object IDs.
A backend may internally use render bundles, display-list-like recordings, bind-group caches, instance groups or other derived accelerators, but those are disposable renderer optimizations. A recorded drawing command stream is not the Execution Plan and cannot replace semantic/runtime data needed for mutation, hit testing, reactive dependencies or live authoring.
Where backend/host support allows it, CPU/runtime work for a later frame may overlap renderer/GPU work from an earlier frame as long as FrameEpoch, ResourceVersion and SubmissionSerial rules make publication and lifetime coherent.
Any execution channel whose value is evaluated on the GPU but is also required for CPU-visible semantics such as hit testing, bounds, native reactive dependencies or host queries must have a defined CPU-observable source/reference path. Noon must not make synchronous GPU readback the mechanism for ordinary semantic observation.
Platform integration shells provide lifecycle mechanics and reuse the same renderer:
The direct Rust execution diagram shows both platform shells around the same typed engine. Both hosts own surface/device/queue configuration, resize and input ingress, and acquire/submit/present/recovery policy. They reuse noon-runtime and noon-render-wgpu; neither owns another scene or scheduler.
Cold browser renderer startup precedes admission of a source's realtime epoch. The first WebGPU submission must complete and pass GPU diagnostics before its presentation receipt can admit playback. This is one asynchronous startup barrier per renderer generation, not a display-scanout claim or a per-frame queue fence. Later source segments, callbacks and presentation barriers preserve the existing Rust wall-to-authored-time mapping; they do not restart it.
Whether native host integration deserves a separate crate such as noon-native or remains a module is a dependency/compilation decision, not an architectural naming requirement.
The renderer is usable directly from the native Rust runtime and through web integration. Neither platform host owns renderer semantics.
Renderer-specific mirrors and caches are derived and disposable. Compact-path submission may combine a bounded window of mutually disjoint raster bounds while retaining shared meshes and contiguous instance ranges. Bounds derive from cached source/target tessellation and current transforms, with a pixel/rounding guard; overlapping, unknown, sampled-morph, and exact-polygon cases retain ordinary painter order. Text, images, and inset composition remain ordering barriers. This is disposable draw compaction, not a semantic reorder or a new scene representation.
Fully revealed, small retained strokes with straight centerlines and polygonal caps/joins use the shared perimeter pixel-area integral. Renderer preparation recovers convex pieces from the existing tessellation; GPU projection supplies the pixel guard, preserving retained geometry across view/pose changes. Partial reveals recover the original triangles and progress interpolation. Packed unique paths retain painter order and coalesced instance updates. Draw batches keep ordinary, convex-fill and covered-stroke shader profiles separate so an ordinary curve does not inherit the polygon clipping shader's cost. Curved, rounded, large and sampled morph strokes retain their existing tessellation profile. This is a bounded renderer specialization; each Manim API/reference still needs its own qualification.
13. Crate and module boundaries#
Crates exist only for real dependency, compilation-target, or reuse boundaries. The Phase A5 normalization is complete, so current ownership and the settled responsibility model are now the same architecture rather than a temporary map waiting to be renamed.
Current implementation ownership#
D2 source. This is an ownership/data-flow view, not a complete Cargo dependency graph. Refresh it when a responsibility boundary changes; do not pin it to a historical inspection SHA.
Settled responsibilities are:
noon
public Rust authoring facade and shared high-level operations
ExecutionSession / LiveSession orchestration and explicit integration surfaces
noon-core
SemanticStore, semantic identity and authored declarations / transactions
shared renderer-independent resources and engine contracts
noon-compile
SemanticStore -> derived execution projection
CompiledScene, specialization, execution mapping and publication preparation
noon-runtime
SceneInstance, effective state, timeline/reactive evaluation, spatial state
scheduling, revisions and runtime publication
noon-render-wgpu
retained GPU resources, preparation and draw encoding for native/web reuse
noon-native
native window/event-loop/input/surface/presentation and optional audio-device dependencies
noon-web
WASM/browser bindings, canvas/frame/input integration and explicit worker transport
direct single-context Rust/WASM execution remains typed in process
noon-geometry / noon-text / noon-typst
supporting provider boundaries justified by reusable algorithms or heavy dependencies
SemanticStore deliberately remains in noon-core: compiler/runtime consumers must not acquire an upward dependency on the public facade merely to make the conceptual Semantic Scene share the noon crate name. CompiledScene remains compiler-owned, effective state remains runtime-owned, and session orchestration remains in noon.
Rules:
- do not reintroduce
noon-ir, migration scene/document models, or normal-path serialized engine bridges; - no crate exists solely for migration compatibility or naming symmetry;
- module structure must expose ownership directly rather than hide unrelated domains behind
#[path]/organizationalinclude!; - native platform dependencies stay outside reusable semantic/compiler/runtime/renderer crates;
- prefer modules over crates until an actual dependency, compilation, or reuse boundary exists;
- conceptual names such as ECS/world/session/scheduler do not justify a public crate or new state authority.
14. Correctness invariants#
- High-level semantics are implemented once in shared Rust.
- The Semantic Scene is the only authored scene authority and owns authored/base state.
- Runtime effective state is distinct from authored/base state; ordinary timeline/reactive/host/editor driver writes do not implicitly mutate authored state or force semantic relowering.
FrameEpochis the coherent effective publication unit and references oneSceneRevisionplus oneExecutionRevision; a new frame epoch may publish while either referenced revision remains unchanged.- Only a committed authored semantic mutation advances
SceneRevision; only a changed derived execution projection advancesExecutionRevision. - Rust authoring is a first-class direct API; Python/JS frontends contain handles/adapters, not scene engines.
- The native Rust path
Rust API -> Semantic Scene -> Execution Plan -> Runtime -> Rendereruses typed in-memory Rust data and requires no serialization, JSON, WASM, browser runtime, or language host. - A Rust-authored scene compiled to WASM uses the same typed in-process engine boundaries when the layers share one WASM execution context; the browser target alone is not justification for a scene/runtime serialization hop.
- Execution and renderer identities never replace semantic identity.
- Authored semantic mutation and required incremental lowering/preparation publish atomically as one coherent semantic/execution update; structural changes never mutate an actively traversed execution revision underneath consumers.
- Static regions are not invalidated by unrelated dynamic changes.
- Reactive evaluation visits only affected dependencies while preserving observable semantic updater order across native and host execution classes.
- Host callbacks observe a coherent callback revision, preserve required ordered read-after-write behavior through a transactional overlay, and may produce effective driver writes and/or authored semantic mutations without conflating the two.
- Structural callback staging supports transaction-local/provisional identity so newly created objects can be mutated/read before atomic publication without becoming prematurely live.
- Cross-context callback read mirrors/caches are versioned, derived and non-authoritative; a read miss cannot silently produce stale/undefined state or implicitly restart arbitrary host code.
- Automatic callback restart/replay after a read miss is allowed only for explicitly replay-safe/idempotent callbacks; Noon rollback does not promise rollback of arbitrary external host side effects.
- No host interpreter is required when no host-dynamic behavior exists.
play()/wait()are logical segment-completion barriers rather than a second scene/runtime mode or a requirement to block a browser event loop; native interaction may coexist with playback.- Live compatibility getters observe the latest coherent effective runtime state unless an API explicitly asks for authored/base state.
- Required ordered host callbacks are non-coalescible by default; authored time cannot advance through an unresolved required callback dependency.
- Ordered discrete-event storage has a bounded backpressure/overflow policy and never silently collapses required discrete semantics during a host stall.
- Direct seek agrees with forward evaluation wherever semantics are deterministic.
- Offline and realtime rendering use the same semantic/runtime behavior, with explicit policy for externally timed/non-replayable behavior.
- Authored/simulation time, wall/presentation time, external input sequence/timestamps and GPU submission time are distinct clock/sequence domains.
- Unsupported compatibility behavior is explicit; silent approximation is not acceptable.
- Local changes remain local through validation/preflight, lowering, spatial maintenance, render preparation and GPU publication unless semantics genuinely require wider work; deliberate maintenance barriers are explicit/instrumented exceptions.
- Serialization is used only at explicit external/cross-context boundaries and never dictates the in-memory engine architecture.
- Platform hosts own platform lifecycle only; they do not become semantic, scheduling, or renderer authorities.
- Supported common semantics implemented for both Rust and Python are demonstrable through equivalent executable examples reaching the shared engine behavior.
- Semantic hierarchy, execution grouping and renderer grouping are distinct representations; derived execution/render structures never become authored authority.
- High-fanout execution may stay compact, but observable consumers may materialize the affected leaves they genuinely require; such work must not spread to unrelated scene content.
- Heavy geometry/text/image/mesh resources are versioned/retained so transform/style-only changes do not regenerate immutable content.
- Late host/compile/resource results carry the exact revision/version context needed to prove applicability and cannot overwrite newer state.
- Normal frame/input/callback execution never depends on synchronous GPU-to-CPU readback.
- A clean settled scene can sleep and wake on relevant work without advancing authored time unnecessarily.
- Long-running live-edit/resource/event churn has bounded reclamation/backpressure behavior; memory must not scale indefinitely with historical mutations or stalled ordered input.
- Frontends share semantic behavior, not a source-language execution model: Rust may use compiled control flow while Python uses interpreter continuation and editor/live inputs use declarative commands.
- The Execution Plan is a derived, replaceable projection of a published Semantic Scene revision; it cannot become the authority for authored semantics or a frozen compilation of the entire future host program.
- Runtime-editable scene/design data does not imply runtime interpretation of arbitrary Rust source; source hot reload/recompilation and semantic live mutation are distinct mechanisms.
- Host expressions that can be captured faithfully may lower into native Noon expression/reactive IR; unsupported or effectful host behavior remains explicit host dynamic behavior rather than being silently approximated.
Roadmap#
The roadmap is deliberately short. Detailed implementation checklists belong in issues/PRs, not permanent competing plan documents.
Phase A — architecture consolidation#
Status: Phase A consolidation and the representative Phase B exit are complete. #953 and #954 record their qualification evidence. The Phase C integration and exit evidence is tracked in #955; narrower compatibility, replay, editor, and performance follow-ons continue under their owning issues.
The permanent Phase A result is already reflected in the normative sections above:
- one authoritative
SemanticStore/ semantic identity space and one persistent mutation vocabulary; - one typed semantic-to-execution lowering/publication path with impact-local preparation;
- first-class Rust authoring plus direct typed native and single-context Rust/WASM execution;
- one
ExecutionSessionorchestration surface over existing compiler/runtime ownership; - authored/base state distinct from effective Runtime state and coherent
FrameEpochpublication; - migration scene/IR/sidecar architecture deleted and serialization limited to explicit codecs or real context boundaries;
- settled module/crate ownership and structural architecture ratchets;
- paired Rust/Python and deterministic execution evidence for representative supported semantics.
Completed implementation tracks and their qualification evidence are recorded in #953 and its linked issues. Those records describe the completed consolidation; they are not unfinished target architecture. Current implementation work belongs in the owning roadmap issues rather than a second checklist in this document. Do not replay or reopen completed migration tracks.
Phase A exit: the #953 checklist is completely green with no unresolved correctness failure; then Phase B breadth becomes the default priority.
Phase B — complete common 2D semantics#
Build breadth only on the consolidated architecture.
Order:
- core
Mobject/VMobjectfamily, bounds, layout, state/copy/target and path-query semantics; - native
Text/MarkupText, Typst/MathTypst part semantics, then realTex/MathTexand numeric text; - lines/arrows/tips/dashes and remaining common shapes;
- transforms, creation/fading/composition and updater animation breadth;
- coordinate systems, plotting and labels;
- Graph/DiGraph using shared family/topology/dependency semantics;
- SVG/images, braces, matrices, tables and moving-camera features.
Every supported Python feature must use shared semantic behavior and add representative ManimCE differential evidence. Significant common features supported by both public frontends should land with equivalent executable Rust and Python examples; the Rust example is the direct product proof and the Python example is wrapper/parity proof.
Callable-backed graph input queries reevaluate the author's scalar function and
use the creating axes' current authored or effective Rust coordinate frame.
The runtime retains only the sampled path and its declared parameter interval;
queries do not resample the path or add frame work. Python t_min/t_max read
that semantic interval through the typed handle. Copy carries the declaration;
Manim become changes appearance while preserving the receiver's callable and
interval. Retained-path inversion remains outside this bounded graph-query
profile. Default 2D Axes ranges, sizing and tips are supported separately through
the shared coordinate constructors.
The interval records whether the plot was mapped through Axes; standalone
scene-coordinate graphs retain query metadata without widening the existing
Axes-only area/partition profile.
Phase B exit: representative common 2D Manim scenes require only the intended language/browser adaptation and run through the same semantic/runtime path from Python and Rust, with representative paired examples usable on the supported Rust execution targets.
Phase C — native interaction, locality and live authoring#
- finish native pointer/keyboard/viewport ingress;
- lower known updater/constraint behavior to native reactive dependencies;
- evaluate optional host-expression capture under #955 C1 when measurements and compatibility needs justify it; any supported subset must reuse shared native lowering, preserve side effects and define explicit unsupported-input behavior;
- make runtime wake/settle and fixed publication ordering explicit so timeline and native interaction can coexist without a second interactive scene mode;
- finish arbitrary host callback slots with ordered mixed native/host evaluation, coherent transactional overlays, callback read views, effective-vs-authored write classification, provisional structural identity, driver arbitration and explicit latency/backpressure policy;
- make retained family/text updates resident and dirty-member-local;
- complete spatial culling and dirty GPU upload locality;
- add editor/session state above semantic identity (selection, hover, drag, undo grouping);
- keep source hot reload under the separate #64 live-authoring contract; Phase C provides the shared mutation and resource-versioning prerequisites, while normal Python Run remains an explicit restart;
- make stale async host/content results revision-safe and bind resource retirement to GPU completion rather than semantic publication alone.
Ownership of the live-session/scalability contracts#
The architecture above does not create a new roadmap phase or runtime owner. Existing cases own the implementation:
- #969 / Phase A2/A6 — logical execution-session/segment completion, frontend continuation mechanics for native and browser/WASM, and the single ordered/coherent publication lane between staged updates and the existing runtime/renderer path;
- #955 C1 — mixed native/host ordered updater evaluation, traceable-host lowering where supported, effective-driver-vs-authored-mutation classification, transactional callback overlay/read view, provisional structural identity, safe read-miss suspension semantics, slow-callback/backpressure policy and driver arbitration;
- #955 C2 — input sequencing, bounded discrete-event queue/backpressure/overflow semantics, input timestamp/time-domain semantics and replayable native input behavior;
- #955 C3/C4 — renderer-facing publication, immutable resource replacement/versioning, GPU-safe retirement and long-running resource locality;
- #955 C7 — high-fanout execution/render domains only as measurement-driven specialization once stable execution/locality layouts exist;
- A1.6 — the typed incremental SemanticStore-to-existing-execution handoff and impact/local-lowering foundation only; it must not grow another execution runtime/session abstraction.
Phase C exit: common interaction works without Python; traceable supported host expressions can leave the frame path; arbitrary Python callbacks are bounded and explicit; local edits stay local through validation, execution and rendering on both platform hosts; clean scenes settle and wake without unnecessary frame work.
Phase D — 3D and broader capability#
Status: the bounded D1–D5 shared-engine milestone is complete; broader 3D usability, compatibility and qualification remain. #956 records the completed world/camera/resource/depth, surface/solid/light, composition and vector-space profiles. #1879 owns their remaining rendering/API work and coordinates the existing qualification owners. Do not reopen the completed substrate or interpret its completion as full Manim 3D support or as “only D6 remains.”
Extend the same architecture:
- 3D semantic transforms and camera behavior;
- immutable mesh resources;
- retained depth/mesh rendering on the existing renderer/runtime;
- surfaces, solids, lighting/material semantics;
- mixed fixed-in-frame 2D/3D composition;
- broader Manim namespace coverage and advanced live-authoring UX.
No separate 3D scene engine or canvas/runtime.
Bounded implementation profiles#
Phase D exposes checked capability slices over the shared semantic scene; the
Manim class name alone does not imply full ManimCE parity. Mesh3D provides
indexed Surface, sphere, cube, prism, torus, cylinder, cone, line, and point
profiles with Rust-owned geometry, transforms, resources, and rendering. The
current implementation includes checked Manim defaults, partial parameter
ranges, axial directions and end options for bounded primitives. Cairo-style
Surface, solid-face, axis-piece, and World-path appearances are implemented as
distinct retained material profiles; native point lighting remains a separate
explicit profile. Unsupported option combinations still reject instead of
silently substituting another result. The bounded extensions below have paired
Rust/Python and backend qualification in
#1891; this does not qualify the
untouched camera examples recorded in #254 and owned by #1879. The opaque indexed-mesh profile remains
available without cell-family overhead.
Sampled Surface cell families retain UV roles on ordinary semantic mesh
objects. The default shade_in_3d=True path records Cairo-compatible endpoint
appearance from the sampled parametric control points; point_lit=True selects
the distinct Noon-native lighting profile, and shade_in_3d=False selects an
unshaded mesh. Checkerboard color and opacity changes use one checked
owner-routed transaction; family transforms reuse the shared world-affine
operation. Surface borders derive immutable exterior triangle edges once and
omit triangulation diagonals. Sampled Cairo cells retain four mapped perimeter
cubics; their fills and borders share a bounded approximation of four chords
per curved edge. Resource bounds retain the perimeter control hull so world
bounds and depth ordering include the curves. The renderer derives these buffers
once. Sampled Cairo fills filter their complete projected face with the shared
polygon pixel-coverage kernel, including subpixel cells; their screen fringe
stays within padded face bounds. Curved fills integrate signed perimeter edges
over the pixel box once, without clipping every fan triangle. For planar sampled
faces, staging replaces an invalid fan anchor only when the area centroid passes
every oriented perimeter half-plane. Those corrected planar faces use a bounded
screen box with the original depth plane as disposable filter support; the
signed perimeter remains authoritative for coverage. Complex concavity beyond
that retained profile remains unqualified under #1879.
Border quads retain analytic pixel coverage
and screen-space extrusion during camera movement. Ordinary mesh vertex and
instance layouts remain unchanged. Cairo-only scenes need no multisample target.
Translucency is bounded to
individual triangle or canonical quad cells. Opaque world geometry populates
depth first; translucent and Cairo cells paint each fill and border together in
camera-depth order, with authored painter order breaking ties. Color-only cell
changes preserve that cached order. Cairo-compatible
Surface gradients and six-face Cube/Prism materials have pinned, bounded raster
profiles. General intersecting transparent solids remain outside the profile.
Cairo World-path miter joins use Cairo's 10× limit; the ordinary World-path profile retains its 4× limit. ThreeDAxes inherits Cairo miter joins on its tips. Straight screen-space Cairo butt strokes derive box-filter pixel coverage from their projected endpoints and existing width conversion. Curves, other caps, fixed-orientation paths and depth-clipped endpoints retain their existing coverage. Cairo World paths share the cached camera-depth order with Cairo and translucent mesh faces, testing opaque world depth without writing it. Authored painter order breaks depth ties. Retained path bounds supply the ordering center. Clean and color-only frames reuse the order; geometry and pose changes update the retained center. Fixed-orientation paths retain painter order after world draws.
Spatial mesh-object get_center() and default affine pivots share Rust's bounds convention:
the world bounds of the retained local AABB. They read the current effective pose
for attached live objects and the authored pose for detached objects. A geometric
center can differ from the transform origin; world_transform and
WorldTransformTo retain their explicit pose-origin convention. These queries
use resource bounds metadata rather than traversing mesh vertices.
Surface cell families use the shared family affine pivots; aggregate world-pose
and world-center observations are not part of the qualified profile.
ThreeDScene and SpatialScene use one effective camera and the shared frame
publication/runtime. Camera profile tracks update the effective pose and
projection together; ambient and 3D-illusion camera motion are authored camera
state, while ordinary object animation remains on the common timeline.
Illusion motion evaluates the pinned sine/cosine orientation analytically
without host callbacks. Camera-motion intervals
are sampled analytically from authored time and share the existing camera-profile
scheduler channel. Overlapping finite camera moves and ambient ownership are
rejected; stopping closes the interval at the exact effective endpoint. Ordinary
authored edits require a completed animation segment, and zero-rate ambient
intervals do not keep settled playback awake. Each camera admits at most 256
camera-motion intervals; further begins fail before publication. Replay budgets count
both saved and incoming motion metadata, including the payload exchanged on seek. Direct seeking and forward playback are required to produce the same effective spatial state.
These authored-time rules also define camera compatibility: evaluate motion at
the requested time, close an interval at its exact endpoint, and initialize the
next interval from that endpoint. A materialized Manim frame and the exact
authored endpoint are distinct observations. Preserve both without adding a
frame offset, another clock, or scene-specific correction to reproduce sampled
integration artifacts. Camera qualification checks the analytic rule and
seek/forward equivalence; raw sampled-Manim differences remain visible evidence.
MoveAlongPath captures a static retained path and target bounds-center offset
at activation, then uses the ordinary Position timeline channel. Path length
sampling plans are derived once, shared across equivalent snapshots, rebuilt
before changed-track evaluation, and retired with their tracks. Replay retains
immutable path snapshots and charges their complexity against its budget.
Unsupported simultaneous path drivers fail before publication.
UpdateFromFunc registers a host callback over a compiler-scheduled finite
composition interval. It reuses the existing callback phase barrier; nested
timing does not create a second host scheduler. Its final callback runs once at
the exact interval endpoint, then the registration retires; an ordinary updater
still uses a half-open activation interval. Revising another registration at
that endpoint cannot reactivate the finished callback. Callback layout replacement
stages all affected transforms and bounds before publication, validates store
and revision ownership, and uses typed bulk rows for same-context WASM calls.
Removing the last active planar updater reconciles its current callback-owned
affine channels through the ordinary authored transaction. Explicit writes in
that transaction retain ownership; unrelated active updaters are not reconciled.
Reverse Transform timing supports the checked smooth(1-t) pop-out profile;
Create/Uncreate retains its existing reveal and easing semantics.
World, FixedOrientation, and FixedFrame composition are represented as semantic domains on ordinary objects/families. World mesh/path content uses depth; FixedOrientation currently supports retained path and vector text with the runtime-derived shared family center; FixedFrame remains planar HUD content. Point-lit mesh material/light inputs are bounded renderer semantics, not Cairo lighting emulation.
The bounded ThreeDAxes implementation supports per-axis ranges, lengths,
ticks, tips, Cairo-shaded shaft pieces, and retained text-label placement through
its label methods. Checked c2p/p2c helpers derive coordinates from authored
or effective axis families. The labels= constructor option and richer custom
tip shapes remain unsupported. The current axis appearance/configuration slice
has pinned, bounded raster qualification. Shared vector-space
implementations include animated VectorScene.add_vector, matrix transforms,
coordinate labels, and bounded faded-vector history. These use the existing
families and animation channels, with paired qualification for the bounded
operations below. They do not add a separate coordinate or scene model.
These profiles do not claim complete Manim API compatibility. In particular, custom Surface materials, general intersecting transparency, unsupported geometry topologies, arbitrary per-face appearance, unsupported vector-space operations, and optional host-language APIs remain outside the qualified surface until their owning capability and paired qualification are added. The JS/TypeScript facade is an independent optional frontend track (D6); it is not a required Phase D 3D exit criterion.
Qualified bounded extensions#
Paired Rust/Python execution, pinned Manim profiles and WebGPU/WebGL raster checks qualify these bounded extensions. The profile-specific limits and unsupported inputs remain part of the contract; they do not establish full Manim compatibility:
- Sphere, Cylinder, Cone, and Torus use checked parameter ranges; Cylinder and Cone also accept finite nonzero axial directions and bounded open-end options.
- Surface has an immutable Cairo-compatible endpoint-gradient material. Cube and Prism retain six semantic faces with Cairo-compatible appearance; this does not qualify general intersecting transparent solids.
- ThreeDAxes supports per-axis configuration, retained text-label methods, and
Cairo-shaded shaft pieces. Its constructor
labels=option and richer custom tip shapes remain unsupported. - Retained World paths support Cairo-compatible endpoint appearance and screen-space stroke rendering under bounded styles; broad arbitrary-path appearance parity remains to be qualified.
- Family world-bounds-center queries use authored state for detached families
and effective state for attached live families. Heterogeneous family
world_transformremains unsupported. VectorScene.add_vectorhas an animated shared-Rust path. Linear Transformation Scene supports coordinates, transformables, coordinate labels, and bounded faded-vector history through shared semantic operations.
Performance tooling reuses the same-run cumulative Product Gate anchor and includes five representative workload instrumentation cases. Runtime assertions require real scene objects and source continuation; opt-in raw timestamps retain the measured frame gaps. Functional instrumentation checks provide no physical-device 60 FPS evidence.
Remaining capability and qualification#
The next work extends these profiles rather than replacing their architecture. Detailed batches, dependencies and evidence belong in #1879 and its linked owners; this overview is not another implementation checklist.
| Workstream | Current qualification and owner |
|---|---|
| Camera/reference qualification | #254 retains all seven pinned ManimCE originals and their dispositions. #1879 records their selected analytic-camera/explicit-font state and raster qualification, including #2252's original zoom replay. Preserve the original sampled-clock and host-font observations separately; a declared profile does not establish unrestricted exact Manim parity. Callback replay classification remains explicit. |
| Common 3D rendering and defaults | #1879 records the qualified bounded Cairo Surface and solid-face materials, World-path appearance/strokes, default axes appearance, and mixed depth/text/image profiles in #1891. Camera qualification remains separate from these material profiles; native point lighting remains distinct from Cairo compatibility, and general intersecting transparency remains separate. |
| Geometry, axes and vector-space breadth | #1879 records the qualified bounded primitive ranges/orientations/open ends, ThreeDAxes labels/configuration/tips, animated vector addition, and coordinate-label/ghost-vector helpers in #1891. Further options require their own selected capability and paired evidence. |
| Performance and pacing | #1653 owns physical Mac/iPhone pacing and attribution, pinned cumulative anchors, representative 3D workloads, and separate authoring/runtime/preparation/locality/upload/package metrics. The five spatial workloads have separate functional and Mac measurement evidence; the fixed Product Gate anchor measures five general gallery workloads. #1900 records three alternating Mac baseline/candidate pairs; occasional long frames and means below the strict 60 FPS requirement remain unqualified. Reuse the existing repeated-pair Product Gate and camera diagnostics; software-GPU or emulated-browser results do not qualify physical-device smoothness. |
Exact FollowingGraphCamera source/state/raster qualification was merged in
#1882. Its callback replay remains
explicitly denied. #1900
qualifies the exact ThreeDLightSourcePosition source, effective light/camera
state and raster on both WebGPU and WebGL. The untouched static
ThreeDSurfacePlot case is qualified for source, camera state, raster, seek and
restart on both backends; this does not qualify animated surfaces or general
transparency. The other four original camera examples now have recorded state
and raster qualification under the analytic-camera and explicit-font rules
accepted in #2247.
#2252 also qualifies deterministic
historical replay of the untouched MovingZoomedSceneAround source through
shared finite layout lowering and sparse inset lifecycle replay. These results
do not make opaque callbacks replayable or establish unrestricted Manim parity.
#254 retains the original
per-case evidence and dispositions, including sampled camera handoff/endpoint
differences and implicit-font appearance mismatches. Original captures are not
rewritten or relabeled as exact parity.
Further World-stroke, axes-label and geometry/vector-space profiles can proceed
independently of general transparency and physical-device pacing attribution.
Every promoted slice preserves the existing 2D output,
atomic publication, authored/effective distinction, retained-resource locality,
ordered callback/replay contract and one presentation surface. Unsupported
defaults/options remain explicit until implemented; silently removing guards or
substituting native appearance is not compatibility completion.
Support records must distinguish implemented profile, cross-frontend qualified, and Manim-qualified. A class name, compilation, a showcase or matching Rust/Python pixels cannot substitute for pinned semantic/timing/raster evidence. Physical performance is a separate qualification dimension: retain exact package/device/backend identities, raw repeated samples and frame-gap measurements, not only mean FPS. A deferral needs a named owner and remains visible as unfinished work; it is not a parity pass or a reason to restart D1/D2.
Independent frontend and live-authoring tracks#
Optional D6 #259 owns high-level JS/TS authoring; existing WASM bindings do not imply that facade has shipped. #1874 owns native CPython/Pyodide same-source semantics and real-binding conformance/performance, distinct from native Rust versus browser-Python evidence. Phase D APIs consume that shared Python path rather than introducing a native-only or browser-only 3D facade; eliminating async is not an acceptance criterion. Source hot reload and advanced live-authoring reconciliation remain #64, separate from the normal Python Run/restart contract. These owners remain independent of the completed bounded 3D milestone and do not create another scene/runtime authority.
15. Validation strategy#
Architecture is enforced with executable evidence, not screenshots alone.
Required categories:
- native Rust authoring -> lowering -> runtime -> renderer -> native-host smoke tests with no Python/JS/browser/serialization initialization;
- direct Rust/WASM authoring -> lowering -> runtime -> renderer -> browser-canvas smoke tests with no JSON/wire round-trip between in-process engine layers;
- structural checks that native and direct single-context Rust/WASM engine paths contain no JSON/wire round-trip between architecture layers;
- Rust/Python semantic parity for equivalent authoring even when the host-language continuation/control-flow mechanism differs;
- paired executable Rust/Python examples for representative supported common semantics;
- ManimCE v0.21 semantic/raster/timing differential tests for supported APIs;
- direct-seek versus forward-playback tests;
- script-continuation tests where logical post-
play()completion exposes coherent effective state across Python interpreter continuation, native Rust compiled/awaited control flow and yielding browser/WASM host mechanics; - authored/base versus effective-state getter tests before/during/after active drivers;
- effective-updater tests proving ordinary per-frame host/native driver writes can advance
FrameEpochwithout advancingSceneRevision/ExecutionRevisionor triggering semantic relowering; - if optional host-expression capture is implemented, tests proving supported expressions use existing native execution without a host callback on the frame path, plus rejection/fallback tests that preserve side effects and ordering;
- frame-publication tests proving each
FrameEpochreferences one coherent semantic/execution revision pair and renderer/query consumers see the same pair; - mutation atomicity/rollback tests including failure after semantic staging but before lowering/resource preparation completes;
- structural callback tests covering provisional object construction -> mutation -> membership -> read -> atomic publish/abort;
- transaction-preflight locality tests that measure scans and temporary work, not only committed slot counts;
- dependency-local reactive tests;
- mixed native-host-native updater-order tests proving implementation classification cannot reorder observable updater semantics;
- ordered host-callback read-after-write overlay tests;
- cross-context callback read-mirror revision/miss tests proving a miss suspends/resumes the same invocation when supported and does not automatically replay arbitrary side effects;
- host callback batching/isolation, stale-result and slow-required-callback barrier/backpressure tests;
- input burst tests proving sampled-state coalescing, discrete-event ordering and paused authored
dt == 0semantics; - bounded discrete-event overflow/backpressure tests for both backpressurable and non-backpressurable sources;
- replay tests for recorded input/editor/external results where classification says replay is supported;
- mixed large-static + small-dynamic performance tests;
- high-fanout family transform tests that measure compact domain work plus only genuinely materialized affected leaves;
- explicit maintenance-barrier/compaction tests proving controlled rebuilds are coherent and not triggered as hidden ordinary local-edit fallback;
- renderer dirty-range/residency/culling tests;
- immutable resource replacement tests covering failed/stale preparation and in-flight GPU
SubmissionSerialretirement; - native host resize/frame/present smoke tests;
- WebGPU/WebGL backend equivalence for supported surfaces;
- browser interactive smoke tests;
- structural architecture tests from Phase A6.
Representative scalability evidence should include at least these workload shapes as the relevant subsystems become available:
100k mostly-static live objects, small visible fraction
clean/settled CPU work ~ O(0) meaningful evaluation
viewport/render work ~ candidates/visible batches
100k mostly-static objects, one native-reactive object driven by pointer
zero host callbacks after authoring
work ~ dirty dependency closure + candidates + changed GPU ranges
100k mostly-static objects, one host updater changing one transform each frame
host work ~ updater working set + one effective driver write
SceneRevision/ExecutionRevision remain stable unless semantics actually change
large family with one compatible shared transform
compiler/runtime may keep fan-out compact
downstream work includes only affected leaves genuinely required by bounds/hit/render consumers
large mixed scene, one local content replacement
unrelated NodeIds/execution slots/runtime state/GPU resources remain resident
long-running edit/add/remove/resource/input churn
memory follows live + intentional cache/queue policy, not unbounded historical mutation or stalled-event count
Performance regressions should be measured in terms of authoring/host bridge calls and bytes, effective driver writes versus authored semantic mutations, validation/preflight work, active channels, ordered updater regions/barriers, dirty dependencies, affected slots/domains/materialized leaves, visible candidates, resource versions and GPU ranges—not only total FPS.
The debug/renderer-smoke-only retained glow fixture uses ordinary compiled tracks and runtime frames, then the actual retained encoder, to qualify real transferable and shared-memory render-worker boundaries. That lower-level fixture is not a public Scene execution proof. Its reference uses extended no-effect primitive/path renders at the same reveal progress for source coverage and an independent full two-dimensional Gaussian, encoded source-over composition, and viewport crop. The existing browser-rendering job owns the full-frame comparisons, exact neutral/rewind checks, source retirement, invalid-payload preservation, and worker recreation. Primitive antialiasing is shared with the production renderer, so this is not an independent geometry-rasterization oracle. Missing browser access, setup failures, and compilation alone cannot count as pixel qualification.
Diagram maintenance#
Editable D2 sources live in docs/diagrams, beside their checked-in SVGs. Markdown embeds SVGs so GitHub readers do not need a D2 renderer. _style.d2 contains presentation only; it does not define another architecture.
Install D2 v0.9.0 and use the same local/CI entrypoint:
# After editing a source, format it (including shared style changes).
d2 fmt docs/diagrams/*.d2
python3 scripts/architecture_diagrams.py
# Non-mutating freshness, formatting, SVG and document-link check.
python3 scripts/architecture_diagrams.py --check
# Real-renderer regression tests for stale/missing/orphan output and failures.
python3 scripts/test_architecture_diagrams.py
D2=/path/to/d2 selects an installed binary; these commands do not download tools. The renderer version, ELK layout/spacing, light/dark themes and padding are pinned. Architecture diagrams installs the checksum-verified release and checks generated output on relevant pull requests and master pushes. It fails on stale SVGs rather than silently editing a contributor's branch. A source/style edit therefore requires regenerating and committing the SVGs, but not manually redrawing them.
When architecture changes, review AGENTS.md, this document, the relevant crate lib.rs / manifests, session publication code and actual browser endpoints before updating a diagram. Keep current placement separate from target/future responsibilities. Source/render agreement checks cannot prove architectural truth; that still requires code review and the engine qualification described above.
16. Decision rule for new work#
Before adding an abstraction, crate, scene representation or compatibility layer, answer:
- Which of the four architecture layers owns this?
- Is there already an authority for the same state or behavior?
- Does this create a second scene, scheduler, identity system or renderer authority?
- Does a local change remain local through validation/preflight, lowering, runtime, spatial work and renderer publication?
- Is the new crate/module boundary required by dependency/compilation/reuse, or merely conceptual organization?
- Can obsolete code be deleted instead of adapted?
- Does this introduce serialization or a transport representation where a typed in-process Rust boundary should exist?
- If transport is proposed for the browser, is there a real cross-worker/cross-context boundary, or are all layers already in one WASM context?
- Is platform lifecycle being kept in a host shell instead of turning the reusable renderer into an application framework?
- Is a semantic hierarchy being forced to serve as an execution/render hierarchy, or can the compiler derive a cheaper structure without changing semantics?
- Is an ECS, state machine, display list, render graph or scheduler being used as a useful implementation mechanism, or is it unnecessarily becoming the product's public semantic model?
- Does this require synchronous GPU readback or per-property host crossings on a normal frame path? If so, why can the value not have a CPU-observable source or batched protocol instead?
- Is this write an effective driver write or an authored semantic mutation? If effective, why would it need
SceneRevision/semantic relowering? If authored, what makes persistence/structure observable? - Which identity generation, state revision, frame epoch, resource version, event sequence or submission serial proves a late result is still valid? Are distinct domains being incorrectly compared as one generic generation?
- What is the operation's authored/simulation-time behavior, and is any wall/external-time dependency explicit in replay/determinism classification?
- If a cross-context callback read misses, can the same invocation suspend/resume safely, or is the implementation incorrectly assuming arbitrary host code can be restarted?
- What bounds an ordered discrete-event queue if a required callback stalls, and what explicit policy applies when the source cannot be backpressured?
- Does a proposed optimization solve a measured or contractually unavoidable cost rather than importing another engine's architecture by analogy?
- If an operation is allowed to perform O(live-state) maintenance, is it an explicit/instrumented maintenance barrier rather than a hidden ordinary local-edit fallback?
- Is the design accidentally requiring Rust, Python and editor/live inputs to share a source-language execution model when only semantic parity is required?
- If host code is proposed for tracing/capture, are purity, side effects, unsupported operations and fallback semantics explicit enough that tracing cannot silently change observable behavior?
For this greenfield project, deletion is preferred over compatibility scaffolding.
17. Non-goals#
- preserving historical Noon APIs or internal formats;
- multiple authoring scene models;
- a Python animation/scheduling engine beside Rust;
- requiring Python, JavaScript, WASM, a browser, JSON, or serialization for native Rust authoring/execution/rendering;
- forcing a Rust-authored WASM scene through JSON, a scene document, an execution mirror, or JavaScript mediation when the engine layers share one WASM context;
- using serialized wire documents as ordinary in-process boundaries between Semantic Scene, Execution Plan, Runtime, and Renderer;
- making
noon-render-wgpuown native/browser application event-loop or window/canvas lifecycle merely for convenience; - a renderer-specific semantic scene or renderer scene graph becoming authored truth;
- a general-purpose public ECS/world/component model as the Noon authoring model;
- a public arbitrary game-engine-style system/plugin scheduler as the animation semantics;
- requiring state machines as the primary animation/interaction programming model;
- using a canvas/display-list command stream as the Execution Plan or semantic scene authority;
- separate architectures for text, Graph, interaction or 3D;
- serializable wire structures dictating the in-memory semantic design;
- running Python/JS object-graph traversal every frame for behavior that can be expressed as native timeline/reactive execution;
- requiring identical host-language source execution/control-flow mechanisms for Rust and Python merely to achieve semantic parity;
- treating the live-scene model as a requirement to interpret arbitrary Rust source at runtime;
- treating one Execution Plan as a frozen compilation of all future host-language authoring decisions;
- silently approximating unsupported/effectful host expressions merely to move them off the host path;
- routing every host updater/property write through authored semantic mutation or relowering when the operation is only an effective runtime driver write;
- automatically restarting arbitrary host callbacks on a cross-context read miss unless they are explicitly replay-safe/idempotent;
- pretending Noon transaction rollback can undo arbitrary external host side effects;
- synchronous GPU-to-CPU readback as an ordinary semantic query/input/callback mechanism;
- unbounded ordered discrete-event queues during callback or presentation stalls;
- promising sublinear re-execution of arbitrary host-language source code; locality guarantees apply to reconciliation and engine work after the host has produced semantic changes;
- promising O(1) end-to-end work for every high-fanout change when an observable consumer genuinely needs affected per-leaf materialization;
- full-scene, full-family or full-resource work as a convenience fallback for local mutation, including hidden validation/preflight scans; explicit/instrumented maintenance barriers are a separate contract;
- unbounded tombstone/resource/cache growth proportional to historical mutations rather than live/intentionally cached state;
- crate proliferation without a real boundary;
- silent approximation of unsupported Manim behavior.