Phase 1 complete Phase 2 · M8 / 14

Gaym

A high-performance voxel sandbox engine and game, written in Rust. AI-first by architecture — every player action and world query flows through a structured command bus that AI agents, mods, and our own test harness use as peer clients via MCP.

commits
331
PRs merged
228
crates
10
days in
24
COMMAND BUS Player winit input AI · MCP peer client Mods same surface World brickmap · gen Render wgpu · LOD Persist event log · snap Lighting sun + block Settings presets · knobs queries · same API
§ what it is

An engine, then a game.

A private learning project to practice software engineering at a serious level: custom engine, strict best practices, maximum performance focus. The game itself is a long-term target; the engine work is the primary learning vehicle.

A persistent, procedurally generated voxel sandbox in the spirit of Minecraft, but with far deeper open-ended progression, Factorio-style automation, a custom high-performance engine, and a native AI agent interface as a first-class citizen — the project's defining USP.

Performance, efficiency, and optimization are non-negotiable in this project. They are preconditions for every merge, not something polished later.

§ the USP

Every action is a typed command.

The defining architectural constraint, from day one. Player input, AI agents over MCP, and mods all dispatch the same commands and read the same queries. There is no separate "AI mode" — the AI is a peer client of the same bus the player uses.

commands

Move · Look · PlaceVoxel · BreakVoxel · Quit · …

Every player action is a structured command. Player input translates winit events into the same enum any other client emits.

queries

VoxelAt · PlayerPose · LoadedBrickCount · BiomeAt · …

A single perception API returns surroundings, inventory, stats, biome, lighting. UI and agents share it byte-for-byte.

determinism · replay

Recorded sessions replay byte-identically.

All state changes flow through the bus, time-stamped. Free debugging, regression tests, and AI training data — and a Phase 1 acceptance test that locks it in.

AI-driven testing

Claude drives the running game through the same MCP surface.

The dev AI is a peer client. Walk scripted scenarios, verify invariants, reproduce bugs, smoke-test new features. Dogfooding the USP is our continuous QA.

# Stand up an MCP server next to the running game
$ cargo run -p gaym-app -- --mcp=8000
# Then any MCP client can drive it through Streamable HTTP:
move_player, look, place_voxel, break_voxel,
query_voxel_at, query_player_pose, query_biome_at,
set_graphics_preset, query_graphics_settings,
§ pillars

Seven non-negotiables.

From Concept.md § 3 — the constraints that decide what merges and what doesn't.

01

Performance first

Custom engine, multithreaded, data-oriented, multi-km view distance. Every milestone carries a wall-clock perf budget; no budget means no merge.

02 · USP

AI-first architecture

Every player action and world query is a structured command, shared by human input, AI agents over MCP, and mods. No parallel mod API, no separate AI mode.

03

AI-driven testing

The development AI drives the game as a peer client of the same AI interface. Dogfooding the USP is the continuous QA loop.

04

Freedom

Fully destructible / buildable world. No duping. Everything must be mined.

05

Long-term progression

No "Netherite + Elytra" dead end. Always something to improve. Capability/tech-tier progression rewards new systems, not just bigger numbers.

06

Automation

Factorio-like full automation of mining and production chains. Every production chain fully automatable by design.

07

Extensibility

Deep modding API, identical to the AI / player command API. One surface to learn, maintain, and document.

§ the dual perf gate

Both gates hold, or it doesn't ship.

One number isn't enough. The ceiling proves the engine lights up a strong GPU at high settings. The floor proves the client stays playable on weak hardware. The floor gate is not optional.

ceiling gate

60 fps @ 4K

preset
Ultra
L0 view dist
640 m (streaming radius)
LOD reach
~10 km (L4 · vd · 2⁴)
hardware
RX 6800 XT
1 % low
≥ 50 fps

Sustained average ≥ 60 on a scripted multi-biome pan-and-fly. Phase 2 exit gate.

floor gate

60 fps @ 1080p

preset
Low
hardware
Steam Deck APU
power
15 W TDP, Van Gogh
1 % low
≥ 45 fps

Single-player, client embeds world + generator. No dedicated server. CPU mesher fallback retained for this gate.

enforced: every milestone PR description includes the measured budget number, the target hardware, and a line-by-line explanation of any delta from the previous milestone. Any frame-time-neutral change that doubles VRAM is a regression.
§ receipts

Numbers, not vibes.

Every PR ships with the measured budget number, the target hardware, and a line-by-line delta from the previous milestone. Below: the wins that earned the most ground, drawn straight from PR descriptions. Both before and after numbers are verbatim — no synthetic averages, no aggregated lines that quietly mix metrics.

