Skip to content

Engine Roadmap

What DigitalHeaven.Engine is building toward, in order. Scope here is deliberate — each milestone is the smallest thing that proves the layer beneath it.

A single map that is fun to move around in, exercising every architectural pillar at least once:

FeatureScope
Default mapGray grid with orange accents — the engine’s dev-texture look
MovementWalk, sprint, crouch, air-strafe
Consolekill, teleport, noclip
NetworkingEmbedded server over loopback — singleplayer is a client connected to a local server
AvatarsglTF avatar import for the player model

The embedded server is in Milestone 1 on purpose: if singleplayer does not go through the client-server path from the start, multiplayer-first is a slogan rather than an architecture.

One dedicated server and two independently launched clients can join the same authoritative world, see one another move as upright box avatars, run caller-scoped commands correctly, and disconnect or reconnect without disturbing the remaining player.

FeatureScope
World structureStable world identity and explicit Universe ownership, removal, and disposal; one playable world, with transfers deferred
Player structureConnection-scoped player sessions point to world membership and world-resident pawn entities instead of retaining raw ECS entities as identity
World rulesSimulation values are per-world world.* preferences, including world.cheats; only the authoritative server console may change cheat policy
NetworkingDedicated LiteNetLib server plus independent remote clients, while embedded loopback remains the singleplayer path
ReplicationMultiple pawns, caller ownership, smooth snapshot interpolation, and clean spawn/disconnect/reconnect lifecycle
AvatarsEach remote pawn is an upright box stretched to the current character dimensions; body yaw follows camera yaw while pitch and roll do not tilt the body
Hot reloadWorlds, pawns, and world rule values survive gameplay assembly reload; connected clients receive refreshed command metadata

M2 remains a Compact float world. M2 establishes the first cheap compatibility seams described in Large-World Coordinates: an immutable profile selected when each World is created and used to select profile-matched physics once. Reporting the profile during connection setup and selecting specialized gameplay systems, snapshot codecs, and renderers remain deferred with LargePosition, the second Box3D ABI, the Large snapshot codec, and the camera-relative renderer until those paths are implemented.

M2 deliberately excludes weapons, skeletal animation, player transfer between worlds, account persistence and permissions, chunk streaming, map switching, and the lighting stages below. Client-side prediction and reconciliation were added as an M2 follow-up.

The player can configure the game through a real preference system and a proper options screen, reads legible colored logs both in the terminal and the in-game console, launches into a chosen display mode without the window rudely stealing focus, and sees a crosshair. Under the hood, the Source-style convar system is replaced by a scoped, hierarchical preference system, and an admin concept lets a trusted client run world-authoritative commands with contextual targeting.

FeatureScope
PreferencesTyped preference handles replace convars; hierarchical dotted scopes client / universe / worlds.<slug> / server, with world.* as a contextual alias for the caller’s current world. Persisted to a structured TOML profile — arrays and nested objects are stored natively, never stringified
Console accessRaw-dog reads and writes (world.cheats, world.cheats 1) as the primary path; set / get / reset as optional aliases. Ranked autocomplete offers the branch node first (world.) then its children
Admin & identityA session IsAdmin flag generalizes host authority so a trusted remote client may run world-authoritative commands; targeting resolves from the caller’s world context, with explicit worlds.<slug>.* for overrides and the context-less server console. A persistent server.operators list keyed by an opaque identity behind an IIdentityProvider seam — a local dev provider ships now, networked identity plugs in later
LoggingOne unified sink pipeline: per-category hashed color (FNV-1a → DigitalHeaven.Core.Color), 24-bit ANSI truecolor in the terminal, and per-line coloring in the in-game console. The separate uncategorized client/Vulkan log path is folded into channels so every line has a category, and log lines feed the in-game console ring with category filtering
Display & windowA display-mode setting (windowed / borderless / exclusive fullscreen) with resolution, refresh-rate, and VSync options. The window only auto-focuses and captures the cursor in exclusive fullscreen; windowed and borderless launch unfocused
CrosshairAn in-game center crosshair, on by default, selectable from a dropdown of built-in presets
Settings screenA data-driven settings screen bound to the preference system, rendered by Halcyon with a category rail and a substring search: sections (Controls, Display, Graphics, Camera, Audio, Crosshair, Player, Storage) of typed controls — sliders, enum and value dropdowns, toggles, an RGBA color editor and a text field — each reading and writing its preference live (a console client.fov 90 reflects while open). Resolution and refresh dropdowns derive from the monitor’s reported video modes; every display change — mode, resolution, refresh, VSync, fps_max — applies live, never on restart

