Files
Oxide/PLAN.md
T
Homer Simpson 9eead719b0 Import Oxide engine (Stages 0–10) under MIT license
Full project snapshot migrated to new Gitea remote without history:
engine, editor, physics, script, examples, tests, docs, and assets.
Relicensed from GPLv3 to MIT and updated repo URLs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 20:41:02 +02:00

79 KiB
Raw Blame History

Oxide Engine — Long-Term Development Plan

Each stage must be completed and signed off (manual + automated testing) before the next begins. Stages are designed so that each one produces a usable, standalone artifact that later stages build upon.

The in-engine editor (oxide-editor) is a first-class deliverable. It is developed in parallel with the engine: each stage adds editor support for the systems introduced in that stage. The editor crate lives at editor/ and is always buildable alongside the engine.

The project must remain installable as a Linux package at all times. install.sh builds a release binary and installs it to the system. Keep it current whenever new binaries or assets are added.


Development Phases

Oxide is built in two phases:

  • Phase 1 — The general-purpose engine (Stages 016). Everything needed to build and ship any 3D game: core framework, editor + project system, input, UI, physics, scripting, animation, particles, shaders, audio, a content kit, and game export to Linux + Windows. Stage 16 is the milestone: a complete, general-purpose engine that can build and export real games.
  • Phase 2 — Built-in modules (Stages 17+). Optional, self-contained capabilities — ray-traced audio, a developer console, a procedural toolkit, terrain, open-world streaming, pathfinding/AI, water — each shipped as a feature-gated module built entirely on Phase-1 systems. Phase 2 is also the proof the module API holds: adding a capability should mean writing a module, not surgery on the core.

The headline framing is simply a general-purpose 3D engine that scales from stylized low-poly to realistic, shipping only what each game uses. Simulation, open world, and procedural generation are capabilities the engine supports through modules, not design drivers — the goal is to build tools, not games.


Platform & Target Strategy

