SeedEngine · v0.1.0 · MPL-2.0
A game engine built for one game.

SeedEngine is a custom Rust game engine developed alongside a solo indie game project. Every system ships because the game needs it, not because the spec says so.

Overview
What SeedEngine is, what it can do, and who it is for.
Language
Rust stable
GPU API
Vulkan 1.3
License
MPL-2.0
Since
Feb 2026
What it is

SeedEngine is a custom Cargo workspace game engine written in Rust. It is purpose-built for a single game project and developed as both projects evolve together — the engine only grows in directions the game actually exercises.

The workspace is structured as a set of focused crates with strictly enforced dependency direction. The renderer targets Vulkan 1.3 via ash. Shaders are written in HLSL and compiled to SPIR-V with dxc. The render pipeline uses MBOIT transparency, AgX tonemapping, and an NPR shading model aimed at Japanese animation aesthetics.

Core systems
ECS
Archetype / SoA layout with rayon parallel scheduling. Fixed-step accumulation driven by EngineClock.
Rendering
Vulkan 1.3 backend. Render passes declared with explicit resource usage; barriers inserted automatically.
Physics
Jolt Physics 5.5.0 via a C++ FFI bridge. Sphere, box, and capsule shapes. Dynamic, kinematic, and static bodies.
Audio
cpal backend. Adaptive music layers, stingers, spatial SFX, per-bus 10-band parametric EQ.
Navigation
NavMesh tiles, pulse spatial queries, SLAM occupancy maps, raycasting against geometry and entities.
AI
MAH / HTN / BT / HFSM decision layers. FlowField crowd navigation. Shared perception layer across agents.
Virtual File System
.rsgd game archives, .rssd save files with diff-chain compression. ChaCha20-Poly1305 encryption.
Scripting
Rhai runtime with engine bindings. Lua has been fully removed and will not return.
Who it is for

SeedEngine is open source under MPL-2.0, but it is not a general-purpose engine. There is no editor, no asset pipeline GUI, and no plugin system. If you are looking for a complete engine, Bevy or Godot will serve you better.

What it offers is a clear, well-documented Rust codebase that demonstrates how to wire together Vulkan, ECS, physics, audio, and AI into a working game loop — with every architectural decision made under real production pressure.

Create your first project
Add a new crate to the workspace and run a minimal engine loop with a camera, ECS world, and Vulkan renderer.
SeedEngine targets Windows with an RTX-class GPU (Vulkan 1.3 required). Vulkan drivers, the Vulkan SDK, and dxc must be installed. Rust stable toolchain is sufficient.
1
Clone the repository
The workspace root is SeedEngine/. All engine crates live under crates/.
git clone https://github.com/kuriminanya/SeedEngine.git cd SeedEngine
2
Add a new crate to the workspace
Your game lives as a crate inside the same Cargo workspace. Create it alongside the engine crates.
cargo new --bin crates/hello_engine
Then register it in the root Cargo.toml:
# Cargo.toml (workspace root) [workspace] members = [ "crates/clock", "crates/ecs", # ... other crates ... "crates/hello_engine", # <-- add this ]
3
Declare dependencies
In crates/hello_engine/Cargo.toml, depend on whichever engine crates you need:
[package] name = "hello_engine" version = "0.1.0" edition = "2021" [dependencies] anyhow = "1" log = "0.4" env_logger = "0.11" glam = "0.29" clock = { path = "../clock" } ecs = { path = "../ecs" } event = { path = "../event" } graphics = { path = "../graphics" } logic = { path = "../logic" } scene = { path = "../scene" }
4
Write the game loop
SeedEngine uses a two-thread architecture: winit stays on the main thread (OS requirement) and sends events over a channel; the game thread owns the ECS world and drives the renderer.
// The core pattern — see the full hello_engine example for details. let (renderer_tx, renderer_rx) = std::sync::mpsc::sync_channel::<Renderer>(1); let (event_tx, event_rx) = std::sync::mpsc::sync_channel::<SeedEvent>(256); let (ctrl_tx, ctrl_rx) = std::sync::mpsc::sync_channel::<ControlMsg>(8); // game thread: wait for renderer, then run the loop std::thread::spawn(move || { let mut renderer = renderer_rx.recv().unwrap(); let mut clock = EngineClock::new(DEFAULT_FIXED_STEP); let mut world = setup_world(); loop { for event in event_rx.try_iter() { /* handle input / resize / quit */ } clock.tick(); for _ in 0..clock.fixed_tick_count() { world.run(&ctx); } let scene = extract_render_scene(&world); submit_frame(&mut renderer, &scene); } }); // main thread: run winit event loop graphics::frontend::winit::init::run(renderer_tx, event_tx, ctrl_rx, window_mode, "hello_engine")?;
5
Register a camera and spawn entities
Insert a SceneManager resource, spawn a camera entity, and set it as active. The renderer reads this every frame to compute the view-projection matrix.
world.insert_resource(SceneManager::new(16.0 / 9.0)); let cam = world.entity() .with(Camera::cinematic_default()) .with(WorldTransform::new( glam::Vec3::new(0.0, 1.0, 5.0), glam::Quat::IDENTITY, glam::Vec3::ONE, )) .spawn(); world.get_resource_mut::<SceneManager>() .unwrap() .set_active_camera(cam);
6
Build and run
A debug build is fastest for iteration. Set RUST_LOG=info to see the ECS heartbeat log.
set RUST_LOG=info cargo run -p hello_engine
A black window is the expected output — no meshes are loaded in the minimal example. Add RenderObjects to the RenderScene to see geometry.
Full example

