Architecture
This document explains how Oxide is structured and the principles that govern how it is built. For per-system detail, see the topic documents (e.g. math.md).
Goals
Oxide is a general-purpose 3D game engine written in Rust, built to make any
3D game — scaling from stylized low-poly to realistic graphics — and to ship
only what each game uses. It is built in two phases: a general-purpose engine
(Phase 1, scene graph, render pass pipeline, input, UI, physics, scripting,
animation, particles, shaders, audio, content kit, and game export) and a set of
optional, feature-gated built-in modules (Phase 2: ray-traced sound, developer
console, procedural toolkit, terrain, open world, pathfinding/AI, water). The
guiding idea is build tools, not games, all driven through a first-class
in-engine editor. See PLAN.md for the staged roadmap.
Design philosophy
These principles are non-negotiable and shape every decision:
- Build in stages. Each stage produces a usable, standalone artifact and
must be fully tested and stable before the next begins. See
development.md and
PLAN.md. - Composability. Every system should be usable independently of the others. You should be able to pull in the math module, or the scene graph, without dragging in the renderer.
- Correctness and clarity over premature optimization. Optimize when a benchmark says to, not before.
- Minimal public surface. Internal complexity is fine; the external API
should be small and clean. Modules expose a curated set of types through
pub use, not their whole internal structure. - No feature creep between stages. New ideas go to the backlog, not into the current stage.
- The editor grows with the engine.
oxide-editoris a first-class deliverable, gaining panels and tools as each stage adds systems. - Build tools, not games. Ship composable building blocks; genre-specific behavior belongs in game code or optional modules.
- Ship only what's used. Subsystems are feature-gated modules (from Stage 5); an exported game compiles in only the modules it registers.
- Scalable fidelity. A data-driven render pass pipeline lets a project run anything from a flat low-poly/stylized look to a full realistic stack, paying only for the passes it enables.
- Modules are the primary extension point. A module registers engine logic and editor UI and its own settings through one documented API; anyone — including AI agents — can write one.
Workspace layout
Oxide is a single Cargo workspace. Crates share version, edition, license, and
dependency versions through [workspace.package] and [workspace.dependencies]
in the root Cargo.toml.
Oxide/
├── engine/ # oxide-engine — the core library (all engine systems)
├── editor/ # oxide-editor — the in-engine editor binary
├── examples/ # oxide-examples — runnable examples, one+ per stage
├── tests/ # oxide-tests — integration / end-to-end test harness
├── docs/ # this documentation
├── assets/ # logos and shared assets
├── install.sh # release build + system install
├── PLAN.md # authoritative staged roadmap
├── README.md # short project overview
└── CLAUDE.md # rules and context for AI-assisted development
Crate responsibilities
oxide-engine— the library that contains every engine system. It is organized as one module per system (math, and laterscene,render,physics, …). Each module is independently usable and re-exports its public types. Apreludemodule collects the most common imports.oxide-editor— the binary users run. It depends onoxide-engineand builds a UI on top of engine systems (its own window with a placeholder viewport since Stage 2; egui panels from Stage 3). It never contains engine logic itself; it is a consumer of the engine.oxide-examples— small, focused programs that each demonstrate one stage's capabilities. They double as manual-review artifacts and as living documentation.publish = false; they are never installed.oxide-tests— integration tests that exercise the engine the way a real consumer would, including cross-module scenarios and fuzz/property tests.
Engine module structure
Inside oxide-engine, each system is a module under engine/src/. The pattern,
established by the math module, is:
engine/src/
├── lib.rs # declares modules, defines the prelude
├── math/
│ ├── mod.rs # module docs + curated `pub use` re-exports
│ ├── transform.rs # one type/concept per file, with its own tests
│ ├── aabb.rs
│ └── ...
├── render/ # Stage 2: GPU acquisition + surface clear loop
│ ├── mod.rs # RenderError, clear_view, re-exports
│ ├── gpu.rs # Gpu (instance/adapter/device/queue)
│ └── context.rs # RenderContext (surface, resize, render_frame)
└── window/ # Stage 2: window + event loop + App trait
├── mod.rs # WindowConfig, `event` re-export module
├── app.rs # App trait, AppCtx
└── runner.rs # winit ApplicationHandler internals
Rules of thumb:
- One concept per file. Prefer many small focused files over a few large ones.
- Tests live with the code. Each file has a
#[cfg(test)] mod testsblock covering core behavior and edge cases. - The module root curates the API.
mod.rsdecides what is public viapub use; submodules are private (mod foo;, notpub mod foo;) unless there is a reason to expose the path. - The prelude is the front door.
oxide_engine::prelude::*brings in the types a typical consumer needs, including re-exported third-party math types so downstream code needs only one dependency for everyday work.
Key dependencies
Chosen for portability and a lightweight footprint:
| Concern | Crate | Notes |
|---|---|---|
| Math | glam |
SIMD-friendly vectors/quats/matrices; serde feature enabled |
| Graphics | wgpu |
Portable across Vulkan/Metal/DX12 (since Stage 2; re-exported as oxide_engine::wgpu) |
| Windowing | winit |
Cross-platform windows and events (since Stage 2; re-exported as oxide_engine::winit) |
| ECS | hecs |
Lightweight archetypal ECS (Stage 3+) |
| Physics | rapier3d |
Rigid bodies and collision (Stage 6+) |
| Editor UI | egui |
Immediate-mode UI (Stage 3+) |
| Logging | log + env_logger |
Facade + env-driven backend |
| Errors | anyhow + thiserror |
Application vs. library error handling |
| Serialization | serde + ron |
Scene/asset (de)serialization (Stage 3+) |
Error handling and logging
- Libraries (
oxide-engine) define their own error types withthiserrorso callers can match on failure modes. - Binaries (
oxide-editor, examples) useanyhowfor ergonomic error propagation at the top level. - Logging uses the
logfacade throughout the engine; binaries initialize a backend (env_logger). Control verbosity withRUST_LOG, e.g.RUST_LOG=oxide_engine=debug cargo run -p oxide-examples --bin math_demo.
Installability
Oxide must remain installable as a Linux package at all times. install.sh
builds in release mode and installs the oxide-editor binary and assets under a
prefix (/usr/local by default, overridable with PREFIX). Any new installed
binary or asset must be reflected in install.sh in the same change.
See also
- conventions.md — coordinate system, units, color space
- development.md — workflow, testing, and how stages progress
PLAN.md— the full staged roadmap