M3 keeps the existing per-world value scoping and caller-scoped authority from M2 — the preference migration re-faces that store with a hierarchical namespace and typed handles rather than rebuilding it. The DigitalHeaven.Overlay port to engine ImGui runs as an independent parallel track, and a movement code-organization audit (engine mover versus game-side command wiring) precedes any relocation.

M3 deliberately excludes audio, which becomes its own milestone (miniaudio for mixing and UI sound, Steam Audio for 3D spatialization). It also excludes networked (Steam/Facepunch) identity providers, weapons, skeletal animation, and the lighting stages below.

The DigitalHeaven workspace becomes the engine’s asset home. A map — its geometry, colliders, spawns, and Source-style dev textures — is authored in the workspace, compiled to a .pallet, and loaded at runtime; surfaces render with sampled textures instead of procedural shading; 2D and spatialized 3D sounds play through a real mixer; and the DigitalHeaven overlay becomes a proper modal layer that frees the cursor, gates gameplay input, and takes part in an Escape stack.

FeatureScope
Renderer texturesVulkan combined-image-sampler support in the mesh pipeline — a GpuTexture type, a set-1 albedo descriptor, and a fragment shader that samples the already-plumbed UVs. glTF materials stay unsupported by design; DigitalHeaven pallets are the material system
Asset integrationAn engine-side runtime loader over the compiled .pallet format, referencing the existing DigitalHeaven.Core binary reader directly. Textures load from PNG blobs, models from GLB blobs, materials from semantic texture slots, with a CRC32-keyed cache for hot rebuilds — mirroring how DigitalHeaven.Unity consumes the same artifact
Maps as assetsA new first-class dh.map asset type in the DigitalHeaven product (Core schema, compiler, CLI): visual geometry, explicit collider primitives, spawn points, and material assignments. The engine’s map installer sources a whole map from a pallet, and the debug map is re-authored in the workspace with orange and gray-grid dev textures. Compiled pallets are the bar; opening unpacked source maps is designed-for but deferred
AudioA DigitalHeaven.Engine.Audio stack: hand-rolled miniaudio P/Invoke owns the device and node-graph mixer (fixed 48 kHz), Steam Audio (phonon) provides per-source binaural HRTF spatialization as a DSP node, with an atomic parameter handoff to the audio thread. Ships device + mixer, a 2D sound, and one spatialized 3D source; sound files load directly with a formal dh.sound type deferred
OverlayThe overlay behaves as a modal layer — it frees and shows the cursor while gating gameplay input (which was already gated), and Escape pops a stack: overlay first, then the pause menu, then gameplay. The dormant DigitalHeaven logo is shown via a minimal registered-texture path in the ImGui backend

M4 reuses seams already in place: the UV vertex path is complete end-to-end, the ImGui backend already implements the Vulkan sampler path for its font atlas (the copy-from template for both mesh and logo textures), and the M2 map-installation lifecycle is where a loaded dh.map plugs in. The DigitalHeaven.Core change stays additive so the dependency-free Unity runtime keeps building.

M4 deliberately excludes glTF material rendering, a formal dh.sound type, ambisonics, audio occlusion/reverb, GPU audio (TrueAudio Next), audio streaming and capture, Studio editing for dh.map, texture atlasing/streaming, multi-map PBR materials beyond albedo, skeletal animation, and the lighting stages below.

Milestone 5 — Multiplayer Content Delivery

Section titled “Milestone 5 — Multiplayer Content Delivery”

A client no longer needs the current map baked in ahead of time, a player’s own windowed game can host friends without standing up a separate dedicated process, map authors get a proximity-triggered logic entity to build with, and distant textures stop shimmering.

