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>
79 KiB
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 0–16). 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'swayland+x11backends, 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/winitalready 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
Moduletrait; lightweight modules can berhaiscripts. - 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 1–8 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 fmtall pass - Empty integration test harness
README.mdwith engine description, build instructions, and install instructionsinstall.sh— builds release binaries and installs to/usr/local(or$PREFIX)- Editor stub:
oxide-editorbinary that starts, logs, and exits cleanly
Test Criteria
cargo buildsucceeds from clean checkoutcargo testruns (even with zero tests) without error- Clippy reports zero warnings with
deny(warnings) install.shcompletes without errors on a clean Linux systemoxide-editorbinary runs after installation
Stage 1 — Math & Core Primitives ✅
Goal: Solid math foundation every other system will depend on.
Deliverables
- Choose and integrate math library (
glampreferred) — done (glamwithserde) Transformtype: position, rotation (quaternion), scale —engine/src/math/transform.rsAABB,Ray,Plane,Frustumtypes —engine/src/math/{aabb,ray,plane,frustum}.rs- Common utility types:
Color,Rect,Range3—engine/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
testsmodule - 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
Transformstores decomposed TRS; composition takes an exact fast path for uniform scale and re-decomposes from the matrix for the non-uniform case.Range3is a value range (clamp/lerp/remap), distinct fromAabb(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
winitintegration for window creation and event loop —engine/src/window/(Apptrait,AppCtx,WindowConfig,run)wgpudevice/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
WindowEventforwarded toApp::event; types re-exported viawindow::event - Editor: opens its own window using this infrastructure; displays a placeholder viewport (dark clear, raw input logged,
Ctrl+Qquits)
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_windowkeys 1–5/Space) - Manual: run the
hello_windowexample 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/winitare 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 asoxide_engine::hecs) Scenetype: add/remove/query entities —engine/src/scene/graph.rsTransformhierarchy (parent-child with world-space resolution) —Scene::world_transform/world_transformsNodecomponent: 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(orderedroots/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'sEguiLayerdraws 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
Meshtype: vertex buffer (position, normal, UV), index buffer- GLTF loader (static meshes only, no animation yet)
- Forward renderer: depth buffer, basic lit pass
Materialtype: 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/Engineassembled by registeringModules. AModule::build(&mut App)hook registers: update systems (with an explicit ordering/schedule), component types, asset loaders, and (via Stage 6) editor integration.DefaultModulesbundles 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 theTagscomponent; the project's valid group names live in aGroupRegistryso the editor offers a fixed vocabulary to pick from. Separate from the hot-pathLayerMask.
- 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-valuedLayer, multi-valuedTags+GroupRegistry. - ✅ Reflection/type registry (
oxide_engine::reflect) — generic name-keyed component access. - ✅ Asset server + handles (
oxide_engine::asset) — ref-countedHandle<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) — composableRenderPasslist,ClearPass/ForwardPass, cameraLayerMaskvisibility; editor viewport +hello_meshretrofitted. Maintainer manual sign-off 2026-06-12 (viewport +hello_meshrender identically).
Known tech-debt (tracked): there are now two
Apptypes — the engine containeroxide_engine::app::Appand the windowingoxide_engine::window::Apptrait (the per-window event handler). To avoid the clash the coreAppis intentionally not in the prelude (import it asoxide_engine::app::App);window::Appstays in the prelude. Resolve by renaming the windowing trait (e.g.WindowApp/AppHandler) when the editor adopts the coreApploop 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
LayerMaskfiltering 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
Modulegains 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
Appnaming clash. As the editor adopts the engine's coreoxide_engine::app::Apploop here, rename the windowingwindow::Apptrait (e.g.WindowApp) so the coreAppcan 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), andheld/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/releasedfire exactly once on the correct frame;heldreflects 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 1–7 complete and on main:
- ✅ Widget tree + layout (
oxide_engine::ui::{widget, style, layout}). - ✅ Styling & theming (
visual,theme; cascade default → named → per-instance). - ✅ Text shaping + glyph atlas (
text/{font, atlas, shape}viaab_glyph). - ✅ 2D overlay render pass — screen-space (4a) + world-space
UiPanel(4b). - ✅ Input routing (
routing::Router; hit-test + hover / press / focus state machine). - ✅ Events + data binding (immediate-mode
RouterFrame::clicked_left(...)etc. + typedWidgetValue). - ✅
examples/ui_menu— themed main menu + draggable slider + checkbox settings.
Remaining for sign-off:
- ✅
examples/ui_hud(health / ammo / minimap overlay + centre crosshair, composited over the Stage-4 forward pass) — signed off onmain2026-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). - ⬜ 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) andexamples/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 (1–7) 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
9–16.
Known minor follow-up (UI canvas): egui logs
Widget rect … changed id between passeswhile 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 stableui.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 onScene::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, futureRigidBody, 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
Meshasset 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 newoxide-engine-deriveproc-macro crate generates, for a struct's public fields, a field table (name + type + typed get/set) re-exported fromoxide_engine.reflect.rsgrows 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-writtentransform_inspector/mesh_inspectorare 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; updatearchitecture.md.
Piece breakdown (each = one green commit)
- ✅
oxide-engine-derivecrate +Reflecttrait (per-field name/type/get/set) + derive — pure logic, unit-tested, onmain. Public-only fields +#[reflect(skip)]; per-field RON get/set. - ✅
reflect.rsper-field get/set through the registry viaregister_reflected::<T: Reflect>(field_infos/get_field/set_field); Transform + Node deriveReflect. Whole-value API unchanged. Pure logic, onmain. - ✅ Generic reflection-driven inspector (signed off on
main2026-06-15 after eye-check). Split:- 3a:
EditorState.registry(Transform + Node reflected) + genericSetFieldCmd{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-writtentransform_inspector;mesh_inspectorstays until piece 4.Nodeskipped (name/enabled shown by the header). Enum→combo deferred (needs variant reflection).
- 3a:
- 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
MeshRendererreflected and retire the bespokemesh_inspector. - Enum→combo widget (add variant reflection to
#[derive(Reflect)]so enum fields likePrimitiveShapeget a dropdown instead of the RON fallback).
- Prefab/archetype spawn entries (logic + a little GUI) — split:
- ✅ 5a (engine logic, on
main):oxide_engine::prefab—ComponentSpec/Prefab/PrefabRegistry, data-driven (component name + RON applied via theTypeRegistry),spawn/spawn_child/unknown_specs. Pure logic, unit-tested;docs/prefabs.md. - 🟡 5b (editor GUI, on
dev— awaiting eye-check):EditorState.prefab_registryseeded 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. PlainAddRoot/AddChildretired (theEmptyprefab covers a bare node).
- ✅ 5a (engine logic, on
- 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— typedassets/subfolders (AssetKind: fonts/textures/models/audio/ui), stableAssetUid↔ relative-path manifest (assets.manifest),scan/register/open/save, and uid→Handle<T>resolution through theAssetServer. Unit-tested round-trip + moved-project case;docs/assets.md. - ✅ 6b (engine logic, on
main):asset_ref_targetparses anAssetRef<T>/Handle<T>field's syntactictype_nameto its target type, andAssetKind::for_handle_targetmaps that to the picker's filter kind. Added the serializableAssetRef<T>reference type (the form a component stores —Option<AssetUid>thatresolves to aHandle<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_widgetasset-picker forAssetRef<T>/Handle<T>fields (lists assets of the matching kind, stores theAssetUid); bundles Inter (SIL OFL) as the default UI font, seeded into new projects'fonts/. The first component using anAssetReffield arrives with piece 7 (UI canvas).
- ✅ 6a (engine logic, on
- Migrate Stage-8 piece 9 (UI canvas) onto the reflection-driven inspector — split:
- ✅ 7a (engine logic, on
main): widget-tree authoring primitives inoxide_engine::ui—WidgetPath(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/.otfloadable viaAssetServer) +VisualStyle.font_asset: Option<AssetRef<Font>>(the engine's firstAssetRef<T>field; overrides theFontRefdescriptor 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 aUiPaneldocument; 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 aui/asset; edits route throughSetUiPanelCmd(undoable). Follow-ups: engine-font-accurate preview via the realUiOverlayPass; change-kind; widget drag-reorder (the 7amove_subtreeprimitive is ready); anchor offset/handles for free drag-positioning.
- ✅ 7a (engine logic, on
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. TheAssetDatabasealready 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
Scriptcomponent inspector (Stage 10 follow-up). Create a new.rhaifrom a template intoassets/scripts/and auto-assign it to the component, without leaving the editor — today scripts must be authored as files outside the editor. The.rhailoader +AssetKind::Scriptalready exist; this writes a template file + registers it. - Open a script in an editor (Stage 10 follow-up). Double-click / button to
open a
.rhaiin 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
- Play-state model + snapshot/restore — ✅
main.PlayState {Editing,Playing,Paused}+play_snapshotonEditorState(enter_play/toggle_pause/stop); registry-awareoxide_engine::scene::SceneSnapshot(captures reflected components + intrinsicTags/DisabledComponents, not justto_ron's hierarchy), restoring the scene bit-for-bit. - Schedule driver primitives — ✅
main.App::step()(one fixed tick, accumulator bypassed) andoxide_editor::play::tick_for(the testable Playing→Frame / Paused+Step→FixedStep / else Idle decision). - Toolbar + viewport tint + runner — ✅
main. Play/Pause/Step/Stop toolbar (gated by state, badge; Resume routes throughplay_or_resume), Ctrl+P / Ctrl+. shortcuts, green/amber viewport border; host runner owns the playApp, swaps the editor scene in/out per tick (state.scenestays 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 theSchedule(incl. the fixed-timestep slot) only whilePlaying, and advances exactly one fixed tick per Step whilePaused. - 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(andPlaying), 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
rapier3das a feature-gated module registered through the Stage-5 app builder RigidBodycomponent: static, kinematic, dynamic (mass, damping, gravity scale, CCD)Collidercomponent: box, sphere, capsule, cylinder, convex hull, trimesh; friction/restitution, collision groups/masks driven by the Stage-5LayerMask, 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
LayerMaskfiltering - 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) andexamples/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 (
rhaipreferred — embeddable, sandboxed, Rust-friendly): aScriptcomponent 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
Skeletontype: joint hierarchy with bind pose- GLTF skinned mesh and animation clip loading (via the Stage-5 asset server)
AnimationPlayercomponent: 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
ParticleEmittercomponent: 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 (
kiraorrodio): load/decode clips (WAV/OGG via the asset server), play/stop/pause, volume, pitch, looping, per-bus mixing (master/music/sfx) AudioSource/AudioListenercomponents 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 runand 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-engineandoxide-editor(verified on Windows;wgpuDX12/Vulkan,winitWin32) 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.shfor 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/AudioListenerfrom 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 /
LayerMaskfiltering) - 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/
Biomehelper: 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:
DirectionalLightdirection arrow (+ cone for future spot lights), camera frustum, collider/audio shapes. Pairs with the multiple-light-types work below. -
Multiple light types —
DirectionalLightis the only light today; add point + spot (and the renderer support to gather light entities, which the reflectedDirectionalLight/Cameracomponents 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 aVec3/Transformfield 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.
- Local-offset field for things that are naturally single per node but
positioned — a collider's
-
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
rhaiscripts; 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_glyphrasterizer without rewriting the shaper.
Testing Protocol (All Stages)
- Unit tests — every module has tests for core behavior and edge cases
- Integration tests — the
tests/crate runs end-to-end scenarios - Example review — each stage ships at least one runnable example; both user and Claude review it
- Benchmarks — performance-sensitive systems have
criterionbenchmarks; regressions block stage completion - Clippy + fmt — zero warnings, consistent formatting required before stage sign-off
- 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