170 fps @ vd=640 Ultra · RX 6800 XT
5.4× steady-state fps · −93% GPU prefix-scan · −82% mesher pass count · 111× log compression · 70+ measured wins · 3 weeks
M7-X-15, cumulative cold-start frame mean: 13822 µs (72 fps) → 5876 µs (170 fps) = +136% fps · #94 · #52 + #53 stack to 538 → 2915 fps steady
timeline · ~90 perf-tagged PRs, stacked by day
#20 · M1 graphics settings framework · Apr 25 · ceiling #22 · M2 GPU-driven draw + indirect MultiDraw · Apr 25 · ceiling #28 · M3-4 LOD render integration · multi-km horizon · Apr 25 · ceiling #29 · M3-5 LOD robustness · collision skip · Apr 25 · ceiling #30 · M3-5 polish · stream-sweep 14ms→18.9ms · Apr 25 · ceiling #31 · M3-6 edit cascade L0→L1-L4 · Apr 25 · both #32 · M4 GPU compute frustum cull · CPU cull→0 µs · Apr 25 · ceiling #36 · M5-4a parallelize GPU mesher · 50.96s→12.31s cold-fuzz (~4×) · Apr 25 · ceiling #37 · M5-4b GPU mesher integration · 700→1000 fps standing-still (+43%) · Apr 25 · ceiling #39 · M6-2 Hi-Z occlusion · valley +29% · Apr 26 · ceiling #40 · M6-4 Hi-Z default-on · valley 400→550 fps (+37.5%) · Apr 26 · ceiling #49 · live-frame telemetry + bench-frames perf gate · Apr 26 · diagnostic #51 · per-stage frame breakdown · Apr 26 · diagnostic #52 · prune stale dirty queue · 1850→739 µs (−60%) · Apr 26 · ceiling #53 · skip per-LOD scan · 739→343 µs · cumulative 538→2915 fps (5.4×) · Apr 26 · ceiling #62 · per-vertex lighting through GPU mesher (M7-4b) · Apr 27 · floor #64 · frame-budgeted lighting queue · −27% p99 · Apr 27 · floor #66 · cascaded LOD lightmaps + boundary slabs · −28% p99 · Apr 27 · ceiling #67 · visibility raycast + inverse-square block light · Apr 27 · ceiling #71 · per-vertex sampling + Minecraft-style AO (M7-X-5) · Apr 27 · floor #73 · edit-perf bench at 32m radius (M7-X-6) · Apr 27 · diagnostic #75 · persistent GPU brickmap (M7-X-3b PR1) · Apr 27 · ceiling #77 · persistent GPU lightmap block channel (M7-X-3b PR2b) · Apr 27 · ceiling #78 · GPU compute block-light propagator · 5.9ms→717µs edit (8×) · Apr 27 · ceiling #84 · cold-start drain fix at Ultra · 12+ min stuck → 3 min drain · Apr 28 · ceiling #85 · cap dirty set + drop dispatch prune · 30-43ms → 4-10ms mesh_us · Apr 28 · ceiling #87 · mesher direct slab writes (M7-X-3b PR3) · render_us 11.5ms → 0.28ms · Apr 29 · ceiling #88 · prewarm cache slabs at GPU-mesher takeover (M7-X-9) · render_us p99 −34% · Apr 29 · ceiling #90 · top-K dispatch selection (M7-X-11) · −22% frame, +28% fps · Apr 30 · ceiling #92 · parallel build_mask + downsample (M7-X-13) · pass_count −57% · Apr 30 · ceiling #93 · incremental score cache (M7-X-14) · 47.17s→3.21s, +111% fps · Apr 30 · ceiling #94 · GPU prefix-scan parallelized (M7-X-15) · pass_prefix −93% · Apr 30 · ceiling #96 · SSAO replaces per-vertex AO · floor-gate held 6.2× · Apr 30 · floor #98 · mesher empty-slice skip · pass_count −82% · May 1 · floor #100 · mesher empty-row skip · cumulative −85% pass_count vs main · May 1 · floor #105 · persistent GPU sun-lightmap (M7-X-7 PR1) · May 1 · ceiling #106 · GPU sun propagator + per-frame dispatch · May 1 · ceiling #107 · batch sun-propagator readback hotfix · May 1 · ceiling #108 · skip dense Box alloc when sun result uniform · May 1 · floor #115 · GPU mesh bench bin · validates M7-X-19 on iGPU · May 2 · floor #119 · flip production L0 mesher to GPU-pool-read lighting · May 2 · ceiling #122 · per-LOD GPU pools + flip gpu_sun_propagator default-on · May 2 · ceiling #127 · drop 25% pool-sizing headroom (VRAM-1) · vd=256 fits 4 GiB iGPU · May 2 · ceiling #130 · L0 pool LRU eviction + adapter-aware capacity (VRAM-2c) · May 3 · ceiling #135 · per-LOD pool allocation + VRAM cap (Phase B-1 retry) · May 3 · both #137 · mesher reads per-LOD pools at L1+ (Phase B-1 visual gate) · May 3 · ceiling #139 · GpuBrickmap multi-shard pool prep (Phase B-2 PR1) · May 3 · ceiling #142 · per-shard switch-dispatch in mesher + propagator (Phase B-2 PR4) · May 3 · ceiling #146 · sparse block-channel lightmap pool (audit § 1.1a) · VRAM 3.2 GiB → 191 MiB (−94%) · May 5 · ceiling #147 · distance fog over outer L4 ring (audit § 3.1) · May 5 · ceiling #148 · integer-keyed map → FxHash (audit § 2.3) · May 6 · floor #150 · per-stage tick timers · diagnose 99% load_propagate stall · May 6 · diagnostic #151 · rate-limit L0 load-propagation GPU upload (audit § 4.5) · May 6 · ceiling #152 · pre-size per-LOD hash tables on view distance (audit § 2.3) · May 6 · floor #153 · pre-size dirty + in_flight queues (audit § 2.3) · May 7 · floor #154 · pool BFS dist + queue scratch in propagate_brick (audit § 1.2) · May 7 · floor #155 · initial sun-guess at streaming-load (audit § 4.4 A) · May 7 · ceiling #156 · in-brick lateral sun spread for cave fade (audit § 4.4 A2) · May 7 · ceiling #157 · stack-allocate blocked_above scratch (audit § 1.2b) · May 7 · floor #158 · stack-allocate sun_in scratch (audit § 1.2) · May 7 · floor #160 · GpuBrickmap grow_for_streaming_load API (audit § 2.6 phase 1) · May 8 · floor #161 · grow API for GpuLightmap + GpuSunLightmap (phase 2) · May 8 · floor #162 · wire pool_buffer_revision into GpuMeshStreamer (phase 3) · May 8 · floor #164 · GPU pool lazy-grow at startup (phase 4) · VRAM 2.4 GiB → 256 MiB (−9×) · May 8 · floor #166 · sparse sun-channel lightmap pool (audit § 1.1b) · May 8 · floor #168 · rate-limited L1+ GPU pool load path (audit § 1.1b) · May 9 · floor #170 · HDR pipeline + Reinhard tonemap (audit § 3.4) · May 9 · ceiling #176 · exposure tune 1.5 → 2.5, ambient 0.05 → 0.02 (audit § 3.4) · May 9 · ceiling #177 · half-res bloom + separable Gaussian (audit § 3.3) · May 9 · ceiling #181 · coalesce FreeList push (mesh-cache eviction phase 1) · May 12 · ceiling #184 · GPU compaction for mesh-cache eviction (audit § 2.4 phase 2) · May 12 · ceiling #187 · skip mesher dispatch + invalidate on eviction (audit § 4.4 sticky-sun) · May 12 · ceiling #188 · cache worldgen underground mask per (level, BrickCoord) · May 12 · ceiling #189 · wire MSAA 4× anti-aliasing (audit § 3.2) · May 13 · ceiling #190 · QuadData per-quad surface (audit § 1.5 phase A) · May 13 · ceiling #191 · per-quad slab + GPU mesher write path (B.1) · May 13 · ceiling #192 · PerQuad vertex pipeline + default-on (B.2) · May 13 · ceiling #193 · delete legacy vertex/index path (C) · May 13 · ceiling #195 · incremental score cache for CPU mesh-streamer (audit § 1.6) · May 13 · floor #198 · GpuSunPropagator scaffold (audit § 2.5 PR1) · May 14 · ceiling #199 · real LDS-iterative BFS shader (PR2) · May 14 · ceiling #201 · wire GPU sun propagator into L0 streaming-load (PR3) · May 14 · ceiling #202 · load-time block-light propagation (audit § 4.4 b fix) · May 14 · floor #203 · spawn warmup + loading screen (audit § 2.2) · May 14 · ceiling #204 · M8 PR1 · overlay codec + BLAKE3 hash · May 14 · both #206 · M8 PR3 · off-thread Compactor with cancellation · May 14 · both #208 · M8 PR5 · wire Compactor into main loop · May 14 · both #209 · M8 PR6 · acceptance suite · 100K edits → 0.9% (111×) · May 14 · both #223 · batch template writes + spare quad slab in compact() · compact_us 34.5 → ~10 ms · May 15 · ceiling #228 · sprint fly 12.5 → 50 m/s + slow_frame threshold 250 → 50 ms · May 15 · diagnostic #231 · packed-queue bench replaces warm-stable (--bench default 60s) · May 15 · diagnostic #234 · lighting drain skips the 7× neighbour fan (Phase 2.B PR-F) · May 16 · ceiling #235 · speculative-bright + final-on-bounding · mesh p50 −45%, frame p50 −10% (Phase 2.B PR-G) · May 16 · ceiling #240 · per-frame GPU dispatch rate-limit on L0 load loop · safety net enabling PR-2 (Bug A PR-1) · May 16 · ceiling #241 · bounded upward probe re-land · cold 65 → 78 fps, sprint 58 → 66 fps (Bug A PR-2) · May 16 · ceiling #242 · par_iter L0 light-prep on rayon · cold 78 → 83 fps, sprint 66 → 72 fps (Bug A PR-3) · May 16 · ceiling #243 · velocity-weighted candidate priority · structural for curving motion (Bug A PR-5) · May 16 · ceiling Apr 23 May 5 · audit opens May 14 · M8 May 16
ceiling · RX 6800 XT floor · Radeon 880M / Steam Deck both gates diagnostic / infra hover any dot for details · stack height = PRs that day
#37

