# Module Boundary Contract
Type: Contract.
**Authority.** This document is an implementation contract. The RFC is the canonical
narrative; this contract supersedes the RFC for module ownership rules. Accepted ADRs in
`docs/decisions/` override both.
This document defines implementation ownership. Public examples may re-export simple types,
but code should keep these module responsibilities separate.
## Ownership Table
| `scene` | graph storage, typed keys, transforms, bounds, cameras, lights, anchors, clipping, scene queries, `scene::interaction` hover/selection state | GPU device resources, asset fetch, platform events |
| `assets` | fetchers, caches, glTF/GLB parsing, parsed clip data, texture decoding, asset handles, retain policy, hot reload | scene graph mutation, GPU resources, rendering passes, window/canvas state |
| `geometry` | primitive builders, generated meshes, technical lines, grids, axes, labels metadata | material shading policy, GPU upload, scene hierarchy |
| `material` | material descriptors, texture slots, color-space tags, alpha modes, PBR/unlit configuration | texture fetching, GPU pipeline creation, renderer stats |
| `render` | wgpu device/surface, prepare lifecycle, pipelines, passes, render targets, GPU resource table, stats | asset fetching/parsing, application event loop, domain state |
| `animation` | runtime mixer state, rebound animation channels, skinning palette state, morph target playback | parsed asset cache ownership, physics, simulation, game update loop, host frame scheduling |
| `controls` | platform-neutral camera control math and optional event adapters | global input dispatch, app event ownership, renderer internals |
| `picking` | ray construction, bounds/BVH/triangle tests, typed hit results | selection state owned by the application, domain actions |
| `diagnostics` | structured errors, warnings, capability reports, debug overlay data | swallowing errors, implicit fallbacks without reporting |
| `platform` | thin winit/browser/headless adapters | renderer logic, asset ownership, scene mutation rules |
| `testing` | headless helpers, screenshot/pixel comparisons, leak/allocation harnesses | production renderer state hidden from normal APIs |
## Boundary Rules
- `Renderer` consumes prepared scene/resource state. It does not fetch, parse, or own source
assets.
- `Assets` may decode and retain CPU-side source data, but it does not add nodes to a
`Scene`.
- `Scene` owns graph identity and mutation. It never stores raw GPU objects.
- `platform` modules translate host events and surfaces into renderer APIs. They do not
contain render-pass, asset-cache, or scene-graph logic.
- `diagnostics` must make fallback decisions visible through structured warnings or errors.
- `controls` may compute camera transforms from host events, but the host still owns the
event loop and decides when to call `render()`.
- `picking` returns typed hits. `scene::interaction` stores only renderer-visible hover and
primary selection state; application semantic actions remain outside the renderer.
- Public convenience APIs may compose modules, but the underlying ownership stays explicit.
## SOLID/KISS Gate
Every public feature must name exactly one owner module, its supporting modules, its
structured error surface, and its proof surface before implementation starts.
KISS constraints:
- No catch-all `Manager`, `Engine`, `World`, or broad `Context` type in v1.0 implementation.
Narrow context values are allowed only when their owner is explicit, for example
`InteractionContext`, `RenderContext`, `PrepareContext`, or `DiagnosticContext`.
- No global singleton renderer, asset manager, event loop, or mutable registry.
- No abstraction is added only for future flexibility. Add an abstraction when it removes
real duplication, narrows ownership, or makes an existing contract easier to enforce.
- Source modules crossing 500 significant non-comment lines must be split or justified by an
ADR-backed doctor allowlist before the owning milestone exits.
- Public convenience APIs may be ergonomic, but they must delegate to the same owner module
contracts as the lower-level API.
Host-owned convenience facade exceptions:
This section is the host-owned convenience facade exceptions contract for v1.0.
- `HeadlessGltfViewer` and `InteractiveGltfViewer` are the v1.0 host-owned convenience
facade exceptions to the "no public type may become a catch-all facade" warning. They
compose the first headless/model-viewer flows; they are not new owner modules.
- These viewers may hold `Assets`, `Scene`, `Renderer`, `SceneImport`, camera, and optional
controls only so the host can call load -> instantiate -> frame -> prepare -> render in
one object.
- The viewers must not expose owner-module fields directly, own the application event loop,
hide asset fetches in `render`, hide GPU upload in `render`, or bypass the lower-level
`Scene`, `Assets`, and `Renderer` contracts.
- Mutable accessors remain explicit escape hatches back to the owner modules.
Large module allowlist:
| `src/assets.rs` | `assets` | Public asset store, cache, retained reload, and typed handle facade; split submodules own environment, glTF, load reporting, fetchers, and texture decoding. |
| `src/assets/environment.rs` | `assets` | Environment descriptor and fixture metadata stay together to keep cache identity and derivative metadata auditable. |
| `src/viewer.rs` | host convenience facade | v1.0 first-render helper intentionally composes owner modules without owning the event loop or renderer internals. |
| `src/material.rs` | `material` | Public immutable material descriptors and texture-slot helpers remain one user-facing value surface for API stability. |
| `src/picking.rs` | `picking` | Ray construction, hit sorting, and typed hit payloads share one narrow interaction contract. |
| `src/scene/inspection.rs` | `scene` | Inspection reports are grouped so debug metadata mirrors the scene graph without adding serialization ownership. |
| `src/render/prepare/resources.rs` | `render` | Prepare resource extraction is centralized because it is the single bridge from scene/assets state to renderer-owned prepared data. |
| `src/assets/gltf.rs` | `assets` | Top-level glTF `SceneAsset` public types and parser orchestration stay together while specialized parsing lives in child modules. |
SOLID constraints:
- Single responsibility: each module owns only the responsibilities listed in the ownership
table.
- Open/closed: v1.0 keeps the fixed render graph and reserves v1.x extension points without
exposing unstable custom-pass APIs.
- Liskov/interface substitution: backend degradation is represented through capabilities,
diagnostics, and structured errors, not backend-specific public object types.
- Interface segregation: `Scene`, `Assets`, `Renderer`, and `SceneImport` remain separate
public nouns; no public type may become a catch-all facade for all four.
- Dependency inversion: platform adapters translate host events into renderer APIs. Renderer
internals do not depend on winit, DOM, browser event objects, or application event loops.
## Forbidden Crossings
- No hidden asset fetch, shader compile, or first-time GPU upload inside `render()`.
- No `Scene` mutation from `Renderer::render()`.
- No direct backend objects exposed as public handles.
- No silent fallback when a required backend capability or required glTF extension is
missing.
- No global singleton renderer, asset manager, input dispatcher, or event loop.
- No simulation, robotics, PLC/domain, process, physics, or game-engine concepts in any
module.
## Review Gate
Every new public feature must name:
- its owner module;
- any supporting module it depends on;
- its structured error surface;
- its proof surface: unit, integration, headless screenshot, browser rendered-output,
capability, leak, allocation, or benchmark test.