These are project-wide constraints that span every stage, not a single deliverable:

  • Primary platform is Linux, and Linux means both Wayland and Xorg (X11) — the editor and any engine app must run on either without code changes. This is the default and must stay working at every stage (verified via winit's wayland + x11 backends, both enabled).
  • Windows support comes later — once enough of the engine exists to be worth porting, a compiled Windows build of the editor/engine is added. Code should avoid Linux-only assumptions so this port stays cheap; wgpu/winit already abstract the platform layer.
  • Game export targets both Linux and Windows binaries. A project built in the editor must be exportable to standalone runnable binaries for both platforms (see Stage 16). The editor and the exported game share the same engine runtime.

Cross-Cutting Principles (apply to every stage)

  • Build tools, not games. The engine ships composable building blocks (e.g. noise + modifiers, not a "make me a world" button). Genre-specific behavior belongs in game code or optional modules.
  • Ship only what's used. Subsystems are feature-gated modules; an exported game compiles in only the modules it registers. The same engine produces a tiny stylized game and a heavy realistic one, differing only in which modules are present.
  • Scalable fidelity. The renderer is a data-driven pass pipeline so a project can run a flat unlit/low-poly look (or a stylized post effect like a VCR filter) or a full realistic stack with shadows, SSAO, and bloom — paying only for the passes it enables.
  • Modules are the primary extension point. A module registers engine logic (systems, components, asset loaders) and editor integration (panels, menu items, tools, inspectors) and its own settings, through one documented API. Anyone — including AI agents — can write a module. Native modules are Rust crates implementing the Module trait; lightweight modules can be rhai scripts.
  • Editor- and script-editable types. Engine component types (e.g. Transform, Node, Script, materials, colliders) must be editable both from the editor UI and from scripts/code, through the same reflected/serializable representation. From Stage 5 this is backed by a reflection/ type registry, so any editor — or an AI agent in the editor terminal — can create and edit game code and scene data in real time. New component types are added with this dual-editability built in.
  • Live reload by default. Game scripts/assets are watched on disk; changes are picked up and applied to the running editor without a restart (watcher foundation in Stage 6, full script reload in Stage 10). Assets added earlier keep their loaders reload-friendly.
  • The editor grows every stage. Each stage adds editor support for its systems through the Stage-6 editor framework: a top menu, dockable panels, an undo/redo command stack, a module-driven extension API, and a settings/preferences framework that every system contributes pages to (engine prefs, editor prefs, input bindings, per-module settings, enable/disable modules).
  • Choice of subsystems. Where the engine offers an advanced system it also offers a standard one so each project can choose its trade-off — most concretely audio: a standard mixer/spatial system (Stage 14, core) and a ray-traced propagation module (Stage 17), sharing components.
  • Comprehensive, not minimal, gameplay systems. Physics, input, and the UI are first-class, full systems (Stages 7, 8 & 9), not thin wrappers — they must cover the cases real games need.
  • Document as you build, including how to extend. A stage is not complete until its systems are documented under docs/. Every module ships documentation for both how to use it and how to create one like it (an authoring guide), so the module ecosystem is approachable.

Status

Stage Title State
0 Project Foundation Complete
1 Math & Core Primitives Complete
2 Window & Render Context Complete
3 Scene Graph & Entity System Complete
4 Basic 3D Rendering Complete
Phase 1 — General-purpose engine
5 Engine Core Framework (modules, layers, assets, reflection, render graph) Complete
6 Editor Framework & Project System Complete
7 Input System (mapping + remappable actions) + gizmos Complete
8 Comprehensive UI System Complete (pieces 18 on main; the editor UI canvas — piece 9 — shipped as Stage 8.5 piece 7)
8.5 Reflection v2, Editor Redesign & Asset Database (foundation) Complete — every component type is editor- and script-editable with no per-type code; asset database + UI canvas on main
8.7 Editor Play Mode (Play/Pause/Step/Stop) Complete on main — snapshot/restore + play-state model, App::step/tick_for, toolbar + Ctrl+P/Ctrl+. + viewport tint, scene-swap host runner (eye-checked & approved 2026-06-16)
9 Physics Integration (comprehensive) Complete on main — full sim/events/queries/joints/character controller + 2 examples + editor types/play-loop/collider gizmos + raycast debug probe (freeze-on-click; eye-checked & approved 2026-06-17)
10 Scripting, Live Reload & Editor Terminal 🚧 Core done on main (eye-checked & approved 2026-06-17): oxide-script crate (Script, .rhai loader, sandboxed ScriptEngine, ScriptModule) + lifecycle (init/update(dt) via ScriptHost) + Vec3/transform engine API + live reload (examples/script_spin) + editor integration (Script addable; play-loop shares asset server/db → live .rhai edits update a playing scene) + Console (captures the log stream — script print/errors) + command terminal ($ → sh -c) + interactive PTY terminal (portable-pty+vt100, tabbed, auto-close on exit, Tab/arrows/Esc routed; runs shells/TUIs/claude). Remaining: richer script API (spawn/despawn + component add/edit beyond Transform); then the editor-UX batch (file explorer, native New/Open dialog, New-Script button, open-script-in-editor)
11 Animation System Planned
12 Particle System Planned
13 Shader System & Advanced Graphics Planned
14 Standard Audio System Planned
15 Engine Content & Prototyping Kit Planned
16 Windows Support & Game Export Planned — Phase 1 milestone
Phase 2 — Built-in modules
17 Ray-Traced Spatial Audio (module) Planned
18 Developer Console & Cheats (module) Planned
19 Procedural Toolkit (module) Planned
20 Terrain System (module) Planned
21 Open World Support (module) Planned
22 Pathfinding & NPC AI (module) Planned
23 Water System (module) Planned

Backlog (post-roadmap capabilities and additional modules) is at the end of this file.


Stage 0 — Project Foundation

Goal: A buildable, testable Rust project with core structure in place.

Deliverables

  • Cargo workspace layout (engine/, editor/, examples/, tests/)
  • Basic logging and error handling infrastructure
  • CI-ready: cargo build, cargo test, cargo clippy, cargo fmt all pass
  • Empty integration test harness
  • README.md with engine description, build instructions, and install instructions
  • install.sh — builds release binaries and installs to /usr/local (or $PREFIX)
  • Editor stub: oxide-editor binary that starts, logs, and exits cleanly

Test Criteria

  • cargo build succeeds from clean checkout
  • cargo test runs (even with zero tests) without error
  • Clippy reports zero warnings with deny(warnings)
  • install.sh completes without errors on a clean Linux system
  • oxide-editor binary runs after installation

Stage 1 — Math & Core Primitives

Goal: Solid math foundation every other system will depend on.

Deliverables

  • Choose and integrate math library (glam preferred) — done (glam with serde)
  • Transform type: position, rotation (quaternion), scale — engine/src/math/transform.rs
  • AABB, Ray, Plane, Frustum types — engine/src/math/{aabb,ray,plane,frustum}.rs
  • Common utility types: Color, Rect, Range3engine/src/math/{color,rect,range3}.rs
  • Unit tests for all types (edge cases: zero scale, gimbal lock paths, etc.) — 69 unit tests
  • Editor: no visible UI yet; math types available via oxide_engine::math / prelude

Test Criteria

  • 100% unit test coverage on all math types — every type has a tests module
  • Fuzz transform composition (random chains should remain stable) — tests/ stage1::fuzz_transform_chains_stay_stable
  • Benchmark: 1M transform multiplications under 10ms — ~5.1ms (cargo bench -p oxide-engine)

Notes

  • Transform stores decomposed TRS; composition takes an exact fast path for uniform scale and re-decomposes from the matrix for the non-uniform case.
  • Range3 is a value range (clamp/lerp/remap), distinct from Aabb (geometry).
  • Runnable example: cargo run -p oxide-examples --bin math_demo.

Stage 2 — Window & Render Context

Goal: A window opens, GPU context is acquired, a colored screen is displayed.

Deliverables

  • winit integration for window creation and event loop — engine/src/window/ (App trait, AppCtx, WindowConfig, run)
  • wgpu device/queue/surface setup — engine/src/render/ (Gpu, RenderContext)
  • Clear-color render loop (configurable clear color) — render::clear_view, AppCtx::set_clear_color
  • Resize handling — RenderContext::resize, driven by the event loop; minimized windows clamp to 1×1
  • Input event plumbing (keyboard, mouse — raw events only, no abstraction yet) — every WindowEvent forwarded to App::event; types re-exported via window::event
  • Editor: opens its own window using this infrastructure; displays a placeholder viewport (dark clear, raw input logged, Ctrl+Q quits)

Test Criteria

  • Window opens on Linux without errors — manual sign-off (2026-06-11, Wayland, RADV/Vulkan)
  • Resize does not crash or produce artifacts — manual sign-off
  • Clear color changes are reflected immediately — manual sign-off (hello_window keys 15/Space)
  • Manual: run the hello_window example and confirm stable 60+ FPS on minimal GPU — ~3700 FPS (RX 9070 XT, Mailbox present mode)
  • Automated: headless GPU clear verified by pixel readback — tests/ stage2::headless_clear_fills_texture_with_clear_color

Notes

  • Docs: docs/windowing.md, docs/render-context.md.
  • wgpu/winit are re-exported (oxide_engine::wgpu / ::winit) so consumers don't need to version-match their own copies.
  • egui editor panels intentionally deferred to Stage 3 (Stage 2 editor scope is the placeholder viewport only).

Stage 3 — Scene Graph & Entity System

Goal: Entities with transforms exist in a hierarchy; scene can be queried and mutated.

Deliverables

  • Choose and integrate ECS — done (hecs, re-exported as oxide_engine::hecs)
  • Scene type: add/remove/query entities — engine/src/scene/graph.rs
  • Transform hierarchy (parent-child with world-space resolution) — Scene::world_transform / world_transforms
  • Node component: name, enabled flag — engine/src/scene/node.rs
  • Scene serialization/deserialization (RON) — Scene::to_ron / from_ron, stable index-based format
  • examples/scene_basic: create a hierarchy, print world transforms — done
  • Editor: scene hierarchy panel; select, rename, enable/disable, reparent nodes — done (egui Hierarchy + Inspector panels)

Test Criteria

  • Parent transform propagates correctly to children — unit + tests/ stage3
  • Removing a parent removes or detaches children (configurable) — DespawnPolicy::{Recursive, DetachChildren}
  • Serialize → deserialize round-trip produces identical scene — byte-identical re-serialization
  • 10,000 entity scene with 5-level deep hierarchy resolves in under 1ms — ~0.6ms (cargo bench -p oxide-engine --bench scene)
  • Manual: editor hierarchy/inspector panel verified by maintainer (2026-06-11, Wayland, RADV/Vulkan)

Notes

  • Docs: docs/scene.md.
  • The hierarchy is owned by Scene (ordered roots/children), not stored as components, for deterministic ordering and cheap reparenting; the ECS still owns all entity data, so later stages attach their own components.
  • egui is an editor-only dependency: the engine exposes a generic post-clear draw hook (App::render(&RenderCtx)) and the editor's EguiLayer draws through it, keeping egui out of the engine's dependency tree.

Stage 4 — Basic 3D Rendering

Goal: Meshes are loaded and rendered with a simple material system.

Deliverables

  • Mesh type: vertex buffer (position, normal, UV), index buffer
  • GLTF loader (static meshes only, no animation yet)
  • Forward renderer: depth buffer, basic lit pass
  • Material type: albedo color/texture, roughness, metallic (PBR-lite)
  • Camera component with perspective projection
  • examples/hello_mesh: load a GLTF cube/sphere and render it
  • Editor: 3D viewport renders the active scene; camera orbit/pan/zoom controls; material inspector panel

Test Criteria

  • GLTF primitives (cube, sphere, plane) render correctly
  • Depth ordering is correct (no z-fighting on simple scenes)
  • Camera movement updates the view matrix correctly
  • Manual: inspect rendered output for correct lighting on a sphere — maintainer sign-off (2026-06-12)

Notes

  • Docs: docs/rendering.md.
  • The forward renderer has automatic render-backend fallback (Vulkan/Metal/DX12 → GL → software) so it runs on a wide range of GPUs.
  • The hardcoded forward pass from this stage is generalized into a data-driven render pass pipeline in Stage 5 — later passes (shadows, post-process, overlay UI) compose into it rather than rewriting the renderer.

Stage 5 — Engine Core Framework

Goal: The structural "spine" every later system plugs into: a module system, scene/object layers & tags, a central asset server, a minimal reflection/type registry, and a data-driven render pass pipeline. Built now, while the engine is small, so later stages add capabilities without rewriting the core.

Deliverables

  • Module system + app builder. An App/Engine assembled by registering Modules. A Module::build(&mut App) hook registers: update systems (with an explicit ordering/schedule), component types, asset loaders, and (via Stage 6) editor integration. DefaultModules bundles the built-in set. Math/window/render/scene are wrapped as built-in modules; subsystems from Stage 9 on are built as their own feature-gated crates (oxide-physics, oxide-audio, …) so an exported game compiles in only what it registers.
  • System scheduling. A simple, deterministic ordered schedule (e.g. Input → PreUpdate → Update → PostUpdate → Render) that modules attach systems to, with a fixed-timestep slot reserved for Stage 9 physics.
  • Layer & Tags.
    • LayerMask — a 32-slot bitmask with a project-level named-layer registry. One shared definition consumed by physics collision groups + sensor filtering (Stage 9), camera visibility masks (which layers a camera renders — first-person view models, editor-only gizmo layers, minimap cameras), and scene/raycast query filtering, plus editor per-layer show/hide/lock.
    • Groups/tags — the multi-valued counterpart to the single-valued Layer (Unity model: an entity is on one layer but in any number of groups). Membership lives in the Tags component; the project's valid group names live in a GroupRegistry so the editor offers a fixed vocabulary to pick from. Separate from the hot-path LayerMask.
  • Asset server + handles. A central registry returning typed, ref-counted handles (Handle<Mesh>, Handle<Texture>, …) with async loading and a pluggable loader registry. Stage 4's GLTF/texture loading is migrated onto it. This one system underpins live reload (Stage 10), streaming (Stage 21), and export packing (Stage 16), so it lands before more loaders accrue.
  • Reflection / type registry (minimal). Register a component type once and get serialization (serde/RON), a generic editor inspector (Stage 6), and script access (Stage 10) — the backbone of the dual-editable types principle. Existing hand-written inspectors are migrated as the registry matures; every component type from Stage 7 on is dual-editable by construction.
  • Data-driven render pass pipeline. Generalize Stage 4's forward pass into an ordered, composable list of passes (depth, lit, transparent, post-process, overlay) with shared render targets. A project/module enables only the passes it needs — this is the mechanism behind scalable fidelity (a flat low-poly look vs. a full realistic stack) and the insertion point for Stage 13's post-process effects and Stage 8's UI overlay.
  • Editor: no new dedicated panel beyond wiring — the hierarchy/inspector now read the reflection registry, the viewport gains a layer visibility toggle, and the groundwork is laid for Stage 6's module/extension framework.

Delivered (all on main)

  • Layers, groups & tags (oxide_engine::layer) — LayerMask, LayerRegistry, single-valued Layer, multi-valued Tags + GroupRegistry.
  • Reflection/type registry (oxide_engine::reflect) — generic name-keyed component access.
  • Asset server + handles (oxide_engine::asset) — ref-counted Handle<T>, dedup, loaders, background load, in-place reload; glTF migrated onto it.
  • Module system + scheduling (oxide_engine::app) — App + Module + Schedule (phases incl. fixed-timestep), enable/disable/remove; CoreModule/RenderModule/DefaultModules.
  • Data-driven render pass pipeline (oxide_engine::render::RenderPipeline) — composable RenderPass list, ClearPass/ForwardPass, camera LayerMask visibility; editor viewport + hello_mesh retrofitted. Maintainer manual sign-off 2026-06-12 (viewport + hello_mesh render identically).

Known tech-debt (tracked): there are now two App types — the engine container oxide_engine::app::App and the windowing oxide_engine::window::App trait (the per-window event handler). To avoid the clash the core App is intentionally not in the prelude (import it as oxide_engine::app::App); window::App stays in the prelude. Resolve by renaming the windowing trait (e.g. WindowApp/AppHandler) when the editor adopts the core App loop in Stage 6/7 — a GUI-affecting change best done with a manual-test pass, not in isolation.

Test Criteria

  • An app composed purely by registering modules runs; removing a module removes its systems, components, and assets with no dangling references
  • A feature-gated module excluded at compile time produces a binary with none of its code
  • LayerMask filtering is correct and shared: the same named layer drives a render visibility mask and (stubbed) a query filter identically
  • The asset server returns ref-counted handles; loading the same asset twice yields one resource; dropping all handles frees it
  • A component registered with the reflection registry round-trips through RON and is read/written generically (proving the inspector/script path) without type-specific code
  • The render pipeline runs Stage 4's scene through the new pass list with identical output, and a pass can be added/removed without touching the renderer core
  • Benchmark: module/system scheduling overhead is negligible vs. the Stage 4 hardcoded loop

Stage 6 — Editor Framework & Project System

Goal: Turn the editor from a fixed set of panels into a proper, extensible application shell with a project system — so every later stage (and every third-party module) plugs UI, tools, and settings into a consistent host, and real projects can be created, opened, and saved.

Deliverables

  • Editor shell. A top menu bar (File / Edit / View / Project / Window / Help / module menus), a dockable/resizable panel system, status bar, and a registry of panels modules can add to.
  • Command / undo-redo stack. A central, editor-wide command stack so every tool (transform gizmos, inspector edits, and later sculpt/paint/scatter) is undoable/redoable through one mechanism — landed here so all subsequent editor tools are undoable for free.
  • Module → editor extension API. The Stage-5 Module gains an editor hook to contribute: menu items, dockable panels, viewport tools/gizmos, component inspectors (via the reflection registry), and settings pages. This is the mechanism by which "anyone can write a module" that extends both engine logic and the editor.
  • Settings / preferences framework. A unified, serialized settings system with pages contributed by the engine, the editor, and each module:
    • Editor preferences (theme, layout, viewport, shortcuts) and engine preferences (render/quality defaults) — grown incrementally as later stages add their options.
    • Module management: list installed modules, enable/disable them, and edit each module's own settings page.
    • Input bindings get their settings page here once Stage 7 lands.
  • Project system. Create / open / save a project: a project file (RON) plus a defined asset/ scene/script folder layout, a list of enabled modules and their settings, and per-project preferences. A recent-projects list and a "new project" flow. Scenes load/save within a project.
  • File-watcher foundation. Watch the project's asset/scene folders (notify) and reload changed assets live — the groundwork for Stage 10's full script hot-reload and the "edit with any external editor or AI agent" workflow.
  • Editor: the existing hierarchy/inspector/viewport are reparented into the new docking shell; a Project panel/asset browser; a Preferences window with the settings pages above.
  • Resolve the Stage-5 App naming clash. As the editor adopts the engine's core oxide_engine::app::App loop here, rename the windowing window::App trait (e.g. WindowApp) so the core App can live in the prelude unambiguously (see the Stage-5 tech-debt note).

Test Criteria

  • Panels can be docked, undocked, resized, and restored across restarts (layout persists)
  • A trivial test module adds a menu item, a panel, and a settings page purely through the extension API — with no edits to editor core
  • Undo/redo correctly reverses and re-applies a sequence of edits (e.g. rename, reparent) through the single command stack
  • Creating, saving, closing, and reopening a project restores its scenes, enabled modules, and settings unchanged (RON round-trip)
  • Enabling/disabling a module in settings adds/removes its systems and editor contributions live
  • Editing a watched asset file on disk updates the running editor without a restart
  • Manual: maintainer creates a project, rearranges the layout, toggles a module, and reopens it

Stage 7 — Input System (mapping + remappable actions) + gizmos

Goal: A comprehensive input abstraction used by all higher-level systems, with full per-key edge detection and a remappable named-action layer — and the editor's viewport transform gizmos, built on the Stage-6 command stack.

Status (2026-06-14): Complete. Shipped in six pieces — InputState (1), ActionMap / Binding (2), axis + 2D-axis actions (3), editor flythrough camera (4), input-bindings preferences page with disk persistence (5), gizmo math + viewport tab sizing + viewport gizmos (6a/6b/6c). Final maintainer test surfaced three issues (snap mid-drag, uniform-scale sensitivity, sphere winding) — all fixed in the same stage. Gamepad bindings are deferred ("where available" in the deliverables); the rest is in.

Deliverables

  • InputState: keyboard, mouse button, mouse delta, scroll, gamepad (where available)
  • Per-key edge + state queries for any physical key/button: pressed (this frame), released (this frame), and held/down (current state) — i.e. map any key to its press, up, and down events.
  • Named-action mapping with defaults and remapping. Bind named actions (e.g. "Jump") to one or more physical inputs, each action carrying a default binding (e.g. Space). Game code queries only the action name (input.action_pressed("Jump")) and never the physical key, so a project's settings screen can remap "Jump" to any other key without touching game code.
  • Bindings persistence: action maps load/save (RON) so game settings can store user remaps, surfaced through the Stage-6 settings framework.
  • Input consumed/propagated model (UI/editor can consume before game)
  • Axis/2D-axis actions (e.g. movement) composed from keys and/or gamepad sticks
  • examples/input_debug: print all actions and raw input each frame; examples/input_remap: rebind an action at runtime and show game code unaffected
  • Editor: input bindings editor page (assign/clear/remap action bindings, restore defaults) in the settings framework; live input-state debug overlay; viewport flythrough camera (WASD/QE move, mouse-look, with Shift to accelerate) layered on top of the existing orbit/pan/zoom controls
  • Editor transform gizmos: on-screen manipulators for the selected entity — translate (axis arrows + plane handles), rotate (axis circles), and scale (axis/uniform handles) — dragged directly in the viewport, every edit recorded on the Stage-6 undo stack. Includes a snap system with configurable increments (grid distance, angle step, scale step) and a modifier key to toggle snapping; switch active tool with hotkeys (e.g. W/E/R). With gizmos in place, the inspector's transform fields become directly type-editable (click to type an exact value), not drag-only.

Test Criteria

  • pressed/released fire exactly once on the correct frame; held reflects current state
  • Dragging each gizmo axis moves/rotates/scales the selection on exactly that axis; snapping constrains the result to the configured increment; typed inspector values apply exactly; each is undoable/redoable through the command stack
  • Action bindings can be changed at runtime and persisted/restored via RON
  • Multiple keys can be bound to one action; one key can drive multiple actions
  • Remapping an action changes behavior while game code (querying the action name) is unchanged
  • Mouse delta is correct across frame boundaries (no jump on first frame)

Stage 8 — Comprehensive UI System

Status (2026-06-15): 🟡 In progress. Pieces 17 complete and on main:

  1. Widget tree + layout (oxide_engine::ui::{widget, style, layout}).
  2. Styling & theming (visual, theme; cascade default → named → per-instance).
  3. Text shaping + glyph atlas (text/{font, atlas, shape} via ab_glyph).
  4. 2D overlay render pass — screen-space (4a) + world-space UiPanel (4b).
  5. Input routing (routing::Router; hit-test + hover / press / focus state machine).
  6. Events + data binding (immediate-mode RouterFrame::clicked_left(...) etc. + typed WidgetValue).
  7. examples/ui_menu — themed main menu + draggable slider + checkbox settings.

Remaining for sign-off:

  1. examples/ui_hud (health / ammo / minimap overlay + centre crosshair, composited over the Stage-4 forward pass) — signed off on main 2026-06-15 after maintainer eye-check. The atlas cache-hit claim it demonstrates is also covered by an automated GPU test (render::ui_pass::atlas_caches_glyphs_and_reaches_steady_state).
  2. Editor UI canvas — visual document builder.

Goal: A first-class, in-game UI system — widgets, layout, styling, text, and input routing — that ships inside exported games. This is distinct from the editor's egui (which stays editor-only): this UI is an engine system a project uses to build its menus, HUDs, and tools, and it consumes input through the Stage 7 model and renders through the Stage 5 pass pipeline.

Deliverables

  • Widget tree owned by the engine and renderable in shipped games: containers/panels, label/ text, button, image, checkbox, radio, slider, drop-down, text input, progress bar, and a scrollable list/view
  • Layout system: stack (row/column), grid, and anchor/dock layouts; padding/margin, alignment, and sizing modes (fixed / grow / fit-content); resolution- and DPI-aware scaling
  • Styling & theming: per-widget styles plus reusable themes (colors, fonts, spacing, borders), overridable per instance
  • Text rendering: TTF font loading, a glyph atlas, alignment, wrapping, and multi-font support
  • Input integration: UI consumes input before the game using Stage 7's consume/propagate model; hover/focus/press states; keyboard/gamepad focus navigation. (The Stage 7 bindings/remap settings screen is itself buildable with this UI system.)
  • Events & data binding: widget callbacks/events and binding widget values to game data
  • 2D overlay pass integrated as a Stage-5 render pass (batched draws), plus optional world-space UI (a UI panel rendered on a quad in 3D)
  • Serializable, dual-editable UI documents (RON): layouts round-trip through the engine's representation so they are authored in the editor and editable from scripts/AI agents
  • examples/ui_menu (main menu + settings, including action remapping) and examples/ui_hud (health/ammo/minimap overlay)
  • Editor: a visual UI canvas to build and arrange UI documents — widget palette, drag/resize, property inspector, and live preview — saving the same RON the runtime loads

Test Criteria

  • Widgets render and lay out correctly across window sizes and DPI scale factors
  • Input routing is correct: UI consumes clicks/keys over its widgets, and the game receives input only where the UI does not consume it
  • Hover/focus/press states and keyboard/gamepad focus navigation behave correctly
  • Text renders crisply with correct wrapping and alignment; the atlas handles large glyph sets
  • A UI document round-trips through RON unchanged (proving dual-editability)
  • Manual: build a menu in the editor's UI canvas, run an example, and interact with it (including remapping an action from the settings screen)

Stage 8.5 — Reflection v2, Editor Redesign & Asset Database (foundation)

Status: Complete (2026-06-16). All pieces (17) on main; piece 7c (UI canvas) eye-checked & approved. Inserted before Stage 9 (and before Stage 8's deferred piece 9) because every later stage adds component types (colliders, animation clips, particle emitters, audio sources, …) and each one would otherwise need a hand-written editor inspector and hand-wired add/remove plumbing. Doing this now was the cheapest it would ever be — it stops that per-type cost from accruing across Stages 916.

Known minor follow-up (UI canvas): egui logs Widget rect … changed id between passes while interacting with the canvas — id instability from dynamically-sized property widgets across egui's two-pass layout. Harmless (egui recovers each frame) but noisy; quiet it by scoping the canvas's per-widget sections under stable ui.push_id(...)s. Low priority.

Why: Stage 5 shipped a whole-value reflection registry (oxide_engine::reflect round-trips a whole component as RON). The editor inspector, however, is still hand-coded per type (transform_inspector, mesh_inspector in editor/src/shell.rs), and component add/remove is bespoke per type (PendingAction::AddMesh/RemoveMesh). This finishes the project's dual-editable types principle: a component's public fields should surface automatically in the inspector and over scripts (Stage 10), the Unity/Godot model — public mutable fields appear as editable inspector fields with no per-type code.

Component model — node-baked vs modular (2026-06-15 decision)

The maintainer surfaced a distinction the editor must honor explicitly:

  • Node-baked (essential): every entity inherently carries them — they're part of being a node, not a feature the user adds. Today: Node, Transform, Layer (auto-attached on Scene::spawn). Always present, single-instance, rendered in a fixed canonical order, no enable checkbox, no remove button, no drag-reorder handle, not offered by the Add Component menu.
  • Modular: the user's choice — MeshRenderer, Camera, DirectionalLight, future RigidBody, particle emitters, audio sources, custom scripts. Addable from the registry, removable, disable-able, reorderable.

The editor enforces this with ESSENTIAL_COMPONENTS = ["Node", "Transform", "Layer"] (see editor/src/shell.rs::is_essential_component). Future node-baked additions extend that list; modular ones just register through register_addable.

Layers & groups in the inspector (2026-06-16 decision — Unity model "A"). The single-valued Layer renders as a single-select dropdown (with a Layer Names editor); gameplay Groups (multi-valued, backed by Tags + a GroupRegistry) render as a multi-select dropdown (with a Groups editor). Both sit in the node-baked section above the modular list. An archetypal ECS allows only one component of a given type per entity, so a second mesh/collider/etc. lives on a child entity (the Add Component menu's "as child" path); several distinct addable types (MeshRenderer, Camera, DirectionalLight) is what makes multi-component nodes and drag-reorder exercisable.

Multi-component-of-same-type (e.g. two colliders, two meshes) is not supported by archetypal ECS (hecs allows one per type per entity) and won't be lifted in Oxide. The two principled patterns the engine offers:

  • Child entities (Bevy-style): each "additional" piece is its own entity, parented to the owner. Composes with the existing hierarchy + drag-drop reparent + per-child enable/disable.
  • Submeshes / multi-slot components (Unity-style): a single component wraps a list internally (e.g. a Mesh asset can have multiple submeshes with their own material slots).

Stage 9 (Physics) picks for colliders; Stage 13 (Shaders) may revisit for meshes. The Add Component menu surfaces this clearly when a user tries to add a duplicate ("Add Child with …" affordance — TODO in piece 4 polish).

Deliverables

  • Reflection v2 — per-field reflection + a #[derive(Reflect)] macro. A new oxide-engine-derive proc-macro crate generates, for a struct's public fields, a field table (name + type + typed get/set) re-exported from oxide_engine. reflect.rs grows from whole-value to field-level access while keeping the existing name-keyed registry API. Pure logic, fully unit-testable.
  • Generic reflection-driven inspector. One inspector that walks a selected entity's registered components and renders a widget per field by type (f32→drag, bool→checkbox, Color→picker, Vec3→vec3 drag, enum→combo, Handle<T>→asset picker), falling back to a RON text field for unknown types. The hand-written transform_inspector / mesh_inspector are deleted and proven equivalent. All edits route through the existing undo/redo command stack.
  • Editor redesign items. Hierarchy right-click context menu (Add Child / Add prefab / Rename / Duplicate / Delete); inspector "Add Component ▾" menu enumerated from the type registry; remove-component control per component. (The deferred Stage-8 piece 9 — the visual UI canvas — lands on top of this redesign so its property inspector is reflection-driven, not bespoke.)
  • Prefabs / archetypes (lightweight). A named "spawn a thing that already carries these components" entry so the hierarchy add-menu is data-driven (an entity is its component set; this is not a parallel "object type" system).
  • Asset database + typed project folders. assets/ gains typed subfolders (fonts/, textures/, models/, audio/, ui/); an asset database maps project-relative paths ↔ stable ids ↔ Handle<T> so scenes and UI documents reference "fonts/Inter-Regular.ttf" rather than absolute system paths (a prerequisite for clean game export at Stage 16). The Project panel becomes an asset browser; "import" = drop a file into the right folder and let the watcher register it. A bundled default UI font lands here (resolves the Stage-8 follow-up).
  • Handle<T> as a reflected field type so the generic inspector renders an asset-picker filtered by type — selecting a UI element then choosing its font is exactly this.
  • Docs: docs/reflection.md (per-field model + derive), docs/assets.md (database + folders + picker), and an editor-redesign note; update architecture.md.

Piece breakdown (each = one green commit)

  1. oxide-engine-derive crate + Reflect trait (per-field name/type/get/set) + derive — pure logic, unit-tested, on main. Public-only fields + #[reflect(skip)]; per-field RON get/set.
  2. reflect.rs per-field get/set through the registry via register_reflected::<T: Reflect> (field_infos / get_field / set_field); Transform + Node derive Reflect. Whole-value API unchanged. Pure logic, on main.
  3. Generic reflection-driven inspector (signed off on main 2026-06-15 after eye-check). Split:
    • 3a: EditorState.registry (Transform + Node reflected) + generic SetFieldCmd{entity,type,field,before,after} with merge-coalescing. Pure logic.
    • 3b: the inspector UI — walks the registry, one heading per component, one typed widget per field (f32→drag, bool→checkbox, Vec3→3 drags, String→text, Quat→Euler degrees, unknown→RON fallback). Deletes the hand-written transform_inspector; mesh_inspector stays until piece 4. Node skipped (name/enabled shown by the header). Enum→combo deferred (needs variant reflection).
  4. Hierarchy redesign + component lifecycle (GUI) — → dev, eye-check:
    • Hierarchy right-click context menu (Add Child / Add prefab / Rename / Duplicate / Delete).
    • Drag-and-drop reparenting in the hierarchy, and remove the inspector's Parent dropdown (maintainer request 2026-06-15: the dropdown lists every entity, so it doesn't scale to large scenes — reparenting belongs in the tree via drag-and-drop).
    • Inspector "Add Component ▾" (enumerated from the registry) + per-component remove control; make MeshRenderer reflected and retire the bespoke mesh_inspector.
    • Enum→combo widget (add variant reflection to #[derive(Reflect)] so enum fields like PrimitiveShape get a dropdown instead of the RON fallback).
  5. Prefab/archetype spawn entries (logic + a little GUI) — split:
    • 5a (engine logic, on main): oxide_engine::prefabComponentSpec/Prefab/ PrefabRegistry, data-driven (component name + RON applied via the TypeRegistry), spawn/ spawn_child/unknown_specs. Pure logic, unit-tested; docs/prefabs.md.
    • 🟡 5b (editor GUI, on dev — awaiting eye-check): EditorState.prefab_registry seeded with built-in prefabs (Empty/Cube/Sphere/Plane/Camera/Directional Light); hierarchy add-menus (toolbar Root/Child, row right-click Add Child, empty-area Add Root) are submenus listing them. Plain AddRoot/AddChild retired (the Empty prefab covers a bare node).
  6. Asset database + typed folders + Handle<T> reflected + asset-picker widget (logic → main; picker GUI → dev) — split:
    • 6a (engine logic, on main): oxide_engine::asset::AssetDatabase — typed assets/ subfolders (AssetKind: fonts/textures/models/audio/ui), stable AssetUid ↔ relative-path manifest (assets.manifest), scan/register/open/save, and uid→Handle<T> resolution through the AssetServer. Unit-tested round-trip + moved-project case; docs/assets.md.
    • 6b (engine logic, on main): asset_ref_target parses an AssetRef<T>/Handle<T> field's syntactic type_name to its target type, and AssetKind::for_handle_target maps that to the picker's filter kind. Added the serializable AssetRef<T> reference type (the form a component stores — Option<AssetUid> that resolves to a Handle<T>). Pure logic, unit-tested.
    • 🟡 6c (editor GUI, on dev — awaiting eye-check): EditorState.asset_db (opened + scanned on project open/create, rescanned on watcher events); Project panel is an asset browser (typed-folder sections from the database + Rescan); field_widget asset-picker for AssetRef<T>/Handle<T> fields (lists assets of the matching kind, stores the AssetUid); bundles Inter (SIL OFL) as the default UI font, seeded into new projects' fonts/. The first component using an AssetRef field arrives with piece 7 (UI canvas).
  7. Migrate Stage-8 piece 9 (UI canvas) onto the reflection-driven inspector — split:
    • 7a (engine logic, on main): widget-tree authoring primitives in oxide_engine::uiWidgetPath (positional addressing) + Widget::{get_path,insert_child,remove_path,move_subtree} (the basis for the canvas's undoable add/remove/move). Unit-tested.
    • 7b (engine logic, on main): FontLoader (default-registered, makes .ttf/.otf loadable via AssetServer) + VisualStyle.font_asset: Option<AssetRef<Font>> (the engine's first AssetRef<T> field; overrides the FontRef descriptor when set). Unit-tested.
    • 7c (editor GUI, on main — eye-checked & approved 2026-06-16): the UI canvas (PanelKind::UiCanvas, centre tab beside the Viewport — it needs the large central area), in a scroll area — open/new a UiPanel document; widget-tree panel (selectable, using 7a), Add ▾ palette (Leaf/Row/Column/Grid/Anchor), Remove, scaled canvas preview (egui painter), and a type-aware property panel: id/text, kind-specific (Leaf intrinsic; Stack direction/gap/main-align; Grid cols/rows/gap), visual (background/foreground/font size/font asset picker), and a Layout section (sizing, align x/y, padding, margin, anchor preset) so widgets are sized and positioned. Save to a ui/ asset; edits route through SetUiPanelCmd (undoable). Follow-ups: engine-font-accurate preview via the real UiOverlayPass; change-kind; widget drag-reorder (the 7a move_subtree primitive is ready); anchor offset/handles for free drag-positioning.

Test Criteria

  • A brand-new component (e.g. a Timer { repeating: bool, duration: f32 }) becomes fully editable in the inspector and over RON/scripts with only #[derive(Reflect, Serialize, Deserialize)] + one registration line — no per-type editor code.
  • The generic inspector reproduces the old Transform/Mesh inspectors' behavior (positions, rotation in degrees, scale, mesh shape) — verified by an automated round-trip plus a manual eye-check.
  • An asset referenced by project-relative path resolves to the same Handle<T> across save/load and survives moving the project directory.
  • Selecting a UI element in the editor and picking a font from the asset browser updates the rendered text.
  • Manual: right-click the hierarchy to add/duplicate/delete; add a component from the inspector; pick an asset for a Handle<T> field.

Editor-UX follow-ups (deferred from piece 6c — maintainer feedback 2026-06-16)

Piece 6c shipped a functional asset browser, but the maintainer flagged UX improvements to do properly later (not blockers; kept out of their stages to avoid feature creep mid-stage). Reconfirmed + expanded 2026-06-17 (during Stage 10): do this batch after Stage 10 pieces 4 (terminal panel) & 5 (error console). Each is GUI → dev + eye-check.

  • Unity-style asset/file explorer in the Project panel. Today the typed folders (fonts/ textures/ …) are fixed, so importing means creating/using a folder with that exact name. Wanted: a real file explorer — create/rename/move folders, rename/delete assets, drag files in from the OS to import, context menus, breadcrumb navigation. The AssetDatabase already tracks arbitrary relative paths, so this is a UI/interaction layer over it (re-classify by folder still applies; loose files fall back to extension).
  • Proper file/path opener for New/Open Project. Today both take a raw typed path, which the maintainer confirms "is very hard to use". Wanted: a native file/folder picker (a rfd-style dialog, working on Wayland + X11) and/or a better-designed in-editor chooser, instead of a free-typed path field.
  • "New Script" button on the Script component inspector (Stage 10 follow-up). Create a new .rhai from a template into assets/scripts/ and auto-assign it to the component, without leaving the editor — today scripts must be authored as files outside the editor. The .rhai loader + AssetKind::Script already exist; this writes a template file + registers it.
  • Open a script in an editor (Stage 10 follow-up). Double-click / button to open a .rhai in an in-editor text view or launch $EDITOR / a configured external editor; edits flow back through the existing live reload.

Stage 8.7 — Editor Play Mode

Status: Complete on main (all three pieces; piece 3 GUI eye-checked & approved 2026-06-16). Inserted before Stage 9 because Stage 9 already assumes the editor can play/pause/step the simulation to test physics, yet no piece defined that loop. Decided with the maintainer 2026-06-16: a hybrid model — in-editor play as the primary loop now, a standalone launch later. Docs: docs/play-mode.md.

Piece breakdown

  1. Play-state model + snapshot/restore main. PlayState {Editing,Playing,Paused} + play_snapshot on EditorState (enter_play/toggle_pause/stop); registry-aware oxide_engine::scene::SceneSnapshot (captures reflected components + intrinsic Tags/ DisabledComponents, not just to_ron's hierarchy), restoring the scene bit-for-bit.
  2. Schedule driver primitives main. App::step() (one fixed tick, accumulator bypassed) and oxide_editor::play::tick_for (the testable Playing→Frame / Paused+Step→FixedStep / else Idle decision).
  3. Toolbar + viewport tint + runner main. Play/Pause/Step/Stop toolbar (gated by state, badge; Resume routes through play_or_resume), Ctrl+P / Ctrl+. shortcuts, green/amber viewport border; host runner owns the play App, swaps the editor scene in/out per tick (state.scene stays the source of truth), undo cleared on Play/Stop.

Goal: Drive the engine's update Schedule from the editor so the open scene can be run, paused, single-stepped, and stopped in the viewport, with edits made during play safely reverted on stop. The same fixed-timestep tick a shipped game uses is what the editor drives, so play behaves like the real runtime.

Deliverables

  • Play-mode toolbar (Play / Pause / Step / Stop) in the editor's top bar, with matching shortcuts; a clear "PLAYING" viewport tint / indicator so edit vs play state is never ambiguous.
  • Play-state model on EditorState (Editing / Playing / Paused); the host runner ticks the Schedule (incl. the fixed-timestep slot) only while Playing, and advances exactly one fixed tick per Step while Paused.
  • Snapshot / restore. On Play, snapshot the scene (reuse the RON scene serialization); on Stop, restore it, so play-mode mutations (physics moving bodies, scripts spawning entities) never corrupt the authored scene — avoiding Unity's classic "edited in play mode, lost it" footgun.
  • Live inspect during play. The reflection inspector and gizmos keep working while Paused (and Playing), so a field can be tweaked and the result observed live — the payoff of the Stage-8.5 reflection work. (rhai scripts hot-reload with no compile step, so iteration is instant.)
  • Deferred to Stage 16 (export): a "Launch standalone" button that runs the real exported runtime in a separate window/process via the export builder — the truest-to-ship check, reusing the Stage-16 packer rather than the in-editor loop.

Test Criteria

  • Press Play → the scene's update systems run and the viewport animates; Pause halts ticking; Step advances exactly one fixed tick; Stop returns the scene bit-for-bit to its pre-play state (automated: snapshot, mutate via a tick, stop, assert the scene RON matches the snapshot).
  • Entering/leaving play mode does not leak entities, handles, or undo-stack entries into edit mode.
  • Manual: play a scene, pause, edit a reflected field, observe the change; stop and confirm the scene reverts.

Stage 9 — Physics Integration (comprehensive)

Goal: A comprehensive rigid-body physics system — collision, queries, constraints, and a character controller — built as a module (oxide-physics) on rapier3d, not a minimal wrapper.

Deliverables

  • Integrate rapier3d as a feature-gated module registered through the Stage-5 app builder
  • RigidBody component: static, kinematic, dynamic (mass, damping, gravity scale, CCD)
  • Collider component: box, sphere, capsule, cylinder, convex hull, trimesh; friction/restitution, collision groups/masks driven by the Stage-5 LayerMask, and sensor (trigger) colliders that report overlap without resolving (filtered by layer — e.g. a trigger that only fires for the Player or NPC layer)
  • Forces & control: apply force/impulse/torque, set velocities, sleep/wake
  • Joints/constraints: fixed, revolute, prismatic, spherical
  • Scene queries: raycast, shape-cast, point/overlap queries against the physics world, with LayerMask filtering
  • Kinematic character controller (capsule): move-and-slide, step offset, slope limit, grounded state — reused later by the Stage-15 prototyping kit's character controller
  • Collision/trigger events surfaced to game code (enter/stay/exit)
  • Physics step synchronized with game loop (fixed timestep with interpolation to render, using the Stage-5 fixed-timestep schedule slot)
  • examples/physics_stack (boxes fall and collide) and examples/character_capsule (walk/jump a capsule over terrain)
  • Editor: collider shape gizmos in viewport; RigidBody/Collider inspector panels; play/pause/step physics simulation; raycast debug visualization — all contributed via the Stage-6 extension API

Test Criteria

  • A dropped sphere lands on a static plane and comes to rest
  • Stacked boxes maintain stable contact without jitter
  • Kinematic bodies move without being affected by forces
  • Raycast/shape-cast return correct hits; sensor colliders fire enter/exit without resolving contact; layer masks correctly include/exclude bodies from collisions, triggers, and queries
  • A joint (e.g. revolute) constrains motion to its expected degrees of freedom
  • Character controller climbs steps below the offset, is blocked by walls, and reports grounded
  • Physics and render transforms stay in sync (no visual lag/offset) under fixed-timestep interpolation

Piece breakdown (status)

The oxide-physics crate is a new workspace member; the ECS is the source of truth and the rapier world is a transient resource rebuilt from components (so play-mode snapshot/restore works for free). Pure-logic/headless pieces go to main; editor gizmos/visual behaviour stop at dev for eye-check.

# Piece Where Status
1 Component data model + module wiring: RigidBody/RigidBodyKind, Collider/ColliderShape, PhysicsModule, PhysicsSettings (gravity) oxide-physics (new crate) on main
2 Rapier-backed simulation: build the world from components, step on FixedUpdate, write transforms back; by-entity forces/velocities/sleep oxide-physics on main
3 Collision groups/masks via LayerMask, sensors, collision/trigger events (enter/stay/exit) oxide-physics on main
4 Scene queries: raycast, shape-cast, point/overlap with LayerMask filtering oxide-physics on main
5 Joints/constraints: fixed, revolute, prismatic, spherical (programmatic API; component authoring needs serializable entity refs — backlog) oxide-physics on main
6 Kinematic character controller (capsule): move-and-slide, step offset, slope limit, grounded oxide-physics on main
7 examples/physics_stack + examples/character_capsule (headless console demos) examples on main
8a Editor integration: register RigidBody/Collider/CharacterController (addable, reflected) + enums; wire PhysicsModule into the play App (physics = first real consumer of Play; Stop reverts via snapshot) oxide-editor on main
8b Editor viewport: collider shape wireframe gizmos (box/sphere/capsule/cylinder), green=solid / amber=sensor, View ▸ Show Colliders toggle; matches the sim (ignores Transform::scale) oxide-editor on main
8c Editor viewport: raycast debug viz — PhysicsWorld::sync_to_scene (pure-logic) + a View ▸ Raycast Probe toggle: click freezes a camera→cursor ray into the world (orbit to view it as a 3D line) + hit point/normal oxide-physics + oxide-editor on main

Standalone "Launch" stays deferred to Stage 16. Convex-hull/trimesh colliders (need mesh data) land within piece 2/3 or as a follow-up.


Stage 10 — Scripting, Live Reload & Editor Terminal

Goal: Game logic lives in watched scripts that hot-reload while the editor runs, an integrated terminal hosts tools and AI agents, and every component type is editable from both the editor and scripts/code through the Stage-5 reflection registry.

Deliverables

  • Scripting layer (rhai preferred — embeddable, sandboxed, Rust-friendly): a Script component attaching a script file to an entity; lifecycle hooks (init, update(dt), events). Scripts can also register lightweight script modules through the Stage-5 module API.
  • Component reflection / dual-editability: scripts read/write engine component types through the Stage-5 reflection registry, so the same fields are editable from the editor inspector, from scripts, and from external tools/AI agents — backed by the serde/RON representation. Scripts can spawn entities and add/edit components.
  • File watching + live reload: extend the Stage-6 watcher to recompile/reload changed scripts into the running editor with no restart, preserving scene state where possible. This is what lets any external editor or AI agent edit game code and have it take effect live.
  • Integrated editor terminal panel: a real shell/terminal inside the editor able to run commands and host long-running processes — including AI agents that edit the watched scripts, whose edits flow back through live reload.
  • Safe error surfacing: a script error pauses that script and reports to the editor console/terminal without crashing the editor.
  • examples/script_spin: a script rotates an entity each frame; editing the script live changes the spin without restarting
  • Editor: terminal panel; script console (errors/logs/print); per-entity script attach/detach; generic reflection-driven inspector for component fields

Test Criteria

  • Editing a watched script changes running behavior without an editor restart
  • A script reads and writes a component (e.g. Transform) and the editor inspector reflects the same value (and vice-versa) — proving dual-editability over one representation
  • A scripted entity spawn/despawn and component add/edit round-trips through RON serialization
  • A script runtime error is reported and isolated; the editor stays alive
  • A command run in the editor terminal executes and streams output back to the panel
  • Manual: run an AI agent / external editor against the watched script dir and confirm live updates

Stage 11 — Animation System

Goal: Skeletal animation plays on a skinned mesh.

Deliverables

  • Skeleton type: joint hierarchy with bind pose
  • GLTF skinned mesh and animation clip loading (via the Stage-5 asset server)
  • AnimationPlayer component: play, pause, seek, loop
  • Animation blending (linear blend between two clips)
  • examples/anim_character: load a character GLTF and play walk/idle clips
  • Editor: animation timeline panel; clip browser; playback controls; blend weight sliders

Test Criteria

  • Idle and walk clips play without visual artifacts
  • Blend between clips produces smooth interpolation
  • Seeking to frame 0 and frame N is deterministic
  • Manual: visually verify no mesh distortion at extreme joint angles

Stage 12 — Particle System

Goal: GPU-driven particle emitters with configurable behavior.

Deliverables

  • ParticleEmitter component: spawn rate, lifetime, velocity, gravity, color-over-lifetime
  • GPU-side simulation (compute shader)
  • Billboard rendering pass (registered into the Stage-5 render pipeline)
  • Emitter types: point, cone, sphere surface
  • examples/particles_fire: a fire-like particle effect
  • Editor: particle emitter inspector with live preview; curve editor for color/size over lifetime

Test Criteria

  • 100,000 particles maintain 60 FPS on mid-tier GPU
  • Lifetime and color curves produce correct visual output
  • Emitter enable/disable works without particle artifacts
  • Manual: fire effect looks plausible

Stage 13 — Shader System & Advanced Graphics

Goal: Custom shaders, post-processing, and advanced rendering effects — the realistic end of the scalable fidelity spectrum, all composed as passes in the Stage-5 render pipeline.

Deliverables

  • Hot-reloadable WGSL shader pipeline (shaders watched via the Stage-6 file watcher)
  • Post-process stack built as composable render passes: bloom, SSAO, tone mapping, FXAA, plus a stylization slot (e.g. a VCR/CRT-style filter) — each pass opt-in, so a project ships only the effects it enables
  • Shadow mapping (directional light, single cascade)
  • Skybox rendering (HDR cubemap)
  • examples/advanced_scene: showcase scene with shadows, SSAO, bloom; examples/stylized_scene: the same scene with a flat/low-poly look and a VCR post filter (demonstrating fidelity scaling)
  • Editor: shader asset editor with hot-reload button; post-process stack configuration panel (enable/reorder passes)

Test Criteria

  • Shader hot-reload works without GPU crash or validation errors
  • Shadows appear on correct geometry, no peter-panning
  • SSAO adds visible depth cues on curved surfaces
  • Enabling/disabling individual post passes changes only that effect; a project with no post passes pays no post-process cost
  • Manual: realistic scene looks polished; stylized scene shows the low-poly + VCR look

Stage 14 — Standard Audio System

Goal: A conventional, low-overhead audio system every project can use by default — the simple audio model, with the ray-traced model available later as an opt-in module (Stage 17).

Deliverables

  • Audio backend/device + mixer (kira or rodio): load/decode clips (WAV/OGG via the asset server), play/stop/pause, volume, pitch, looping, per-bus mixing (master/music/sfx)
  • AudioSource / AudioListener components designed to be shared with Stage 17 (same component types, swappable backend) — standard spatialization: distance attenuation (inverse-square) + stereo panning from listener-relative position, optional low-pass with distance
  • One-shot and streaming playback; music crossfade
  • Per-source audio-model selector (standard now; "ray-traced" becomes selectable once the Stage-17 module is enabled) — both share components and the listener
  • examples/audio_basic: positional sound that pans and attenuates as the listener moves
  • Editor: audio source inspector (clip, volume, range, loop, bus); per-source model selector; range-sphere gizmo

Test Criteria

  • Clips play/stop/loop with correct volume and pitch; buses mix independently
  • Panning and attenuation track listener-relative position (inverse-square within 5% error)
  • Selecting a non-standard audio model on a source requires no component-type change (forward-compat with Stage 17)
  • Manual: positional audio feels correct moving around a source

Stage 15 — Engine Content & Prototyping Kit

Goal: Ship the batteries — built-in primitives, controllers, shaders, and example scenes — so projects start building immediately instead of from an empty scene. The general-purpose capstone before export.

Deliverables

  • Prototyping primitives: ready-to-use cube/sphere/capsule/plane/cylinder meshes and a grid/ checker "prototype" material, placeable from the editor (greybox blockout workflow)
  • Built-in character controller asset: a drop-in first/third-person controller built on the Stage-9 capsule controller + Stage-7 named actions (move/look/jump/crouch/sprint), remappable
  • Built-in shader library: curated, documented WGSL shaders/materials (unlit, PBR, prototype grid, simple sky) usable as-is or as templates for Stage-13's shader editor
  • Example games/scenes: a small set of runnable example projects exercising the engine end to end (e.g. a walk-around prototype level, a physics playground)
  • Editor: an "Add ▸" content menu/asset browser exposing the primitives, controller, materials, and example scenes
  • Everything here is built only from already-shipped engine systems (no new core subsystems)

Test Criteria

  • Each prototyping primitive spawns and renders correctly from the editor
  • The built-in character controller walks/looks/jumps on a prototype level with remappable actions
  • Built-in shaders compile and render without validation errors
  • Example projects build and run via cargo run and from the editor
  • Manual: blockout a small level using only built-in content and play it with the controller

Stage 16 — Windows Support & Game Export

Goal: A Windows build of the editor/engine, and one-click export of a project to standalone Linux and Windows game binaries. This completes Phase 1: a general-purpose engine that builds and ships real games.

Deliverables

  • Windows build of oxide-engine and oxide-editor (verified on Windows; wgpu DX12/Vulkan, winit Win32) alongside the existing Linux Wayland/Xorg build
  • Game runtime/export pipeline: package a project's scenes, scripts, and assets into a standalone runnable game that links the engine runtime (without the editor), compiling in only the modules the project enables (validating the Stage-5 feature-gating)
  • Cross-target export: from the editor (on Linux) export to both a Linux binary and a Windows binary (cross-compile, e.g. x86_64-pc-windows-gnu), bundling assets next to the executable
  • Export presets (target platform, asset packing, build profile) and a CLI equivalent for CI
  • Packaging: keep install.sh for the editor; add export output layout docs for both platforms
  • examples/: export one of the Stage-15 example projects to Linux and Windows binaries
  • Editor: "Export Project" dialog (choose Linux/Windows targets, output dir, profile)

Test Criteria

  • The editor builds and runs on Windows (Wayland/Xorg Linux build remains unaffected)
  • A project exports to a Linux binary that runs standalone (no editor, no toolchain) with its assets
  • The same project cross-exports to a Windows binary that runs on Windows
  • Exported binaries contain no editor-only code paths and none of the modules the project didn't use
  • Manual: run an exported Linux build and an exported Windows build of an example project

Phase 2 — Built-in Modules

Each Phase-2 stage is a self-contained, feature-gated module built on Phase-1 systems and wired in through the Stage-5 module API and Stage-6 editor extension API. Every module ships documentation for both using it and authoring one like it, and each automatically works with export (Stage 16) and the settings framework (Stage 6). The order below is a default; modules are largely independent and can be reprioritized to match the needs of the first real game built in Oxide.

Stage 17 — Ray-Traced Spatial Audio (module)

Goal: Sound propagation that simulates real wave behavior from scene geometry — the advanced audio model, built on the Stage-14 components so projects opt in per source.

Deliverables

  • Reuses AudioSource / AudioListener from Stage 14 (ray-traced model selected per source)
  • Sound ray casting: shoot rays from source, accumulate energy at listener (built on the engine's own ray casts / LayerMask filtering)
  • Occlusion: blocked rays reduce volume
  • Reflection: rays bounce off surfaces and arrive with delay (reverb model)
  • Distance attenuation using inverse square law
  • examples/sound_cave: walk around a cave with echoing footstep sounds
  • Editor: audio source visualizer (range sphere, ray debug lines); reverb parameter inspector; the module's settings page

Test Criteria

  • Sound behind a wall is measurably quieter than line-of-sight
  • Moving away from source follows inverse square law within 5% error
  • Reverb tail length correlates with room size
  • Switching a source between standard and ray-traced models requires no component-type change
  • Manual: walk through the cave example and confirm intuitive sound behavior

Stage 18 — Developer Console & Cheats (module)

Goal: A drop-in developer console any game can enable for commands, cheats, and debugging help — trivially added to a project and removed from release builds.

Deliverables

  • In-game console overlay (built on the Stage-8 UI) with command input, history, autocomplete, and log output; toggled by a Stage-7 action (default backtick)
  • Command registry: register named commands with typed arguments from engine, game code, scripts, or other modules; built-in commands (spawn, teleport, set var, toggle layer, time scale, etc.)
  • Cheats / dev variables (cvars): registerable toggles and values (godmode, noclip, give, fly) with a guard so they can be compiled out or locked in release/export builds
  • Bind console commands to keys via the Stage-7 input system for quick dev shortcuts
  • examples/console_demo: a scene with registered cheats and commands
  • Editor: command browser/inspector; console mirrored in the editor; the module's settings page (enable in build, default key, release lockout)

Test Criteria

  • Registered commands execute with correct typed-argument parsing; unknown commands report cleanly
  • Cheats/cvars change game state live and can be disabled/compiled out for release
  • The console consumes input over the game via the Stage-8 routing model
  • Console state and command registry are unaffected by hot-reload of game scripts
  • Manual: enable the module in a game, open the console, run commands and toggle a cheat

Stage 19 — Procedural Toolkit (module)

Goal: Composable procedural building blocks — noise and a modifier/operation stack the user combines — not a fixed world generator. Consumed by the terrain module (Stage 20) and usable standalone. Build tools, not the game.

Deliverables

  • Noise library: Perlin, Simplex, Worley, fractal octaves, domain warping (deterministic, seeded)
  • Modifier/operation stack: chainable operations over heightfields/value grids (add, multiply, terrace, erode, mask, remap, blend) authored as a data-driven, serializable graph/stack
  • Seeded RNG utilities and distribution helpers for scatter/placement
  • Simple L-system helper for vegetation/structure layout
  • Region/Biome helper: blend by temperature/humidity/height maps (a tool, not a built-in world)
  • examples/procgen_playground: build a noise + modifier stack and preview the result
  • Editor: noise preview panel (2D heatmap); modifier-stack editor with live re-evaluation; seed controls; the module's settings page

Test Criteria

  • Same seed + same stack always produces identical output (determinism)
  • Each modifier transforms its input correctly and composes in a chain
  • A modifier stack round-trips through RON unchanged (dual-editable)
  • Benchmark: evaluating a representative stack over a 1024² grid completes within target time
  • Manual: build and preview a multi-stage noise stack in the editor

Stage 20 — Terrain System (module)

Goal: A comprehensive terrain module: generate terrain (via the Stage-19 toolkit), sculpt and paint it with editor brushes, and scatter vegetation/objects onto it. Designed to stream later under Stage 21.

Deliverables

  • Terrain representation: chunked heightmap terrain with a meshing pass (normals, UVs) and collider generation (heightfield collider via Stage 9); LOD-ready chunk layout
  • Generators: drive terrain from the Stage-19 noise + modifier stack, from basic Perlin/Simplex up to layered erosion/domain-warp stacks and biome-driven generation
  • Sculpt brush tools (editor): raise/lower, smooth, flatten, noise — adjustable radius, strength, falloff; live edits, undoable through the Stage-6 command stack
  • Texture/splat painting (editor): paint multiple terrain material layers (e.g. grass/rock/sand) with a splat map and per-layer tiling; brush-based painting with falloff
  • Foliage & object scatter (editor): paint/spray grass, trees, rocks, or arbitrary prefabs with density/scale/rotation jitter and slope/height masks; instanced rendering for dense foliage, entity placement for larger objects
  • Dual-editable & serializable: terrain data (heightmap, splat layers, scatter sets) round-trips through RON so it is editable from editor and scripts/AI agents
  • examples/terrain_playground: generate a terrain, sculpt/paint it, scatter grass and trees
  • Editor: terrain tool panel (generate, sculpt, paint, scatter sub-tools); brush settings; layer/material list; foliage/prefab palette; the module's settings page

Test Criteria

  • A generated terrain produces correct mesh normals and a matching heightfield collider
  • The same seed/generator stack produces identical terrain (determinism)
  • Sculpt and paint brush edits modify the heightmap/splat map correctly and are undoable
  • Scatter respects density and slope/height masks; instanced foliage renders without per-instance entity overhead
  • Terrain + splat + scatter data round-trips through serialization unchanged
  • Manual: sculpt, paint layers, and scatter grass/trees on a terrain and confirm it collides correctly

Stage 21 — Open World Support (module)

Goal: Streaming, chunked worlds that can exceed memory limits.

Deliverables

  • World chunk system: load/unload chunks based on camera position (built on the Stage-5 asset server)
  • Async asset streaming (chunks load on background threads)
  • Level of Detail (LOD) system for meshes and terrain
  • Entity streaming: entities activate/deactivate with their chunk
  • examples/open_world: a large world that streams seamlessly as you move
  • Editor: world chunk map overlay; streaming debug view (loaded/unloaded chunks); LOD visualizer; the module's settings page

Test Criteria

  • No frame drops during chunk load/unload transitions
  • Memory usage stays bounded regardless of world size
  • Entities in unloaded chunks do not tick (verified via counters)
  • Manual: drive across chunk boundaries and confirm invisible seams

Stage 22 — Pathfinding & NPC AI (module)

Goal: Built-in navigation and reusable NPC behavior building blocks, so games get agents that move and decide without writing it all from scratch.

Deliverables

  • NavMesh: bake a navigation mesh from scene/terrain geometry (with LayerMask-driven walkable/obstacle filtering); runtime queries (path find, nearest point, path smoothing)
  • Agent component: follow a path with steering, avoidance, speed/acceleration, and grounded movement via the Stage-9 character controller
  • NPC AI building blocks: a behavior-tree and/or state-machine system, perception helpers (vision cones / hearing using scene queries and layers), and steering behaviors (seek/flee/wander/ patrol) — composable, not a fixed AI
  • Dual-editable, serializable behavior/agent definitions
  • examples/npc_patrol: agents navigate, patrol waypoints, chase the player, and avoid obstacles
  • Editor: navmesh bake/visualize panel; agent inspector; behavior-tree/state-machine editor; the module's settings page

Test Criteria

  • A baked navmesh produces correct paths around obstacles; queries return valid points
  • Agents follow paths, avoid each other, and stop at goals without jitter
  • A behavior tree/state machine transitions correctly and is serializable (dual-editable)
  • Perception correctly gates on layers and line-of-sight
  • Manual: watch NPCs patrol, detect, chase, and give up

Stage 23 — Water System (module)

Goal: Built-in water — rendering and gameplay mechanics — that a project can drop in.

Deliverables

  • Water rendering: animated water surface (waves/normals), reflection/refraction, depth-based color/fog, shoreline blending — composed as Stage-5 render passes and a Stage-13 shader material
  • Water bodies: planar/ocean and bounded volumes (lakes/pools) with configurable height/level
  • Gameplay mechanics: buoyancy/floating on Stage-9 rigid bodies, water-volume triggers (enter/exit, submerged state), simple flow/current forces, and a swim mode for the Stage-15 character controller
  • Optional underwater post-process (tint, distortion, muffled audio hook for Stages 14/17)
  • Dual-editable, serializable water-body definitions
  • examples/water_scene: a floating object, a swimmable lake, and an ocean horizon
  • Editor: water body inspector and placement gizmos; surface/material settings; the module's settings page

Test Criteria

  • Water renders with plausible reflection/refraction and correct depth blending at shorelines
  • Rigid bodies float and settle at the correct waterline; flow forces push bodies as configured
  • Water-volume triggers fire enter/exit and report submerged state correctly
  • The character controller enters swim mode in water and exits on leaving
  • Manual: drop objects in water, swim the character, and confirm it looks and behaves correctly

Backlog (additional modules & post-roadmap capabilities)

Editor UX / inspector (raised 2026-06-16, maintainer GUI review)

  • Editor visual overhaul — overall styling, spacing, and panel layout need a dedicated polish pass (egui theme, inspector/panel density). Tracked as a standalone editor-styling piece, not folded into feature work.

  • Component gizmos / viewport handles — components with spatial meaning need editor-only viewport visualization: DirectionalLight direction arrow (+ cone for future spot lights), camera frustum, collider/audio shapes. Pairs with the multiple-light-types work below.

  • Multiple light typesDirectionalLight is the only light today; add point + spot (and the renderer support to gather light entities, which the reflected DirectionalLight/Camera components are scaffolding toward).

  • Component multiplicity model (DECIDED 2026-06-16 — hybrid). Archetypal ECS (hecs) allows one component of a given type per entity. Chosen approach, applied as each component type lands:

    • Local-offset field for things that are naturally single per node but positioned — a collider's center, a light/audio emitter offset — stored as a Vec3/Transform field on the component (one node, no extra hierarchy). This mirrors Unity (BoxCollider.center).
    • Child entities for genuine multiples (several meshes on one logical object): each is a real node with its own Transform, gizmo-movable and shown in the viewport — what Unity/Bevy do for multi-mesh. The "Add … (as child)" path stays; editor UX is polished so it doesn't feel like clutter.
    • Explicitly not doing internal multi-instance lists + sub-object gizmos (rejected as the biggest lift / least ECS-native). Concretely: colliders (Stage 9) and audio sources (later) get offset fields; multi-mesh stays child-based.
  • Networking (deterministic lockstep or rollback for simulation use)

  • Multi-threading and job system (rayon or custom)

  • Dynamically loadable native plugins (C-ABI/dylib) — currently modules are compile-time crates or rhai scripts; revisit once the static module API is proven

  • Save-format/asset versioning and migration (backward compatibility for shipped projects)

  • VR/XR support

  • Volumetric fog and clouds

  • GPU-driven rendering (indirect draw, bindless resources)

  • Terrain deformation at runtime

  • Fluid simulation (beyond the Stage-23 water surface model)

  • Vehicle physics module

  • Destruction / fracture module

  • SDF (signed-distance-field) text rendering for the Stage-8 UI — each glyph rasterized once at one canonical size as an SDF, then scaled arbitrarily in the shader. Decouples atlas memory from font-size combinatorics (HiDPI, animated scale, many sizes) and is how Unity TextMeshPro / Unreal Slate / Godot 4's font system all ship. Slots in next to the piece-3 ab_glyph rasterizer without rewriting the shaper.


Testing Protocol (All Stages)

  1. Unit tests — every module has tests for core behavior and edge cases
  2. Integration tests — the tests/ crate runs end-to-end scenarios
  3. Example review — each stage ships at least one runnable example; both user and Claude review it
  4. Benchmarks — performance-sensitive systems have criterion benchmarks; regressions block stage completion
  5. Clippy + fmt — zero warnings, consistent formatting required before stage sign-off
  6. Module authoring docs — every module (Stage 9 on) ships both a usage guide and an authoring guide under docs/; a stage is not done until its docs land