GPU mesher integration

M5-4b ceiling · RX 6800 XT @ 4K Ultra
standing-still fps
CPU 700
+43%
GPU 1000
flying / streaming
CPU 700
parity
GPU 700

Async ring of in-flight slots, shared Arc<MesherPipelines> across all 5 LOD streamers. Initial GPU integration was a regression on flying scenes (700 → 400 fps) until six layered optimisations — encoder coalescing, lazy creation, slot-count tuning — recovered parity. The standing-still win is the headline; the architectural unlock for #94, #98, #100 is the real story.

Apr 25 · #37
#32 + #39

GPU frustum + Hi-Z occlusion

M4 + M6-2 both gates
#32 · M4
CPU cull → 0 µs

Per-brick frustum cull moved entirely to compute. The cull_cpu Tracy span isn't entered when effective_frustum_culling() == Gpu.

#39 · M6-2 · valley dense
450 → 580 fps (+29%)

Hi-Z mip chain after the depth prepass. Six layered shader optimisations (cached bind groups, half-res mip 0, SPD-style 5-mip-per-pass) earned the win.

CPU frustum cost reduced to zero in the GPU path; occlusion stage cuts dense-cave fragment work materially. The 7.5% open-scene overhead is the price of the test infrastructure when there's nothing to find — counted as fair trade.

Apr 25 / 26 · #32 / #39
#52 + #53

Mesh-streamer dirty-queue prune + camera-brick cache

streaming hot path ceiling · RX 6800 XT
frame_us steady cumulative
1850 µs
−81%
343 µs
steady-state fps
538 fps
5.4×
2915 fps

Two single-line insights, back-to-back. #52 prunes stale entries from the dirty queue each dispatch instead of walking them. #53 skips the per-LOD scan entirely when the camera's brick coordinate hasn't changed. Combined: 5.4× steady-state FPS for ~zero added complexity.

Apr 26 · #52 + #53
#64

Frame-budgeted lighting queue

M7-X-1 floor · Radeon 880M
bench p99
4007 µs
−27%
2919 µs
bench max
5636 µs
−23%
4328 µs

Brick-load sun-column recomputes deferred onto a per-frame µs-budgeted side queue; edits use an incremental darken-relight BFS bounded by light radius. Mean is flat (within noise) — what changed is the worst case. Streaming bursts spread their cost instead of spiking one frame.

Apr 27 · #64
#87

Mesher writes directly to MeshCache slabs

M7-X-3b PR3 ceiling · RX 6800 XT
render_us cold-start
11.5 ms
−98%
0.28 ms
cold-start fps
baseline
2× fps
2× faster