The complete hello_engine example (including input mapping, audio init, aspect-ratio sync, and all render passes) is available in the repository at examples/hello_engine/src/main.rs. It mirrors the two-thread architecture of the main game project without any game-specific content.

About SeedEngine
Built by 賽菲莉西亞之花
Version
0.1.0
Language
Rust stable
License
MPL-2.0
Last updated

What is this

SeedEngine is a custom Rust game engine developed by a solo indie studio. It exists for one reason: to ship a game. Every system in the engine is there because the game needs it.

The engine is open source under MPL-2.0. You are free to use, fork, and learn from the code.

Aesthetic direction

The rendering pipeline is built around Japanese semi-realistic animation aesthetics — atmospheric lighting and painterly skies paired with mechanical precision and restrained color grading. NPR shading goals: SDF face shadow, Kajiya-Kay hair, back-face outline, AgX tonemapping, MBOIT transparency.

GitHub
Clock
Delta time, fixed tick accumulator, render alpha · crates/clock
API overview
EngineClockstruct Main engine clock — call tick() once per frame.

Tracks scaled delta time, fixed-step accumulation, render alpha, and both game/real time totals. Create once; call tick() at the top of each frame loop.

use clock::EngineClock;

let mut clock = EngineClock::new(1.0 / 60.0);

// in game loop:
clock.tick();
let dt = clock.delta(); // scaled delta (0 when paused)
let alpha = clock.alpha(); // render interpolation [0, 1]
EngineClock::newfn Create a clock with a fixed step in seconds.

Pass the desired fixed-simulation step, e.g. 1.0 / 60.0 for 60 Hz physics.

pub fn new(fixed_step: f64) -> Self
EngineClock::tickfn Advance time state — call once per frame.

Reads the wall clock, computes raw delta (clamped to 250 ms), and updates all derived values.

pub fn tick(&mut self)
EngineClock::deltafn Scaled delta time for this frame.

Returns zero when paused. Multiply movement and physics by this value.

pub fn delta(&self) -> f32
EngineClock::fixed_tick_countfn How many fixed ticks to run this frame.

Run your physics/game-logic loop exactly this many times per frame.

pub fn fixed_tick_count(&self) -> u32
EngineClock::alphafn Render interpolation factor.

Value in [0.0, 1.0] — use to interpolate between the previous and current physics state for smooth rendering.

pub fn alpha(&self) -> f32
EngineClock::set_time_scalefn Set the time-scale multiplier.

Values above 1.0 speed up game time; below 1.0 slow it down. Clears the paused state.

pub fn set_time_scale(&mut self, scale: f32)
EngineClock::pause / resumefn Pause and resume game time.

pause() freezes delta() and game_time() while real_time() keeps ticking. resume() restores the previous time-scale.

pub fn pause(&mut self) pub fn resume(&mut self)
Entity Component System
Archetype / SoA layout, rayon scheduling · crates/ecs
API overview
Worldstruct Central ECS container.

Owns all entities, components, resources, and the scheduler. One World per application.

use ecs::World;

let mut world = World::new();
let e = world.entity().with(Position { x: 0.0, y: 0.0 }).spawn();
world.run(&ctx);
World::entityfn Begin building a new entity.