FeatureScope
Map replicationThe server advertises the current map as a (name, contentHash) pair — in serverInfo on join, and broadcast as MapLoad on a live switch. A client with that content cached locally hash-skips straight to loading it; otherwise it answers MapRequest and the server streams the pallet as reliable-ordered MapChunks, which the client assembles, hash-verifies, caches to disk, and loads. NetProtocol.Version bumps 78 for the three new message ids. A fresh join gates ClientReady on delivery completing, so a slow transfer never races a pawn spawn
Listen serverTwo archived preferences, server.open (bool, default false) and server.maxPlayers (int, default 1, min 1, counting the local host), let the windowed embedded game bind a real UDP socket alongside its loopback host. A CompositeServerTransport fans one NetServer across both transports; capacity is enforced on connect, with the local host always admitted and over-capacity remotes disconnected with a “server is full” reason. Settings apply at next launch, not live — rebinding a listening socket mid-session was judged not worth the firewall/teardown risk. The dedicated --headless server is untouched and stays uncapped
pressurePlate entityA new logic entity kind: a non-solid proximity trigger with no Use key. The server checks capsule-vs-box overlap against player pawns every tick and fires onStartTouch / onEndTouch on the occupancy edge (0→1 / 1→0), so overlapping touchers never re-fire the same edge. Occupancy count replicates for the entity-gizmo overlay and joins the existing button/door/timer/gates/light logic kinds
Build-time mipmaps + anisotropyThe compiler generates a texture’s full mip chain at build time and stores the reduced levels in a companion MipContainer blob (.mipchain, magic DHMP) beside the texture. The engine uploads the whole chain in one shot with no runtime generation, and samples it trilinear plus anisotropic — samplerAnisotropy enabled and clamped to min(deviceLimit, 8)× wherever the physical device supports it

Map replication and the listen server share one path deliberately: a remote joining a listen server without the host’s map goes through the exact same advert/hash-skip/chunk flow as joining a dedicated server, and only the loopback host is skipped by a broadcast because it reloads in-process. M5 deliberately excludes streamed/partial map loading (a transfer is all-or-nothing), a live-rebindable listen socket, and other proximity-trigger shapes beyond a box.

What the physics module is asked to do beyond moving the player. Everything here is server-authoritative — the client predicts its own movement and nothing else — and lives behind the IPhysicsWorld vocabulary, so Box3D stays quarantined in one assembly.

FeatureStatus
Character movement + static collisionShipped. A Source-style capsule mover against map geometry: authored box colliders and V-HACD convex hulls, with per-material surfaceprop friction
physicsProp rigid bodiesShipped. A map-authored dynamic box with a mass, simulated by the server’s solver — gravity, contacts, tumbling, sleeping — with its friction re-tuned each tick from the surfaceprop of whatever it is resting on, pushable by a player walking into it, and replicated (position and orientation, slerped on the render timeline) to every client
Standing on props, tall stacking, grab/carryNot implemented. Props exist only in the server’s physics world, so the client predictor cannot stand on one; stacking is whatever the solver gives for free, and there is no carry mechanic
Ragdolls, joints, vehiclesNot implemented. No articulated bodies of any kind yet

Work that serves whoever is building the content rather than whoever is playing it.

FeatureStatus
Headless offscreen renderingShipped. A host launch mode, --render <dir>, shoots a map’s authored camera entities to PNG with no window, no server and no simulation. It resolves world defaults against a virgin preference store and pins every drifting input, so two runs of the same scene produce byte-identical images — which is what makes render diffing and regression shots possible. --renderUi additionally composites the Halcyon layer and writes its whole transition as a fixed-step sequence, so player UI can be looked at without opening the client
Debug overlaysShipped. The client.debug.* family: entity and node gizmos, collider/light/cascade visualizations, and the axis gizmo plus position readout that print authoring coordinates in the exact conventions a .dh-map accepts
Graphics benchmarkingShipped. The benchmark console command and the --benchmark <scenario> launch mode time the renderer over a scripted camera flythrough of a graduated procedural load — geometry and light ladders, plus on/off pairs for ambient occlusion, temporal antialiasing, bloom, sun shadows, multisampling and lightmaps — and write frame-time percentiles as JSON and as a table. Deliberately manual: it is for the moment after a graphics change, not for every build
Automated render-diff regressionNot implemented. Offscreen rendering is deterministic, but nothing yet renders a reference set and compares it on every build

The baked-lighting identity — MonoSH lightmaps, light-probe volumes, box-projected reflection probes — lands in stages rather than as one big lightmapper project:

