game-engine
Custom Rust game engine. World owns all state, everything else is a derived view. Render, UI, audio just read snapshots and do their thing. Not built on top of anything.
Fixed-step deterministic tick loop. Archetype ECS with ~32KiB SoA chunks. Runs headless without a window. March 2026, clean build, ~2,656 tests. Host P0 through P24 done. Audio P0 through P6 done. Editor phases 1 through 6 done. Every Tier 2 crate audited.
Workspace checkcargo check --workspace --all-features clean
Workspace testscargo test --workspace ~2,656 passing
Host lintcargo clippy -W clippy::all clean
Crate Map
Lower tiers can't import higher ones. No circular deps.
Tier 0
- fixed-step tick, 5-stage pipeline
- TypedCommandBuffer, zero-alloc steady state
- EventBus, bounded, per-producer, deterministic merge
- TickClock with O(1) consume, Xoshiro256++ RNG, Lemire range
- per-thread ring-buffer metrics, CrashContext, replay log
Tier 1
- archetype-chunk ECS (~32KiB SoA)
- generational Entity (u32+u32)
- Query, Resources, Relations
- Hierarchy, Snapshots
- Schedule with 5 stages, Plugin trait
- SimLoop wrapping TickLoop + Schedule
#[derive(Component, Bundle, Relation)]
Tier 2
Core runtime
- access sets
- Kahn topo sort
- wave packing
- persistent pool
- shadow mode
- adaptive LPT
- wgpu 27
- RenderSnapshot
- GpuCache<V>
- LRU eviction
- debug lines
- culling
- cook pipeline
- AssetServer
- AssetIndex
- hot-reload
- streaming
- blocking thread pool
- file watcher
- sqlite writes
- log flush
Presentation and scene
- Slint
- UiSnapshot
- UiCommands
- audio thread
- lock-free
- ring buf cmds
- voice pool
- spatial
- Transform
- LocalToWorld
- MeshInstance
- Camera, Light
- extraction sys
- glTF 2.0 loader
- MeshData
- MaterialData
- entity spawner
- AssetStorage reg
Tier 3
- winit event loop, wgpu window, input normalization, gamepad
- Starlark scripting: sandboxed, budgeted, capability-gated
- native plugin loading: C ABI, NativePluginVTable, ABI versioning
- lifecycle, profiling via Tracy + Puffin, replay, hot-reload
- IO boundary dispatch, config wiring for all Tier 2 plugins
Data Flow
- Devices → Host input queueKeyboard, mouse, and gamepad input gets normalized by host and pushed into a bounded
InputEvent queue. Excess input is dropped oldest-first.
- Host → fixed-step tick batchEach frame can run up to
max_catch_up_ticks simulation ticks depending on accumulated real time.
- Sim tick stages
sim.tick(&mut world) injects TickInfo and TimingInfo, then runs Input, PreUpdate, Update, PostUpdate, and PreExtract.
- Extraction
PreExtract produces RenderSnapshot, UiSnapshot, and audio command output for downstream threads.
- Background IO boundaryFile reads, cooks, and decompression enter World only at tick start through
tick_io_boundary().
- Structural mutationSpawn, despawn, and archetype changes happen through
EcsCommandBuffer at stage boundaries only.
Sim thread is the only writer to World during a tick. Render, UI, and audio consume snapshots on their own threads. GPU handles never cross into sim.
Run Modes
run_headless(tick_count)
Skips the window and GPU entirely. Useful for tests, CI, and replay validation. Build with --no-default-features to drop winit and wgpu.
run_headless_timed(duration)
Same headless path, but driven by a duration rather than a fixed tick count.
run()
Normal windowed app mode with winit, wgpu surface, and the full input pipeline.
run_editor()
Runs sim on a background thread. Each tick does offscreen render, blocking readback, and pushes frames through ViewportPixelBridge to the Slint UI thread.