Returns an EntityBuilder. Chain .with::<T>(component) calls, then call .spawn().

pub fn entity(&mut self) -> EntityBuilder<'_>
World::spawnfn Low-level entity creation (raw bytes).

Prefer entity() for ergonomic usage. This is the underlying allocation path.

pub fn spawn(&mut self, components: Vec<(ComponentInfo, Box<[u8]>)>) -> Entity
World::despawnfn Remove an entity and all its components.

Returns true if the entity existed. Stale handles to the entity become invalid.

pub fn despawn(&mut self, entity: Entity) -> bool
World::queryfn Iterate entities with a given component.

Read-only. Yields (Entity, &T) pairs across all matching archetypes.

pub fn query<T: Component>(&self) -> impl Iterator<Item = (Entity, &T)>
for (entity, pos) in world.query::<Position>() {
println!("{:?} at {:?}", entity, pos);
}
World::query2fn Iterate entities with two components simultaneously.

Both components must be present. Returns (Entity, &A, &B).

pub fn query2<A: Component, B: Component>(&self) -> impl Iterator<Item = (Entity, &A, &B)>
World::insert_resource / get_resourcefn Store and retrieve global resources.

Resources are singleton values (not tied to entities). Useful for engine-wide state like SceneManager or AudioEngine.

pub fn insert_resource<T: Resource>(&mut self, res: T)
pub fn get_resource<T: Resource>(&self) -> Option<&T>
World::runfn Execute all systems for one frame.

Runs every registered stage in order. Systems within a stage run in parallel if they have no component conflicts.

pub fn run(&mut self, ctx: &ScheduleContext)
Entitystruct A lightweight entity identifier.

Stores an index and a generation counter to detect stale handles. Copy-friendly.

// derives: Clone, Copy, PartialEq, Eq, Hash, Debug
Componenttrait Marker trait for all component types.