StageWhat it delivers
v0 ✅Analytic lights — shipped. An HDR scene target resolved by a tonemap pass, a metallic-roughness BRDF over normal-mapped surfaces with a Source-style half-Lambert wrap, a masked emissive channel, and up to 64 ranked point/spot/directional lights per frame in a set-0 light UBO. Authored three ways: light map entities, replicated world.sun.* / world.ambient.* world settings (seeded by a map’s lighting block), and live lights.* console commands. Hemisphere ambient stands in for the eventual skybox spherical harmonics
v0.1 ✅Sun cascaded shadow maps — shipped. Three cascades in a D32_SFLOAT array texture, each fitted to a bounding sphere around its frustum slice with the light-space origin snapped to whole texels, so the shadow edge does not crawl or swim as the camera moves. A depth-only pass draws the scene once per cascade (slope-scaled polygon bias plus normal-offset bias at sample time); sampling is 3×3 PCF over a hardware comparison sampler with the kernel rotated per pixel by the shared dither noise, and a dithered blend band dissolves each cascade seam. Shadow attenuates the direct sun term only — ambient is untouched, so the half-Lambert wrap keeps shadowed surfaces softly lit. world.sun.shadows (replicated), client.render.shadowResolution, client.render.shadowDistance and client.debug.cascades all apply live. Sun only; point and spot lights stay shadowless
v0.2 ✅Procedural sky and sky-derived ambient — shipped. A single-scattering Rayleigh + Mie atmosphere with a soft-limbed solar disc, drawn as a fullscreen background pass at far depth from the map’s own sun, and the same analytic model projected on the CPU into order-2 spherical harmonics as the scene’s ambient — so the fill light is the sky that is actually overhead rather than two authored colors. Opt-in per map (lighting.sky), with lighting.ambientMode (auto / hemisphere / sky) deciding the fill; a map’s explicit ambientSky / ambientGround keep the hemisphere rig under auto. The whole block seeds live world.sky.* and world.ambient.mode preferences. This is the “skybox spherical harmonics” v0 stood in for; the remaining probe work is what v0.5 adds
v0.4 ✅Baked irradiance volumesshipped. The first indirect light in the engine. A compile-time baker walks a regular probe grid over the map’s bounds and gathers, per probe, sky visibility plus one albedo-weighted bounce of the sun and the map’s own lights, tracing against a BVH over the map triangles; the result is nine RGB coefficients in the same order-2 basis the sky ambient already uses, stored as half floats in a companion .givolume blob (magic DHIV) beside the map. The forward opaque shader trilinearly blends the eight surrounding probes — renormalizing around any that baked inside solid geometry — and the result replaces the flat ambient inside the volume, falling back to the existing chain outside it. Ambient occlusion multiplies whichever fill wins, unchanged. Opt-in per map (lighting.giVolume), with client.render.giVolume and client.render.giIntensity for judging a bake live. Diffuse only; probes are static and the bounce is a single gray-albedo pass
v0.5In-engine capture — light probes and reflection probes rendered by the engine itself
v1 🟡CPU lightmapper — partially shipped: direct light only. A map that sets lighting.lightmap gets a compile-time bake: the compiler generates lightmap UVs for the map’s geometry, packs them into an atlas, and ray-traces every light marked "mode": "baked" (plus the sun under bakeSun) against a BVH of the real triangles, so the shadows in the atlas are the map’s own geometry rather than an approximation. The result is stored as an RGBM texture in the pallet and added to the direct term at runtime, with precedence enforced structurally — a baked light is dropped from the realtime light list, so it is counted once and can never double up. What v1 still owes: indirect bounce and MonoSH directionality. The bake integrates direct irradiance only, so it is flat-diffuse — no color bleeding between surfaces, no baked specular, and no light in a room the sun cannot see directly. Ambient still does all the indirect fill work
v2GPU lightmapper — the same output, much faster bakes

Each stage is usable on its own — the engine never waits on the final lightmapper to have lighting.

Everything the shipped stages deliberately do not cover — fog, screen-space AO, reflection probes and indirect specular, point/spot shadows, light cookies, area lights, full SH ambient probes — is cataloged against its Unity equivalent on Porting from Unity, so a scene author can tell “not implemented” from “authored wrong” at a glance.

The HDR resolve is where the post chain lives, and it grows one effect at a time rather than as a stack rewrite. What is in it today:

EffectStatus
Exposure, tonemap curve, ditherShipped. The HDR resolve: aces, reinhard or none, with world-authored exposure and a client dither
Panini projectionShipped. A comfort control, so client-only — off by default
BloomShipped. A progressive downsample/upsample pyramid (13-tap box down, 9-tap tent up) with a Karis firefly average on the first downsample and a max-channel soft-knee threshold, composited between the exposure and the curve. The level count is derived from the resolution, so the glow is the same size on screen at 1080p and at 4K. Six world-authored world.render.bloom* values plus two client-only ones
Vignette, chromatic aberration, depth of fieldNot implemented. Calibration targets are recorded on Porting from Unity
Screen-space ambient occlusionNot implemented. Belongs with the lighting stages rather than the post chain

Bloom is deliberately world-authored and the comfort/cost knobs beside it are not — the same split the exposure and the tonemap curve follow.