The architectural pivot of the M7-X-3b series. Eliminates per-slot readback latency by having the GPU mesher write straight into the persistent MeshCache slabs. Removes the GPU back-pressure bottleneck that was strangling cold-start; unlocks the downstream chain (#90 → #94).

Apr 29 · #87
#93

Incremental dispatch-queue score cache

M7-X-14 ceiling · RX 6800 XT
score time cold-start total
47.17 s
−93%
3.21 s
frame_us mean cold-start
15313 µs
+111% fps
6582 µs

The dispatch queue used to re-score every brick every frame. Caching the scores and rebuilding only every 16 frames closes a 47-second cold-start gap to 3 seconds (−93%). +17% drain rate; +111% fps cold-start. Sets up #94 to land later the same day.

Apr 30 · #93
#94

GPU prefix-scan parallelized

M7-X-15 ceiling · RX 6800 XT
pass_prefix
32.23 µs
−93%
2.31 µs
render_us mean
1854 µs
−45%
1016 µs
frame_us mean
6582 µs
152→170 fps
5876 µs

Hillis-Steele inclusive scan replaces a single-threaded serial loop. 256-thread workgroup, 8 barrier rounds. ~14× speedup; render-side bonus from shorter mesher dispatches reducing storage-buffer barrier waits.

May 1 · #94
#98

Mesher empty-slice skip

M7-X mesher floor · Radeon 880M
pass_count
336.85 µs
−82%
59.38 µs
frame_us mean
20206 µs
−30%
14086 µs
frame_us p50
16788 µs
−56%
7370 µs

Most slices in a solid brick are AIR with no exposed faces. A workgroup-shared any_non_air atomic gates the lane-0 greedy-merge scan; empty slices return zero quads instead of paying the full 1024-cell scan.

May 1 · #98
#100

Mesher empty-row skip · cumulative net

M7-X mesher floor · Radeon 880M
pass_count vs main
337 µs
−85%
52 µs
slot_recycle vs main
64 ms
−67%
21 ms
frame_us p99
99535 µs
−26%
73945 µs

Stacks on #98. Per-row row_has_any flags skip the lane-0 walk over entirely-empty rows. Cumulative effect against main is the headline: brick throughput +20% per cold-start (52808 → 63344).

May 1 · #100
#146

Sparse block-channel lightmap pool

audit § 1.1a ceiling · RX 6800 XT
pool VRAM
3208 MiB
−94%
191 MiB
pool slots
102 656
−94%
6 120
materialise / brick
51.9 µs
−66%
17.7 µs

Skip uploads of trivial bricks (uniformly dark or uniformly fully lit). Pool shrinks from 3.2 GiB to 191 MiB — a 3 GiB reduction per LOD. Drain rate +49%. The blueprint that #166 then mirrored on the sun channel and #160-165 generalised to dynamic growth.

May 5 · #146
#160 → #165

GPU pool lazy-grow · 6-phase series

audit § 2.6 floor · iGPU 4 GiB shared
initial cap
32 MiB
L0 · grow on demand
shard size
2 GiB
32 768 slots · max 8
view distance
vd=256m
unlocked on iGPU

Multi-LOD GPU pools used to allocate at full cap upfront, OOM-ing on iGPU. The grow-on-demand series replaces the conservative-cap mitigation with the proper architectural fix: small initial cap, grow at the streamer-driven trigger, free-list ordering preserved across grow boundaries. Bind-group revision counter rebuilds consumers atomically.

May 8 · #160 → #165
#209

Event-log compaction

M8 PR6 both gates
111×
compression
measured ratio 9 / 1000 (0.9 %)
edit count 100 000
main-thread stall 0 µs (per-tick budget verified)
determinism edit → compact → reload → re-compact = byte-identical
crash recovery kill-mid-compaction: world still loads via fallback

Content-addressed brick snapshots with BLAKE3 hash, atomic manifest swap, off-thread Compactor with cancellation. Six PRs in one session (#204 → #209) close the Concept doc's last open question on persistence.

May 14 · #209
the long tail · ~60 more measured wins
PR date change gain tag
#20Apr 25scalable graphics settings framework — Low / Med / High / Ultra runtime reconfigM1
#22Apr 25GPU-driven draw — slab cache, depth prepass, indirect MultiDrawM2
#28Apr 25LOD render integration — multi-km horizonM3-4
#30Apr 25L1-L4 surface cap + F8 skip-bypass14 → 18.9 ms stream-sweepM3-5
#31Apr 25edit cascade — L0 edits propagate up to L1-L4~1 ms / editM3-6
#36Apr 25parallelize GPU mesher — per-slice workgroups + prefix sum~4× cold-fuzzM5-4a
#40Apr 26flip Hi-Z default-on for High / Ultra+37.5% valleyM6-4
#49 / #51Apr 26live-frame telemetry + per-stage breakdown (stream / mesh / upload)infrabench
#62Apr 26per-vertex lighting through GPU mesher + shader (visible)flat no regressM7-4b
#66Apr 27cascaded LOD lightmaps + boundary slabs−28% p99M7-X-2/4
#67Apr 27visibility raycast + inverse-square block lightvisualM7-X-3a
#71Apr 27per-vertex sampling + Minecraft-style AO+61% iGPU held 6.2×M7-X-5
#75Apr 27persistent GPU brickmap — residency + hasharchM7-X-3b PR1
#77Apr 27persistent GPU lightmap — block-channel poolarchM7-X-3b PR2b
#78Apr 27GPU compute block-light propagatoredit dispatchM7-X-3b PR2c
#84Apr 28cold-start drain fix at Ultra — frustum-priority + budget12 min → 3 minM7-X-8
#85Apr 28cap dirty set + drop dispatch prune (neighbour-fan)30-43 → 4-10 ms mesh_usM7-X-8
#88Apr 29prewarm cache slabs at GPU-mesher takeover−34% p99M7-X-9
#90Apr 30top-K dispatch selection (vs full sort)−22% frame, +28% fpsM7-X-11
#92Apr 30parallel build_mask + downsample shader work−57% pass_countM7-X-13
#96Apr 30SSAO replaces per-vertex AO (seams gone)+3.7 ms iGPU held 6.2×M7-X-18
#105 → #108May 1M7-X-7 GPU sun propagator series — persistent sun-lightmap, per-frame dispatch, batched readback, Box-alloc skiparchM7-X-7
#119May 2flip production L0 mesher to GPU-pool-read lightingarchM7-X-7
#122May 2per-LOD GPU pools + flip gpu_sun_propagator default-onarchPhase B PR1
#127May 2drop 25% pool-sizing headroom (VRAM-1)vd=256 fits 4 GiB iGPUVRAM-1
#130May 3L0 pool LRU eviction + adapter-aware capacity2625 → 2462 µs vd=384 iGPUVRAM-2c
#135May 3per-LOD pool allocation + VRAM cap5% peak VRAM vd=384 iGPUPhase B-1 b
#137May 3mesher reads per-LOD pools at L1+ — visual gate5431 µs noisePhase B-1 d
#139 → #142May 3multi-shard pool prep — brickmap → lightmap → sun-lightmap → switch-dispatcharchPhase B-2
#147May 5distance fog over outer L4 ringvisual§ 3.1
#148May 6integer-keyed map → FxHashtighten§ 2.3
#151May 6rate-limit L0 load-propagation GPU uploadstalls gone§ 4.5
#152May 6pre-size per-LOD hash tables on view distancetighten§ 2.3
#153May 7pre-size dirty + in_flight queues on view distancetighten§ 2.3
#154May 7pool BFS dist + queue scratch in propagate_bricktighten§ 1.2
#155 / #156May 7initial sun-guess + in-brick lateral spread (cave fade at cold-start)visual§ 4.4 A/A2
#157May 7stack-allocate blocked_above scratchtighten§ 1.2b
#158May 7stack-allocate sun_in scratchtighten§ 1.2
#160 → #162May 8grow APIs · GpuBrickmap → GpuLightmap → GpuSunLightmap, bind-group revision wiringplumbing§ 2.6 1-3
#164May 8GPU pool lazy-grow at startup (4 doubling grows during cold-start)2.4 GiB → 256 MiB −9×§ 2.6 phase 4
#166May 8sparse sun-channel lightmap pool (mirror of #146)arch§ 1.1b
#168May 9rate-limited L1+ GPU pool load patharch§ 1.1b
#170May 9HDR pipeline + Reinhard tonemap−535 µs noise§ 3.4
#176May 9exposure tune 1.5 → 2.5, ambient floor 0.05 → 0.02visual§ 3.4
#177May 9half-res bloom + separable Gaussian blurvisual§ 3.3
#181May 12coalesce FreeList push (mesh-cache eviction phase 1)refactor§ 2.4
#184May 12GPU compaction for mesh-cache evictionarch§ 2.4 phase 2
#187May 12skip mesher dispatch + invalidate mesh on evictionfix§ 4.4 sticky-sun
#188May 12cache worldgen underground mask per (level, BrickCoord)tighten§ 3.4
#189May 13wire MSAA 4× anti-aliasingvisual§ 3.2
#190 → #193May 13per-quad vertex pipeline · A → B.1 → B.2 → C, deletes legacy patharch§ 1.5
#195May 13incremental score cache for CPU mesh-streamer (mirror of #93)tighten§ 1.6
#198 → #201May 14GPU sun propagator · scaffold → real LDS-iterative BFS shader → wire L0 streaming-loadarch§ 2.5
#202May 14load-time block-light propagation fixfix§ 4.4 b
#203May 14spawn warmup + loading screenUX§ 2.2
#204 → #208May 14M8 plumbing · overlay + BLAKE3 → SnapshotWriter → Compactor → hydrate → wire main looparchM8 PR1-5
#223May 15batch template writes + spare quad slab in compact()34.5 → ~10 ms compact_usM9 b1 r3
#228May 15sprint fly 12.5 → 50 m/s + slow_frame threshold 250 → 50 msdiagBug A
#231May 15replace warm-stable with packed-queue bench (--bench default 60s)infrabench
#234May 16lighting drain skips the 7× neighbour fan7 → 1 dispatch compositionPhase 2.B PR-F
#235May 16speculative-bright + final-on-bounding (per-brick cascade 7 → 2)mesh p50 −45%, frame p50 −10%Phase 2.B PR-G
#240May 16per-frame GPU dispatch rate-limit on L0 load loop — safety net for PR-2infra enables PR-2Bug A PR-1
#241May 16bounded upward probe in compute_entry_sun_for_brick — re-land #227 under cap65 → 78 fps cold · 58 → 66 fps sprintBug A PR-2
#242May 16par_iter L0 light-prep on rayon78 → 83 fps cold · 66 → 72 fps sprintBug A PR-3
#243May 16velocity-weighted candidate priority (deprioritise bricks behind direction of travel)structural curving motionBug A PR-5

methodology: numbers quoted verbatim from PR descriptions in the private repo. Ceiling measurements taken on RX 6800 XT at 2560×1440 or 4K Ultra with view distance 640 m unless noted. Floor measurements taken on Radeon 880M iGPU (notebook proxy for the Steam Deck APU) at 2560×1600 Ultra / vd=256 m. Per-PR measurements are not directly comparable across PRs (different hardware, view distance, and metric per row). For continuous time-series, see the gaym-perf CI gate in the repo.

§ roadmap

Five phases, one engine.

Phase 1 wraps the engine backbone. Phase 2 turns it into a playable world. Phases 3–5 layer game systems, networking, and the public AI/modding surface on top.

Phase 1 complete · 13 / 13

MVP — engine backbone

Cargo workspace · command bus · sparse brickmap · single-biome procedural gen · async streaming · binary greedy meshing · forward renderer · capsule physics · mine + place · event-log persistence · MCP harness · Tracy + perf gates.

Phase 2 in progress · 8 / 14

World, graphics pipeline, gameplay MVP

GPU-driven render, LOD cascade, voxel lighting, event-log compaction landed. Up next: terrain realism, biomes, day/night, inventory + crafting, survival basics, and the first-party blueprint tool.

M1 · Scalable graphics settings (Low / Med / High / Ultra)
M2 · GPU-driven draw: depth prepass + indirect MultiDraw
M3 · LOD cascade + transition cells
M4 · GPU compute frustum culling
M5 · GPU compute mesher (CPU fallback retained)
M6 · Hi-Z occlusion culling (single-phase)
M7 · Voxel lighting (sun + block)
M8 · Event-log compaction + content-addressed snapshots
M9 · Terrain realism: mountains + caves — ridged-FBM mountains live; caves pending
M10 · Multi-biome generation (≥ 5 biomes)
M11 · Day / night + weather
M12 · Inventory + crafting + chests + game-mode toggle
M13 · Survival basics (hunger / thirst / stamina)
M14 · Blueprint tool ("Litematica++") + first-party CLI
Phase 3 planned

Automation · logistics · progression

Automated mining, crafting / processing, logistics / transport. Capability-tier progression rewards. Mobs and combat earliest here, alongside progression. Linked-chest crafting unlocks; basic chests get upgrade paths.

Phase 4 planned

Multiplayer

Authoritative CLI server. Delta sync rides the same event log used for single-player persistence. Server flags govern external MCP access policy; local mods and in-game NPC agents continue to use the bus directly.

foundation · PlayerId envelope, wire codec, per-player Players map, headless gaym-server crate (#210 → #213, landed early under M8 tailwind)

Phase 5 planned

Public AI / mod surface · FSR

External-facing MCP with auth, polished public endpoint. Full modding API. FSR / upscaling integration. Note: the AI-first architecture ships from Phase 1 — Phase 5 only opens the public door.

§ devlog

24 days, 331 commits, 228 PRs.

Hand-curated chapters from the real git history. Solo project, trunk-based, every change goes through a self-reviewed PR — even when the reviewer and the author are the same person.

  1. Apr 23 · day one

    Foundation

    Workspace scaffold lands first. Cargo workspace with the initial nine-crate split, CI running fmt + clippy + test on every push, an empty winit window cleared by wgpu. Then the architectural spine: the command / query bus, with deterministic replay locked in by acceptance test before a single piece of gameplay code exists.

    • e31a648 · M1: scaffold Cargo workspace, crates, CI, minimal wgpu window (#1)
    • 03f37d7 · M2: command/query bus with deterministic replay (#2)
    • cfa1673 · M3: camera + input, bus-routed (#3)
  2. Apr 23 → 24 · two days flat

    Phase 1 wraps

    The remaining ten Phase 1 milestones land in the next 24 hours. Brickmap with a homogeneous-brick fast path, deterministic procedural terrain, async streaming around the camera, binary greedy meshing, the forward renderer hitting the Phase 1 perf gate, capsule physics with auto-step, mine + place through the bus, append-only Zstd event-log persistence, and — the milestone that makes the USP real — the minimum-viable MCP endpoint that lets Claude drive the running game through the same bus the player uses.

    • 8e791d7 · M4: sparse brickmap with homogeneous fast path (#4)
    • 4f7a47b · M5: deterministic heightmap terrain + parallel generation (#5)
    • a126adf · M6: async streaming of bricks around the camera (#6)
    • 5a8ef6c · M7: CPU binary greedy meshing + GPU mesh cache (#9)
    • 58071eb · M8: forward rendering pipeline + streaming mesher (#10)
    • d2d1ced · M9: capsule physics + fixed-timestep tick (#11)
    • 2603f2f · M10: mine and place via raycast + bus (#13)
    • d947e90 · M11: event-log persistence with edit overlay (#15)
    • 0419356 · M12: minimum-viable MCP endpoint (#16)
    • 1789091 · M13: perf gates, Tracy spans, FPS counter (#17)
  3. Apr 24 → 26 · engine track

    GPU-driven graphics

    Phase 2 opens with the graphics settings framework — Low / Medium / High / Ultra, runtime reconfig, no app restart. Then the engine track that will carry the rest of the phase: per-brick CPU draw calls give way to indirect MultiDraw behind a depth prepass; an N-level LOD cascade reaches multi-km horizons with transition cells; frustum culling moves to a compute shader feeding the indirect arg buffer; meshing follows it onto the GPU (CPU mesher retained as floor-gate fallback); and a single-phase Hi-Z occlusion pass cuts the dense-cave draw count by 40 %+.

    • 97da696 · M1: scalable graphics settings framework (Phase 2) (#20)
    • f4c726c · M2: GPU-driven draw — slab cache, depth prepass, indirect multi-draw (#22)
    • d505bec · M3-1: LOD world foundation — generation, downsampling, brick math (#24)
    • 241cf9b · M3-4: LOD render integration (multi-km horizon) (#28)
    • 9c63ac8 · M4: GPU compute frustum cull (#32)
    • 1aef3a8 · M5-1: GPU compute mesher scaffolding (#33)
    • 3e7ed4b · M6-1: Hi-Z mip-chain build pipeline (scaffold) (#38)
    • 234ad78 · M6-2: GPU compute Hi-Z occlusion cull (single-phase, gated) (#39)
  4. Apr 24 → May 2 · the iceberg · 53 commits

    Voxel lighting

    M7 turns out to be the longest milestone of the project. Per-vertex sun + block lighting through the GPU mesher and shader. A flood-fill propagator with a per-frame budget guarded for the floor gate. Then the deeper rewrites: a GPU-side sun propagator, a multi-LOD lighting pool so distant LODs don't lose their light contribution, and a long tail of L1+ correctness work. Every iteration measured against the dual-gate budget; the CPU mesher stays callable on Low.

    • 3c07395 · feat(render): per-vertex lighting through GPU mesher + shader (M7-4b) (#62)
    • 295ca50 · docs: handoff after M7-4 visible lighting landed (#63)
    • ea72a2a · feat(render): mesher reads L1+ sun + block from per-LOD GPU pools (M7-X-7 Phase B PR 2)
    • e6de029 · docs(memory): record M7-X-7 Phase B-1 multi-LOD shipped (#122-#123)
    • … 49 more commits across M7's seven sub-phases
  5. May 4 → 13 · 75 audit commits across 9 days

    The audit

    Self-audit phase. Numbered sections close every loose thread the engine track left behind: § 1.5 rebuilds the surface around PerQuad data and deletes the legacy vertex/index path; § 2.4 adds GPU compaction for mesh-cache eviction; § 3.2 wires real 4× MSAA; § 4.4 chases down a sticky-sun cave-flood lighting bug across three attempts. § 2.6 alone runs to 13 commits.

    • 190 / 191 / 192 / 193 · per-quad surface → slab + GPU mesher write path → default-on → delete legacy (audit § 1.5 phases A–C)
    • 189 · feat(render): wire MSAA 4× anti-aliasing (audit § 3.2)
    • 184 · perf(render): GPU compaction for mesh-cache eviction (audit § 2.4 phase 2)
    • 185 · fix(world): cave-flood worldgen-aware lightmap (audit § 4.4 (A3))
    • 198 / 199 / 201 · GpuSunPropagator scaffold → real LDS-iterative BFS shader → wire into L0 streaming-load (audit § 2.5 PRs 1–3)
    • 202 · render+world: load-time block-light propagation (audit § 4.4 (b) fix)
    • 195 · perf(stream): incremental score cache for CPU mesh-streamer (audit § 1.6)
    • 203 · app+render: spawn warmup + loading screen (audit § 2.2)
  6. May 14 · #196–#197

    Voxel-space coordinates at the boundary

    The 1m-vs-0.5m foot-gun. Player position lives in metres while every voxel-aware surface speaks integer voxel indices. Internal physics, shaders, brickmap, terrain and lighting all stay metric — but the boundary surfaces (MCP queries, HUD, debug) switch to voxel-space-native units. Query::Raycast drops RayHit.t_m; the bus's own doc gets to delete its TODO.

    • #196 · Voxel-space coordinate boundary for MCP, HUD, and debug surfaces
    • #197 · bus: make Query::Raycast voxel-space-native, drop RayHit.t_m
  7. May 14 · #204–#209 · all in one session

    M8: persistence compaction lands

    Phase 2 M8 in six PRs, single session. Closes Concept.md's last open question on persistence: content-addressed brick snapshots over an event-log baseline, BLAKE3-hashed, atomic manifest swap, off-thread Compactor with cancellation, snapshot-preferred hydrate with corruption fallback, idle + size-threshold cadence, and an acceptance suite that proves the 100 k-edit ratio (observed: 0.9 %), byte-for-byte round-trip, and recovery after kill-mid-compaction.

    • 64e72f4 · feat(persist): overlay codec + BLAKE3 content hash (M8 PR1) (#204)
    • 95095fc · feat(persist): SnapshotWriter/Reader + manifest atomic swap (M8 PR2) (#205)
    • ab8c980 · feat(persist): off-thread Compactor with cancellation (M8 PR3) (#206)
    • 5261568 · feat(app): hydrate path prefers snapshot, falls back on corrupt (M8 PR4) (#207)
    • 61391fc · feat(persist+app): wire Compactor into main loop with idle+size cadence (M8 PR5) (#208)
    • c651c1d · test(persist): M8 acceptance suite — 100k ratio + roundtrip + kill-mid-compact (M8 PR6) (#209)
    • 757ef96 · docs: post-M8 hygiene — README/Concept status sync, audit archived
  8. May 14 · #210–#213 · MP foundation

    MP foundation, under M8's tailwind

    Phase 4 ground-laying shipped in the same M8 session. PlayerId envelope on every Recorded bus event (event-log v1 → v2), serde derives + wire codec on the bus, a per-player Players map refactor that collapses Camera / Body / Intent / Mode into one keyed collection, and the headless gaym-server crate skeleton with its own tick loop. No netcode yet — just the seams that make netcode possible without bleeding into the singleplayer hot path.

    • 0667e3f · feat(bus+persist): PlayerId envelope on Recorded; event-log v1→v2 (#210 · MP PR1)
    • 8016edd · feat(bus): serde derives + wire codec (#211 · MP PR2)
    • a8809bf · refactor(app): collapse Camera/Body/Intent/Mode into per-player Players map (#212 · MP PR3)
    • cd79b33 · feat(server): gaym-server crate skeleton + headless tick (#213 · MP PR4)
  9. May 14 → 15 · #214–#217 · M9 opens

    M9: mountains rise

    Phase 2's terrain-realism milestone opens. World-bounds constants pulled into core; the single-biome plain gives way to a ridged-FBM heightmap with domain warp; a LOD-invariant fix keeps mountain ridges crisp across cascade transitions. A closest-first lighting drain (M9 bug 1, round 1) lands as a side fix — the rounds that close it land in the next entry.

    • 3e8cae2 · feat(core): world bounds constants (#214 · M9 PR1)
    • be8d081 · feat(world): ridged-FBM mountain heightmap + domain warp (#215 · M9 PR2)
    • c360629 · fix(world): mountain ridge layer LOD-invariant (#216 · M9 PR2 visual bug)
    • c1dbef4 · fix(world): closest-first lighting drain (#217 · M9 bug1 PR1)
  10. May 15 · #222–#224 · round 3 closes the bug

    M9 bug 1 round 3: L4 overdraw

    Rounds 1 and 2 didn't close it. Round 3 ships probe-first instrumentation for slab-grow and the L0 pipeline (#222), batched template writes plus a pre-allocated spare quad slab in compact() (#223 · compact_us 34.5 → ~10 ms), and the actual fix — recursive L4 coverage with an inverted-gate correction in compact() (#224). Three PRs, one session.

    • fbe40b2 · feat(diag): probe-first instrumentation for slab-grow + L0 pipeline (#222 · PR-A)
    • 9098a8e · perf(render): batch template writes + spare quad slab in compact() (#223 · PR-B)
    • ac42ad9 · fix(render): L4 overdraw — recursive coverage + compact() inverted-gate fix (#224)
  11. May 15 → 16 · #226–#245 · two days, sixteen perf PRs

    Bug A: the L0 throughput ceiling

    Two-day perf hunt. #226 opens with discriminator probes for the L0 load loop; #227 looked like a fix — bounded upward sun probe, O(N) → O(1) — until #230 reverted it after cold-start cratered 65 → 14 FPS at Ultra vd=640. The CPU win let the 3 ms L0 budget fire ~30× more GPU sun dispatches per frame and saturated the swapchain queue. The bench infrastructure itself wasn't measuring the right state: #231 replaces the warm-stable bench with a packed-queue gate that records the operating point the user actually plays at. #228 bumps sprint fly to 50 m/s and drops the slow_frame threshold to 50 ms; #229 + #232 split render_us into CPU acquire / encode / present + GPU color pass; #233 re-bakes the discriminator. Phase 2.B closes the lighting-side wins: #234 drops the 7× neighbour fan from lighting drain, #235's speculative-bright + final-on-bounding cuts per-brick same-LOD cascade from 7 → 2 bakes (mesh p50 −45 %, frame p50 −10 %, in_flight −50 %; the L0 dirty queue drains to zero and stays there).

    Phase 2 of Bug A formally closes mid-investigation. A fresh attack — driven by a finding memo arguing the lighting-bottleneck framing was wrong — pins the actual root cause: a per-frame GPU dispatch throughput ceiling on the L0 load loop, not a lighting cost. Phase 1 ships in five PRs. #239 instruments per-frame L0 GPU dispatch counters (PR-0). #240 adds the per-frame GPU dispatch rate-limit on the L0 load loop (PR-1, the safety net). #241 re-lands #227's bounded probe under the new cap (PR-2 · cold-start 65 → 78 FPS · sprint 58 → 66 FPS — first 60 FPS sprint gate since the M9 saga). #242 moves L0 light-prep onto rayon (PR-3 · cold-start 78 → 83 FPS · sprint 66 → 72 FPS). #243 deprioritises bricks behind the player's direction of travel (PR-5, structural — visible win is for curving motion).

    • 63d2e23 · diag(stream): instrument L0 load loop sub-phases + fix unload-phase capture (#226)
    • 5b85c6d · perf(lighting): bounded upward probe in compute_entry_sun_for_brick (O(N)→O(1)) (#227 · reverted by #230)
    • 3516a68 · perf+diag(app): sprint fly 12.5→50 m/s + slow_frame threshold 250→50 ms (#228)
    • b33ad68 · diag(render): split render_us into CPU acquire/encode/present + GPU color pass (#229)
    • faccc98 · Revert PR #227 — cold-start cratered 65 → 14 FPS at Ultra vd=640 (#230)
    • 0936fb6 · perf(bench): replace warm-stable with packed-queue bench (--bench default 60s) (#231)
    • ea07f04 · diag(render): restore PR #229 render-split probes + plumb into packed-q bench (#232)
    • 979e123 · diag(mesh-stream): re-bake discriminator probe — load vs lighting vs edit (#233)
    • ef06d81 · perf(mesh-stream): lighting drain skips the 7× neighbour fan (#234 · Phase 2.B PR-F)
    • 4f9a0da · perf(mesh-stream): speculative-bright + final-on-bounding (#235 · Phase 2.B PR-G)
    • 664a133 · diag(bench): synthetic sprint-forward input drive (--bench-sprint) (#236)
    • f566140 · diag(bench): per-frame L0 GPU dispatch counters (#239 · Bug A PR-0)
    • b622eef · perf(stream): per-frame GPU dispatch rate-limit on L0 load loop (#240 · Bug A PR-1)
    • d2e9c10 · perf(lighting): bounded upward probe — re-land #227 under PR-1 cap (#241 · Bug A PR-2)
    • 3057ea0 · perf(stream): par_iter L0 light-prep on rayon (#242 · Bug A PR-3)
    • d43933c · perf(stream): velocity-weighted candidate priority (#243 · Bug A PR-5)
    • … docs/memory PRs #237, #238, #244, #245 captured the Phase-2 handoff and the L0-throughput-ceiling finding
§ bug museum

44 bugs I've been fighting.

Every panorama up top is the result of dragging the engine out of one of these holes. Honest devlog — every shot has the actual bug in the caption.

LOD seams & terrain holes 31

Months of fights with the LOD streaming system: seams between levels, whole chunks missing, levels stacked on top of each other, holes the sky shines through.

Blue sky bleeding through terrain between LOD levels.
All LOD levels stacked on top of each other in a weird mess, in LOD colored debug mode.
All LOD levels stacked on top of each other in a weird mess, in LOD colored debug mode.
Blue sky bleeding through terrain between LOD levels, in LOD colored debug mode.
Blue sky bleeding through terrain between LOD levels, in LOD colored debug mode.
All LOD levels stacked on top of each other in a weird mess, in LOD colored debug mode.
All LOD levels stacked on top of each other in a weird mess, in LOD colored debug mode.
All LOD levels stacked on top of each other in a weird mess, in LOD colored debug mode.
Blue sky bleeding through terrain between LOD levels, in LOD colored debug mode.
Large patches of holes in the terrain where the blue sky shines through.
Even larger patches of holes in the terrain where the blue sky shines through.
Large patches of holes in the terrain where the blue sky shines through, in colored LOD debug mode.
Largest patches of holes in the terrain where the blue sky shines through.
Large ring-shaped patches of holes in the terrain where the blue sky shines through.
Blue sky bleeding through terrain between LOD levels.
Blue sky bleeding through terrain between LOD levels.
All LOD levels stacked on top of each other in a weird mess, in LOD colored debug mode.
Blue sky bleeding through terrain between LOD levels, plus L1 showing blocks from below the surface.
Blue sky bleeding through terrain between LOD levels.
L1+ not loaded at all, despite some floating bricks here and there.
Giant patches of terrain missing everywhere, all LOD levels stacked in a mess, in colored LOD debug mode.
Giant black vertical L3+ surface face on the mountain.
Sudden L4 coverage popped in all over the place, covering the entire world (LOD debug mode).
L0 LOD not able to keep up with the player while flying around (distant blue L0 patch in LOD debug mode).
Sudden L4 coverage popped in all over the place, covering the entire world (LOD debug mode).
Black vertical faces between LOD L0 and L1 levels, in colored LOD debug mode.
Black vertical faces between LOD L0 and L1 levels, in colored LOD debug mode.
Sudden L4 coverage popped in all over the place, covering the entire world (LOD debug mode).
Giant ring-shaped missing terrain area where the sky shines through — probably entire L1 missing.
Sudden L4 coverage popped in all over the place, covering the entire world (LOD debug mode).
Black vertical L1 faces on a mountain, in colored LOD debug mode.

Lighting bugs 8

Sun and block lighting going wrong in every direction — underground lit up, surface dark, cave torch failing, far LODs too dim.

Dark terrain at LOD levels L1 and above.
Shading draws weird lines onto the terrain surface where adjacent blocks are.
Lit-up underground blocks in lighting debug mode (red = lit, black = dark) — screenshot taken below terrain, so all should be dark.
Lit-up underground blocks in lighting debug mode (red = lit, black = dark) — screenshot taken below terrain, so all should be dark.
Cave lighting wrong: left wall correctly dark, right wall completely bright, purple torch debug block at the end also incorrectly dark.
Dark vertical block faces around surface level that should be bright.
Dark vertical block faces around surface level that should be bright (newly placed single stone block).
Manually dug-out underground chamber with purple debug torch in the middle — lighting cuts off abruptly, right side completely dark.

SSAO flicker 2

Screen-space ambient occlusion flickering on distant terrain.

SSAO flickering, shown by a distant mountain shot.
SSAO flickering, shown by a distant mountain shot.

Worldgen 3

Issues caught by the worldgen preview HTML before they made it into the world.

Worldgen preview HTML — black stripe on the right side.
Worldgen preview HTML — giant ocean in the middle where the player spawns; bad biome variation.
Worldgen preview HTML — too-perfect middle circle, many biomes completely missing.

§ tech

Stack.

Custom engine, no Unity, no Unreal. Z-up, right-handed. Sparse brickmap (32³) plus a five-level LOD cascade. Binary greedy meshing in compute, CPU fallback always callable.

language
Rust · edition 2021 · 1.95
ECS
bevy_ecs 0.14
graphics API
wgpu 22 · Vulkan / Metal / DX12
windowing
winit 0.30
parallelism
rayon · parking_lot · pollster
math
glam 0.29 · bytemuck
profiling
tracy-client 0.17 · CI perf-gate
storage
framed Zstd · BLAKE3 · LSM-style
AI surface
Streamable HTTP MCP
workflow
trunk-based · squash-merge · self-review
CI gates
fmt + clippy -D warnings + test + perf
platforms
Windows · Linux · macOS
workspace · 10 crates
gaym-core gaym-settings gaym-bus gaym-world gaym-render gaym-persist gaym-mcp gaym-perf gaym-server gaym-app