Implement this (or #[derive(Component)]) for any 'static + Send + Sync struct.

pub trait Component: 'static + Send + Sync {}
Systemtrait Defines a schedulable system.

access() declares read/write sets for conflict detection. run() executes each frame.

pub trait System { fn access(&self) -> SystemAccess; fn run(&mut self, world: &mut WorldAccess<'_>, ctx: &ScheduleContext); }
Scene
Camera, world transform, origin shifting · crates/scene
API overview
Camerastruct Cinematic camera with optical parameters.

Manages lens, sensor format, aperture, exposure, and depth-of-field. Can be stored as an ECS component. Produces a CameraOutput snapshot each frame for the renderer.

use scene::{Camera, SensorFormat};

let cam = Camera::new(SensorFormat::FullFrame, 35.0);
// or use cinematic defaults:
let cam = Camera::cinematic_default();
Camera::view_projectionfn Combined view-projection matrix.

Convenience wrapper that multiplies the view and projection matrices. Pass the result directly to your shader UBO.

pub fn view_projection(&self, transform: &WorldTransform, aspect_ratio: f32) -> Mat4
Camera::outputfn Package camera data for the renderer.

Produces a CameraOutput snapshot (matrices + world position) to be written into a RenderScene each frame.

pub fn output(&self, transform: &WorldTransform, aspect_ratio: f32) -> CameraOutput
WorldTransformstruct TRS transform for a single object in world space.

Stores translation (Vec3), rotation (Quat), and scale (Vec3). Can be used as an ECS component. Provides helpers to derive forward/right/up axes and model matrices.

use scene::WorldTransform;

let t = WorldTransform::from_translation(Vec3::new(0.0, 1.0, 0.0));
let model = t.to_mat4();
FloatingOriginstruct Large-world floating-point precision correction.

Automatically recenters the render origin when the player drifts beyond a threshold distance, preventing f32 precision loss at large coordinates.

use scene::FloatingOrigin;

let mut origin = FloatingOrigin::new(1000.0);
if origin.needs_rebase(player_world_pos) {
let offset = origin.rebase(player_world_pos);
// shift all objects by `offset`
}
SceneManagerstruct Active camera & viewport tracker (ECS Resource).

Insert as a resource into the ECS World. Set the active camera entity each frame so the render pass knows which camera to use.

pub fn new(aspect_ratio: f32) -> Self | pub fn set_active_camera(&mut self, entity: Entity)
SensorFormatenum Physical sensor size for optical calculations.

Variants: FullFrame, ApsC, MicroFourThirds, Custom(f32, f32). Affects FOV derived from focal length.

Assets & Catalog
Async loaders, asset threads, item definitions · crates/assets
API overview
AssetWorkersstruct Async asset loading queue.

Wraps a rayon thread pool. Submit load jobs non-blocking; poll the returned JobHandle<T> for results.

use assets::{AssetWorkers, ImageData};

let mut workers = AssetWorkers::new();
let handle = workers.submit_image(&path, &vfs)?;

// later, in game loop:
if let Ok(img) = handle.try_recv() { /* upload to GPU */ }
JobHandle<T>struct Handle to a running async load job.

Call try_recv() for a non-blocking poll or recv() to block until the job completes.

pub fn try_recv(&self) -> Option<JobResult<T>>
pub fn recv(self) -> JobResult<T>
LocaleManagerstruct Runtime locale / i18n manager.

Auto-detects the system language on creation. Use get() to look up translated strings by key.

use assets::LocaleManager;

let locale = LocaleManager::new(&vfs)?;
let label = locale.get("ui.start_game");
locale.switch("ja", &vfs)?;
ImageDatastruct Decoded image pixels (CPU side).

Contains width, height, format, and raw f32/u8 pixels. Upload to the GPU via your graphics backend after loading.

// fields: width: u32, height: u32, format: ImageFormat, pixels: Vec<u8>
ModelDatastruct Parsed 3D model.

Contains meshes, materials, and scene nodes deserialized from .glb. Does not touch the GPU — upload mesh/texture data yourself.

// fields: meshes: Vec<MeshData>, materials: Vec<MaterialData>, nodes: Vec<NodeData>
ShaderModulestruct Loaded shader bytecode.

Wraps SPIR-V, DXIL, DXBC, or MSL source. Pass to your graphics backend to create a pipeline stage.

// fields: stage: ShaderStage, source: ShaderSource
SubmitErrorenum Reason a submit call failed.

Variants: QueueFull — worker queue is at capacity; Shutdown — workers are stopping.

Virtual File System
.rsgd archives, .rssd save files, ChaCha20-Poly1305 encryption · crates/vfs
API overview
Vfsstruct VFS entry point — use as an ECS Resource.

Abstracts over Dev mode (raw filesystem) and Release mode (.rsgd archives). Create once and inject wherever files need to be read.

use vfs::Vfs;

// development:
let vfs = Vfs::new_dev(data_root);

// release (encrypted archives):
let vfs = Vfs::new_release(archive_paths, encryption_key);

let bytes = vfs.read(&RsfsPath::new("tex/sky.dds"))?;
Vfs::readfn Read a file from the virtual filesystem.

Returns raw bytes. Works identically in Dev and Release mode.

pub fn read(&self, path: &RsfsPath) -> Result<Vec<u8>>
Vfs::existsfn Check whether a virtual path exists.

Returns true if the path resolves in the current VFS mode.

pub fn exists(&self, path: &RsfsPath) -> bool
Vfs::list_dirfn Enumerate all paths under a virtual directory.

Lists all entries in the given virtual directory path.

pub fn list_dir(&self, dir: &RsfsPath) -> Result<Vec<RsfsPath>>
RsfsPathstruct Typed virtual path.

Internally a normalized string. Provides helpers like parent(), join(), extension(), and a stable path_hash() for fast lookups.

pub fn new(path: &str) -> Self
SaveDocumentstruct A save-game document.

A HashMap<String, FieldValue> serialized to .rssd with optional diff-chain compression and ChaCha20-Poly1305 encryption.

// derives: Debug, Clone, PartialEq, Default
SlotManagerstruct Multi-slot save manager.

Manages multiple save-file slots with diff compression chains to minimize on-disk size.

diff_documents / apply_difffn Compute and apply save-file diffs.

Use diff_documents to produce a SaveDiff between two documents, and apply_diff to reconstruct a document from a base and its diff.

pub fn diff_documents(base: &SaveDocument, new: &SaveDocument) -> SaveDiff
pub fn apply_diff(base: &SaveDocument, diff: &SaveDiff) -> SaveDocument
VfsDrivertrait Backend driver interface.

Implement this to add custom backends (e.g. network streaming). DevDriver and ReleaseDriver are the built-in implementations.

pub trait VfsDriver { fn read(&self, path: &RsfsPath) -> Result<Vec<u8>>; fn exists(&self, path: &RsfsPath) -> bool; fn list_dir(&self, dir: &RsfsPath) -> Result<Vec<RsfsPath>>; }
Graphics
Vulkan 1.3 backend, render pipeline · crates/graphics
API overview
Rendererstruct Vulkan 1.3 renderer — passive, caller-driven.

Holds the Vulkan context, swapchain, and resource pools. The caller owns the render loop: call begin_render, add passes via FrameBuilder, then call end_render.

use graphics::{Renderer, RenderScene};

let mut renderer = Renderer::new(&window)?;

// each frame:
let mut fb = renderer.begin_render(&scene)?;
fb.add_pass(my_pass)
.add_pass(post_process);
fb.end_render()?;
Renderer::begin_renderfn Start a frame — returns a FrameBuilder.

Returns Err(RendererError::NeedRetry) on transient swapchain issues (skip the frame and retry next tick).

pub fn begin_render<'r>(&'r mut self, scene: &'r RenderScene) -> Result<FrameBuilder<'r>, RendererError>
FrameBuilder::add_passfn Register a render pass for this frame.

Passes run in registration order. Barriers between passes are inserted automatically based on each pass's resources() declaration.

pub fn add_pass(&mut self, pass: impl RenderPass + 'static) -> &mut Self
FrameBuilder::end_renderfn Submit command buffer and present.

See signature for details.

pub fn end_render(mut self) -> Result<(), RendererError>
Renderer::resizefn Notify the swapchain of a window resize.

Call this from your WindowEvent::Resized handler. Propagates to all registered render passes via on_resize.

pub fn resize(&mut self, width: u32, height: u32)
RenderPasstrait Interface for a single render pass.

resources() declares image usage for automatic barrier insertion. record() writes Vulkan commands. on_resize() recreates size-dependent resources.

pub trait RenderPass { fn resources(&self) -> &[ResourceUsage]; fn record(&mut self, ctx: &PassContext<'_>); fn on_resize(&mut self, ctx: &ResizeContext); }
RenderScenestruct Per-frame renderer input snapshot.

Extracted from the ECS each frame. Contains all visible RenderObjects and the active CameraData.

// fields: objects: Vec<RenderObject>, camera: CameraData
RendererErrorenum Renderer error variants.

NeedRetry — transient (skip frame). Fatal(String) — unrecoverable, shut down.

UI System
Taffy Flexbox layout, MSDF fonts, DrawCommand IR · crates/uisystem
API overview
UiEnginestruct Main UI engine — layout and draw-command generation.

Maintains a tree of Nodes. Call update(viewport) each frame to recompute layout, then build_draw_commands() to get an IR list for your rendering backend.

use uisystem::{UiEngine, Style, NodeContent};

let mut ui = UiEngine::new();
let btn = ui.create_node(style, NodeContent::Text("Start".into()));
ui.append_child(ui.root(), btn);

ui.update([1920.0, 1080.0]);
let cmds = ui.build_draw_commands();
UiEngine::create_nodefn Add a new node to the UI tree.

See signature for details.

pub fn create_node(&mut self, style: Style, content: NodeContent) -> NodeId
UiEngine::append_childfn Attach a node as a child of another.

See signature for details.

pub fn append_child(&mut self, parent: NodeId, child: NodeId)
UiEngine::set_stylefn Update a node's style (marks layout dirty).

See signature for details.

pub fn set_style(&mut self, id: NodeId, style: Style)
UiEngine::updatefn Recompute Flexbox layout for the whole tree.

Pass the current viewport size in pixels. Call once per frame before build_draw_commands().

pub fn update(&mut self, viewport: [f32; 2])
UiEngine::build_draw_commandsfn Produce a DrawCommand list from layout results.

DFS traversal of the node tree. Feed the result to your UiBackend implementation.

pub fn build_draw_commands(&self) -> Vec<DrawCommand>
UiControllerstruct Focus, hover, and hit-testing for UI interaction.

Feed UiEvents from your input layer. Tracks which node is focused (keyboard/gamepad) and hovered (mouse).

pub fn process_event(&mut self, event: UiEvent, engine: &mut UiEngine)
DrawCommandenum Renderer-agnostic draw IR.

Variants: Rect{..}, Text{..}, Glyph{..}, Image{..}, Clip{..}, PopClip. Implement UiBackend to consume these.

UiBackendtrait Custom rendering backend for the UI.

Implement render(&mut self, commands: &[DrawCommand]) to hook into your Vulkan or headless renderer.

pub trait UiBackend { fn begin_frame(&mut self); fn render(&mut self, commands: &[DrawCommand]); fn end_frame(&mut self); }
FontAtlasstruct MSDF font atlas.

Load from a .bin file. Query individual glyph UV rects and metrics via glyph(codepoint).

pub fn from_bytes(data: &[u8]) -> anyhow::Result<Self>
pub fn glyph(&self, codepoint: char) -> Option<&GlyphMetrics>
pub fn measure_text(&self, text: &str, font_size: f32) -> f32
Colorstruct RGBA color (0.0 – 1.0 per channel).

Helpers: from_rgba(r, g, b, a), from_hex("#RRGGBB").

pub fn from_rgba(r: f32, g: f32, b: f32, a: f32) -> Self
pub fn from_hex(s: &str) -> Self
AI
MAH / HTN / BT / HFSM decision layers, FlowField town flow · crates/aisystem
API overview
spawn_agent / despawn_agentfn Register and remove AI agents.

Create an AgentProfile to configure the decision-layer stack, then call spawn_agent to activate it.

pub fn spawn_agent(entity: EntityId, profile: AgentProfile) -> AgentHandle
pub fn despawn_agent(handle: AgentHandle)
use aisystem::{spawn_agent, AgentProfile, EntityId};

let handle = spawn_agent(EntityId(entity_u64), profile);
// later:
aisystem::despawn_agent(handle);
updatefn Run one frame of AI for all agents.

Pass the current PerceptionFrame (visible entities + events). Returns a Vec<AgentCommand> to be consumed by game logic.

pub fn update(perception: &PerceptionFrame) -> Vec<AgentCommand>
inject_eventfn Push an external event to a specific agent.

Use for stimulus like AiEvent::TakeDamage or AiEvent::Alerted that originate outside the perception system.

pub fn inject_event(handle: AgentHandle, event: AiEvent)
MasterAgentstruct MAH — Multi-Agent Hierarchy coordinator.

Runs an HTN planner to select a sub-agent (BT, HFSM, or Follow controller), then ticks that sub-agent each frame.

pub fn tick(&mut self) -> Option<Command>
BehaviorTreestruct Behavior Tree executor.

Composed of BtNode variants: Leaf, Sequence, Selector, Parallel. Reads and writes state via a shared Blackboard.

pub fn tick(&mut self, blackboard: &mut Blackboard) -> (BtStatus, Option<Command>)
Hfsmstruct Hierarchical Finite State Machine executor.

States implement the HfsmState trait. Transitions are evaluated each tick based on blackboard conditions.

pub fn tick(&mut self, blackboard: &mut Blackboard) -> Option<Command>
HtnPlannerstruct HTN planner — decomposes compound tasks into primitives.

Given a root HtnTask, produces a flat sequence of primitive actions to pass to a sub-agent.

pub fn plan(&self, task: &HtnTask, blackboard: &Blackboard) -> Vec<HtnTask>
Blackboardstruct Shared agent memory (KV store).

Supports shared (zone-wide) and private (per-agent) layers. Values are typed via BlackboardValue.

bake_flow_fieldfn Precompute a FlowField for crowd navigation.

Runs BFS/Dijkstra from goal over a NavGrid and writes direction vectors into the FlowFieldNetwork. Call offline or when the goal changes.

pub fn bake_flow_field(nav_grid: &NavGrid, goal: WorldPosition, network: &mut FlowFieldNetwork)
Commandenum AI agent output instruction.

Variants: Move{destination}, Attack{target}, Idle, Interact{kind}, UseSkill{id}, PlayAnimation{clip}, Flee{from}.

Navigation
NavMesh, spatial queries, raycasting, SLAM positioning · crates/navigation
API overview
NavAgentstruct Per-entity navigation agent.

Maintains a waypoint queue and a stuck timer. Poll poll(current_pos) each frame to advance along the path.

use navigation::NavAgent;

let mut agent = NavAgent::new();
agent.push_waypoint(target);

// in update:
let status = agent.poll(entity_pos);
NavAgent::push_waypointfn Add a waypoint to the navigation queue.

See signature for details.

pub fn push_waypoint(&mut self, wp: WorldPosition)
NavAgent::pollfn Advance navigation state — call once per frame.

Returns NavStatus::Moving, Arrived, Stuck, or NoPath.

pub fn poll(&mut self, current_pos: WorldPosition) -> NavStatus
pulse_queryfn Sphere-cast spatial awareness query.

Returns all PulseHits within the query radius that pass the attached filters (tag, distance, stealth, faction).

pub fn pulse_query(query: &PulseQuery, spatial_map: &SpatialMap) -> Vec<PulseHit>
use navigation::{pulse_query, PulseQuery, SpatialMap};

let hits = pulse_query(&PulseQuery {
origin: pos, radius: 20.0,
filters: vec![Box::new(tag_filter)],
}, &spatial_map);
raycast_navmesh / raycast_entitiesfn Line-of-sight raycasts.

raycast_navmesh hits NavMesh geometry; raycast_entities hits the spatial map. Both return an optional RayHit.

pub fn raycast_navmesh(query: &RayQuery, navmesh: &NavMesh) -> Option<RayHit>
pub fn raycast_entities(query: &RayQuery, spatial_map: &SpatialMap) -> Option<RayHit>
SpatialMapstruct Flat per-frame spatial index for entity lookup.

Rebuilt each frame from current entity positions. Clear with clear(), then insert() all entities, then run queries.

pub fn clear(&mut self) | pub fn insert(&mut self, entry: SpatialEntry)
NavMeshstruct Tile-based navigation mesh.

Stores walkable polygon tiles. Used as input to raycast_navmesh and NavAgent pathfinding.

pub fn new(tile_size: f32) -> Self
SlamMapstruct Occupancy grid updated from pulse sensor hits.

Stores per-cell occupancy (occupied/free/unknown). Merge maps from multiple agents with slam_merge.

pub fn new(cell_size: f32) -> Self
PulseFiltertrait Custom filter for pulse_query results.

Implement fn accept(&self, hit: &PulseHit) -> bool. Built-in filters: TagIncludeFilter, TagExcludeFilter, DistanceFilter, StealthFilter, FactionFilter.

Physics
Jolt Physics 5.5.0 via C++ FFI (cxx bridge) · crates/physics
API overview
PhysicsWorldstruct Safe Rust wrapper around the full Jolt physics subsystem.

Create with a PhysicsWorldConfig. Implements Send (but not Sync). Owns the Jolt foundation, broad-phase, narrow-phase, and body interface.

use physics::{PhysicsWorld, PhysicsWorldConfig, BodyDesc, MotionType, ShapeDesc};

let mut pw = PhysicsWorld::new(PhysicsWorldConfig::default());
let handle = pw.add_body(&BodyDesc {
shape: ShapeDesc::Sphere { radius: 0.5 },
motion: MotionType::Dynamic,
..Default::default()
});
PhysicsWorld::newfn Initialize the full Jolt subsystem.

Runs the complete Jolt initialization sequence. Body/pair/constraint limits and thread count are set via PhysicsWorldConfig.

pub fn new(config: PhysicsWorldConfig) -> Self
PhysicsWorld::add_bodyfn Create and register a physics body.

Returns BodyHandle::INVALID if the body pool is exhausted.

pub fn add_body(&mut self, desc: &BodyDesc) -> BodyHandle
BodyDescstruct Full description for creating a physics body.

Specify shape (ShapeDesc), motion (MotionType), initial position/rotation, friction, restitution, and layer.

BodyHandlestruct Opaque handle to a Jolt body.

A u32 wrapper. u32::MAX represents an invalid handle. Check with is_invalid().

pub fn is_invalid(self) -> bool
ShapeDescenum Collision shape descriptor.

Variants: Sphere { radius }, Box { half_extents }, Capsule { radius, half_height }.

MotionTypeenum How the body participates in simulation.

Variants: Static (immovable), Kinematic (moved by code), Dynamic (fully simulated).

UpdateResultstruct Error flags from a simulation step.

Check has_errors() after stepping. Individual flag methods: manifold_cache_full(), body_pair_cache_full(), contact_constraints_full().

pub fn has_errors(self) -> bool
Audio
Spatial SFX, adaptive music, stingers, 10-band EQ · crates/logic
API overview
AudioEnginestruct Production audio engine.

Manages SFX voices, adaptive music layers, stingers, and a bus graph with per-bus EQ. Backed by cpal.

use logic::audio::AudioEngine;

let mut audio = AudioEngine::new()?;
audio.play_sfx("sfx/footstep.ogg", 1.0)?;

// in game loop:
audio.update();
AudioEngine::play_sfxfn Play a one-shot sound effect.

Returns a VoiceHandle for optional stop/volume control.

pub fn play_sfx(&mut self, path: &str, volume: f32) -> Result<VoiceHandle>
AudioEngine::play_musicfn Start or transition adaptive music.

Pass a MusicTrack and a MusicTransition strategy (Immediate, FadeOutIn, SyncedCut).

pub fn play_music(&mut self, track: MusicTrack, transition: MusicTransition)
AudioEngine::set_layer_activefn Toggle an adaptive music layer.

Optionally fade in/out over fade_sec seconds.

pub fn set_layer_active(&mut self, name: &str, active: bool, fade_sec: Option<f32>)
AudioEngine::play_stingerfn Play a one-shot musical insert.

Stingers overlay the current music track without interrupting it.

pub fn play_stinger(&mut self, path: &str, volume: f32) -> Result<VoiceHandle>
AudioEngine::set_bus_volumefn Set the volume of an audio bus.

See signature for details.

pub fn set_bus_volume(&mut self, bus: BusKind, volume: f32)
AudioEngine::set_bus_eq_bandfn Adjust a single EQ band on a bus.

Each bus has a 10-band stereo parametric EQ. band_idx is 0–9.

pub fn set_bus_eq_band(&mut self, bus: BusKind, band_idx: usize, band: EqBand)
AudioEngine::updatefn Advance audio state — call once per frame.

Handles beat switching, voice garbage collection, and graph-snapshot sync for the cpal callback thread.

pub fn update(&mut self)
MusicTransitionenum Strategy for transitioning between music tracks.

Variants: Immediate, FadeOutIn { fade_out_ms, fade_in_ms }, SyncedCut { boundary: BeatBoundary }.

BusKindenum Audio routing bus.

Variants: Master, Music, Sfx, Voice, Ambient.

Input
Keyboard action mapping, gamepad with deadzone & hysteresis · crates/logic
API overview
InputMapperstruct KeyCode → InputAction mapping table.

Register default bindings at startup, then allow players to rebind. Resolve raw key events to semantic actions at runtime.

use logic::input::{InputMapper, InputAction};
use event::KeyCode;

let mut mapper = InputMapper::new();
mapper.register(KeyCode::W, InputAction::MoveForward);

if let Some(action) = mapper.resolve(key_event.code) {
// handle action
}
InputMapper::registerfn Bind a key to an action.

See signature for details.

pub fn register(&mut self, key: KeyCode, action: InputAction)
InputMapper::rebindfn Rebind an existing key mapping.

See signature for details.

pub fn rebind(&mut self, key: KeyCode, action: InputAction)
InputMapper::resolvefn Look up the action for a key.

See signature for details.

pub fn resolve(&self, key: KeyCode) -> Option<&InputAction>
GamepadManagerstruct Gamepad device manager (gilrs wrapper).

Handles hotplug, deadzone filtering, and axis hysteresis. Call pump() each frame to drain events.

pub fn new() -> anyhow::Result<Self>
pub fn pump(&mut self) -> Vec<GamepadInputEvent>
GamepadManager::active_gamepadfn Index of the currently active gamepad.

Returns None if no gamepad is connected.

pub fn active_gamepad(&self) -> Option<usize>
InputActionenum Semantic input action.

Variants include: MoveForward, MoveBack, MoveLeft, MoveRight, Jump, Attack, Interact, and more.

GamepadInputEventenum Raw gamepad event.

Variants: ButtonPressed{..}, ButtonReleased{..}, AxisChanged{..}, Connected(usize), Disconnected(usize).

Scripting System
Rhai runtime with engine bindings · crates/logic
API overview
RhaiEnginestruct Rhai scripting engine wrapper.

Exposes a sandboxed Rhai interpreter with pre-registered SeedEngine bindings. Lua has been fully removed — Rhai is the sole scripting language.

use logic::rhai::RhaiEngine;

let mut engine = RhaiEngine::new();
// eval a script string:
engine.inner.eval::<i64>("1 + 1")?;
RhaiEngine::newfn Create a Rhai engine with default bindings.

Registers SeedEngine types and helper functions into the Rhai scope.

pub fn new() -> Self
Network
QUIC, TLS, tokio runtime · crates/network
Coming soon

Reserved for future networking support (QUIC via quinn, TLS via rustls). No public API yet.