Summary
Implement a .mca-style region file format for chunk storage. Each region file holds a 32×32 grid of chunks with a location table and compressed chunk payloads. This is the foundation for world persistence — no world save/load exists currently.
Current State
The world is entirely ephemeral:
- Chunks are generated procedurally via worldgen and held in a
ChunkStorage HashMap
- When the player quits, all changes are lost
- There is no file I/O for chunk data anywhere in the codebase
ECSRegistry.saveToJson()/loadFromJson() exist but are never called for world state
Specification
Region File Layout
[Header: 1024 bytes]
- 1024 entries × 4 bytes each (256 rows × 4 columns = 1024 chunk slots)
- Each entry: 3 bytes offset (in 4KiB sectors) + 1 byte sector count
- Offset 0 = chunk not present
[Chunk Data: variable length]
- Each chunk: 4-byte length + 1-byte compression type + compressed payload
- Compression: zlib (standard deflate)
- Sectors are 4096 bytes, chunks may span multiple sectors
This matches the Minecraft Anvil .mca format closely, which is well-understood and efficient.
API
const RegionFile = struct {
pub fn open(path: []const u8) !RegionFile;
pub fn create(path: []const u8) !RegionFile;
pub fn close(self: *RegionFile) void;
pub fn hasChunk(self: *RegionFile, local_x: u5, local_z: u5) bool;
pub fn readChunk(self: *RegionFile, local_x: u5, local_z: u5, allocator: Allocator) ![]u8;
pub fn writeChunk(self: *RegionFile, local_x: u5, local_z: u5, data: []const u8) !void;
pub fn deleteChunk(self: *RegionFile, local_x: u5, local_z: u5) void;
};
Region Coordination
- World chunk
(cx, cz) → region file r.{cx/32}.{cz/32}.mca → local slot (cx%32, cz%32)
- Region directory:
~/.config/zigcraft/saves/<world-name>/regions/
Implementation Plan
-
Create src/world/persistence/region_file.zig
- File open/create with header read/write
- Location table parsing (little-endian)
- Chunk read: seek to offset, read length+compression, decompress
- Chunk write: compress, find free sectors or append, update header
- Chunk delete: zero out header entry, mark sectors free
-
Unit tests
- Round-trip: write chunk → close → reopen → read chunk → verify identical
- Multiple chunks in one region
- Overwrite existing chunk (old data freed, new data appended)
- Empty region (no chunks written)
- Corrupt header handling (graceful error, not crash)
-
Free sector tracking
- Simple approach: scan header on open to build free list
- New chunks appended at end of file
- Freed sectors tracked for reuse (optional optimization for later)
Dependencies
- zlib — available via system libraries (Nix flake may need
zlib added)
- Alternative: use
std.compress.zlib from Zig stdlib if sufficient
Files to Create
src/world/persistence/region_file.zig — core implementation
src/world/persistence/zig — module root (if using directory module)
Testing
Roadmap: docs/PERFORMANCE_ROADMAP.md — Batch 1, Issue 1B-1
Summary
Implement a
.mca-style region file format for chunk storage. Each region file holds a 32×32 grid of chunks with a location table and compressed chunk payloads. This is the foundation for world persistence — no world save/load exists currently.Current State
The world is entirely ephemeral:
ChunkStorageHashMapECSRegistry.saveToJson()/loadFromJson()exist but are never called for world stateSpecification
Region File Layout
This matches the Minecraft Anvil
.mcaformat closely, which is well-understood and efficient.API
Region Coordination
(cx, cz)→ region filer.{cx/32}.{cz/32}.mca→ local slot(cx%32, cz%32)~/.config/zigcraft/saves/<world-name>/regions/Implementation Plan
Create
src/world/persistence/region_file.zigUnit tests
Free sector tracking
Dependencies
zlibadded)std.compress.zlibfrom Zig stdlib if sufficientFiles to Create
src/world/persistence/region_file.zig— core implementationsrc/world/persistence/zig— module root (if using directory module)Testing
Roadmap:
docs/PERFORMANCE_ROADMAP.md— Batch 1, Issue 1B-1