Decision log - UnitBuilds-CC/Merkles-World GitHub Wiki
Decision log
Authoritative version with full context, consequences and re-open triggers:
docs/decisions.md.
This page is the skimmable index: 29 ADRs, ADR-001โฆADR-029, no gaps.
Statuses: locked = implement as written ยท locked-contingent = decided, but depends on a spike
result or an open question ยท proposed = our recommendation, needs sign-off.
A decision is changed by amending its ADR, never by quietly coding around it.
Foundation
| ADR | Decision | Status | Rejected instead |
|---|---|---|---|
| 001 | Two-phase project: domain-agnostic engine runtime first, then the game as Mod #1 | locked | one codebase with game rules compiled in; generic engine + separate private mod API |
| 002 | Rust for the engine core | locked | C++ (memory-safety bug classes), Java (the trap Minecraft later rewrote out of), Go (GC again, weaker GPU ergonomics) |
| 003 | WebGPU + WGSL as the rendering API and shader language | locked | hand-written Vulkan (2โ3 months of low-level debugging), GLSL/HLSL native toolchains (no headless validation) |
| 004 | Single-player first; multiplayer consensus deferred | locked | building co-op into the prototype |
| 016 | 64-bit Hierarchical Double-Origin Coordinate System (HDCS): i64 global, f32 camera-relative |
locked | f64 world coords (precision drift), f32 everywhere (breaks hashing past ~10,000 units), 3รi64 per delta (192 bits, a WASM ABI liability) |
| 017 | Determinism boundary: simulation in Rust/WASM, the GPU never authoritative | locked โ highest priority | GPU-authoritative world state |
| 025 | Contract-first agent swarm: read-only contracts/, Architect + CI-gatekeeper roles |
locked | free-for-all file ownership, human review of every line |
State & the ledger
| ADR | Decision | Status | Rejected instead |
|---|---|---|---|
| 005 | Stateless view-time evaluation: S = f(pos, epoch) โ M_local |
locked-contingent (on S7/S8) | a per-tick global hash chain (O(N) replay) |
| 006 | Localized octree Merkle roots, one per region | locked | one global root per tick |
| 007 | Sleep/epoch compaction of the delta mask, LSM-tree analogy | locked-contingent | compact every N minutes; never compact |
| 008 | Slab allocator with bump-pointer reset โ O(1) edits and O(1) mass free |
locked | per-edit heap allocation; copy-on-write chunk arrays |
| 009 | Tiered slabs: L1 VRAM hot / L2 RAM cold, LRU eviction + background micro-checkpoints | locked | forcing a sleep to free memory; unbounded VRAM growth |
| 015 | FlatBuffers + bitpacked u64 deltas for runtime/ledger data |
locked | Cap'n Proto; JSON at runtime |
| 028 | BLAKE3-256 as the ledger digest | locked 2026-10-11 โ signed off: 32-byte digests with domain-separated leaf/branch/event-tag/entity-state prefixes. Locks SPEC-04's hashing half and makes BLAKE3 the first external dependency the workspace takes. Not settled by it: the Merkle tree shape (arity, child order, empty-node rule) โ crates/ledger's first job, and it had to become a contract with published test vectors before any root was canonical. Settled 2026-10-12 by ADR-030: A-10 frozen, crates/ledger landed as the workspace's first crate with behaviour, and fifteen vectors are published in doc 03 |
SHA-256 (slow, no benefit here); non-crypto hashes |
| 029 | Resolve SPEC-09 by taking the slab out of the record address: 16-bit region slot + 36-bit intra-region position (three 12-bit local offsets) + 12-bit payload = exactly one u64. Slabs become append-only record buffers, so their geometry is an allocation choice, not an addressing constraint |
locked 2026-10-11 โ signed off; resolves SPEC-09, freezes A-01 and gives A-03 / A-08 a byte layout | widen the record to 84 โ 96/128 bits (worse per-edit storage, abandons ADR-015); keep 8 bytes with the 16 bits indexing an unbounded per-region sparse edit list; shrink the slab to โค2ยนโถ voxels so 16 bits fits |
| 030 | The Merkle tree shape: octree arity 8 over a derived depth of 12 (36 / 3 from ADR-029's field), child order x_bit | y_bit<<1 | z_bit<<2 with x the low bit, level counted as height above the voxel leaves, one derived empty rule (empty(0) = H(kind 2, 0, โ
), empty(l) = H(kind 1, l, [empty(lโ1); 8])) with separate kinds for an empty region and an empty epoch, ordered collections as binary lists with an odd trailing item paired with itself, fixed-width little-endian integers in every preimage, preimage layout DOMAIN โ kind โ level โ payload, and base terrain committed intensionally (seed โ rule id โ version, never 2ยณโถ voxels) |
locked 2026-10-12 โ signed off after crates/ledger computed the vectors and CI run 38181630859 printed EPOCH_0 91b97a52โฆ byte-identically on x86-64 Linux, x86-64 Windows and ARM64 macOS. Freezes artifact A-10; a rule change from here is a shape break that moves every root and every published vector. Not settled by it: OQ-45 (what the 12 payload bits mean โ hashed opaquely, which is why the shape could freeze first), OQ-27 (the base rule set: fixing it moves GENESIS), and OQ-44, which is now live โ node_parts takes bytes, so nothing types a float out of a preimage |
chained epoch roots (a contract change to A-03 dressed as a tree decision); hashing the base field voxel by voxel (2ยณโถ leaves โ the storage model the design escapes); one flat Morton index as the octree key (docs/02 forbids it, and it makes a disagreeing peer's address wrong); carry-forward for an odd list item (lets a truncated list agree with a longer one); a per-level EMPTY_i table (13 hand-published values to keep in sync, against one rule that derives them) |
Rendering & content
| ADR | Decision | Status | Rejected instead |
|---|---|---|---|
| 011 | Assets are code: SDF raymarching, no meshes, no textures | locked | mesh + PNG pipeline |
| 012 | Shape/material node grammar interpreted by one master shader | locked | one hand-written shader per entity; a visual node editor at runtime |
| 024 | Spatial BVH mapped to slabs for SDF culling | locked | evaluating every SDF for every ray |
| 026 | Reuse an existing GPU runtime (wgpu); do not hand-write low-level plumbing |
locked 2026-10-11 โ signed off: wgpu is the substrate and the raymarcher is ours, written as a WGSL compute pipeline. Answers OQ-26's choice half; spike S9 still owes the cost half |
hand-written Vulkan; adopting a mesh-based voxel engine (Luanti/Minetest โ rejected as a mesh renderer against ADR-011); Bevy only if its plumbing pays for itself |
| 014 | Universal authoring formats compiled on save; RON canonical, JSON accepted | locked | JSON at runtime; binary-only authoring |
Simulation, logic, physics
| ADR | Decision | Status | Rejected instead |
|---|---|---|---|
| 010 | Circuits as compiled graph topologies evaluated analytically f(I, t) |
locked-contingent (on OQ-02) | block-by-block CA redstone (breaks lazy evaluation); "no electricity feature" |
| 013 | WASM (Component Model + WIT) as the modding and logic ABI | locked | Lua/JS embedding; native DLL plugins |
| 018 | Physics by analytic SDF gradient queries (N = โSDF(p)) |
locked | hidden mesh colliders; VRAM readback to CPU |
| 019 | Animation by domain deformation / time-warping | locked | bone rigs and skinned meshes |
| 020 | Audio occlusion by SDF sphere tracing from source to ear | locked | pre-baked sound zones; mesh raycasts |
Multiplayer
| ADR | Decision | Status | Rejected instead |
|---|---|---|---|
| 021 | Transmit signed intent streams (ActionTx), never world state |
locked | authoritative server streaming chunk bytes |
| 022 | WebRTC datachannels / QUIC with split reliable + unreliable channels | locked | one reliable channel for everything |
| 023 | Desync detection by 32-byte epoch root exchange + Merkle bisection repair | locked-contingent (on S3) | per-action server validation; whole-world checksum |
| 027 | Epoch-scoped transaction pool (quorum-or-timer commit) | proposed | per-player epochs (worlds diverge) |
Reading order
If you only read three: ADR-017 (what the GPU may and may not decide โ the whole verification story depends on it, and its acceptance test is the Bitwise Determinism Suite, spike S3 / gate P1.5, see Roadmap & status), ADR-005 + ADR-006 (why the world is a function with local roots, not a hash chain), ADR-011 + ADR-012 (why there is no art pipeline, which is what makes the mod story and the agent swarm possible).