Our region files were not Anvil. Three things stood between them and vanilla's
deserializer, and each is enough on its own:
* everything was nested under a "Level" compound. Chunk data lived there until
1.18; SerializableChunkData builds a flat root and never looks for the key.
* a section's Y was an Int. Vanilla writes putByte and reads getByteOr, so
every section of ours decodes as index 0 and overwrites the one before it.
* block palettes were packed at ceil(log2(size)) bits. Strategy's tableswitch
routes bit counts 1 through 4 to the same four-bit configuration, so a
palette of 2..16 states is four bits wide on disk. Ours were one to three,
which makes the long array a quarter of the length vanilla computes, and
SimpleBitStorage rejects the section outright rather than misreading it.
Biome containers were already right: Strategy has no such floor for them, and a
Global configuration above three bits still stores palette indices, just at its
own width. The suspicion that biomes collapsed on save/load was unfounded --
what let it stand is that every round-trip test in this package set blocks and
asserted blocks, so nothing proved biomes survived. They do now, per cell, for
every palette width a section can hold.
Verified against vanilla rather than against our reading of it:
tools/VanillaChunkFormatCheck.java opens a region file we wrote using vanilla's
RegionFile, NbtIo, Strategy and SimpleBitStorage. Sixteen generated chunks pass.
Reverting either the Y type or the palette floor makes it fail with vanilla's
own message -- "Invalid length given for storage, got: 64 but expected: 256" --
so the check can fail, which is the only reason to trust it passing.
Reading a world the official server generated is what the surface-height parity
capture in CLAUDE.md has always needed, and this is half of it.
|
||
|---|---|---|
| cmd | ||
| internal | ||
| tools | ||
| .gitignore | ||
| CLAUDE.md | ||
| go.mod | ||
| Makefile | ||
| README.md | ||
RegionIO
A Minecraft Java Edition server core written in Go, targeting version
26.1.2 (protocol 775). RegionIO implements the connection lifecycle
(status → login → configuration → play), multiplayer chunk streaming, shared
block editing, persistent worlds, and an overworld generator built on the real
noise_router final_density tree.
Status
- Network: full handshake/status/login (offline mode)/configuration/play state machine with zlib compression, keep-alive, and chunk streaming.
- Registries: 28 synchronized registries + tags, captured verbatim from the 26.1.2 vanilla server and sent during configuration.
- World: revisioned, concurrency-safe chunk snapshots; memoized
level_chunk_with_lightframes; ticket-aware bounded LRU cache; shared frame admission limit; Anvil.mcapersistence with autosave and seed metadata. - Generation: vanilla-derived overworld terrain from the embedded datapack
(
ImprovedNoise/PerlinNoise/BlendedNoise/NormalNoise+ the density function interpreter), 3D multi-noise biomes, surface-rule interpretation, deterministic decoration, and basic template structures. - Gameplay: four-player session registry; player join/leave and movement synchronization; chunk-scoped visibility for players and mobs; shared creative block place/break; broadcast chat; and hotbar item→block mapping.
- Lighting: stored vanilla nibble arrays for sky and block light; horizontal
and cross-chunk propagation; incremental updates after edits; persisted
SkyLight/BlockLight; load-time border reconciliation; and chunk-scopedlight_updatebroadcasts. - Chunk lifecycle: per-client view and prefetch tickets, strict near-first ring streaming, stale-recenter cutoff, explicit client unload packets, and eviction only after the final owner releases a chunk.
- Safety: duplicate chunk generation is coalesced; corrupt stored chunks are not silently regenerated or overwritten; a world cannot reopen with another seed.
Build & run
go build ./...
go run ./cmd/regionio -seed 12345 -port 25565 -viewdistance 2
The world seed defaults to 0; override it with the -seed flag or the
REGIONIO_SEED environment variable. The server listens on 0.0.0.0:25565.
Changing the seed for an existing world directory is rejected. The server caps
the client-requested chunk radius at 2 by default because cold density-based
generation is expensive; raise it with -viewdistance 3 after the surrounding
world has been generated and cached.
Testing
go test ./...
go test -race ./internal/network ./internal/server ./internal/world \
-run 'Test(Integration|BoundaryEdit|PlayerInfo|PlayerRegistry|Concurrent|Incremental|EncodeLight|Cache|Store|Eviction|Region|Ticket|Streamer|LoadSixteen)'
# or run both gates:
make verify
The integration suite exercises four clients across two visibility regions:
join, movement, leaving, mob visibility, and local block/light updates. A
two-client scenario separately covers shared block edits and chat. Concurrency
tests cover simultaneous frame encoding, editing, autosave, cache misses, and
session movement/broadcasts. A 16-client lifecycle test exercises overlapping
ticket ownership, bounded global frame work, packet output, and cleanup after
disconnect. Lighting tests compare the initial flat chunk and a 31x31x31
glowstone propagation volume against fixtures captured from the official
vanilla 26.1.2 server. Optional terrain parity diagnostics compare surface
heights against /tmp/vanilla_ground.json when that capture is present.
v0.4 scope
RegionIO v0.4 is a small creative multiplayer server core, not a complete vanilla gameplay implementation. Player and mob visibility is chunk-scoped, but there is no interest prioritization or delta-movement compression yet. Lighting matches vanilla's block-state dampening, emission, and face-occlusion properties and reconciles persisted borders when chunks re-enter the live cache. Streaming prioritizes Chebyshev rings and abandons unstarted stale work; an already admitted frame calculation completes atomically rather than being interrupted halfway. Unowned clean chunks remain as an LRU warm cache until capacity pressure evicts them. Structures, placed features, mob AI, authentication, inventory, and survival mechanics remain intentionally partial. The density router is vanilla-derived, while biome/surface/decoration layers still contain approximations and require stricter parity fixtures.
Project layout
cmd/regionio/ entry point (config, listener, graceful shutdown)
internal/
protocol/ wire primitives: VarInt, framing, compression, packet IDs
nbt/ NBT encoder/decoder (with modified UTF-8)
registry/ embedded synchronized registries + tags
world/ chunk model, level_chunk encoder, cache, generators, biomes
worldgen/ noise core + density-function interpreter + climate finder
network/ per-connection state machine (handler/conn/play/login/...)
server/ shared core: config, status response, profiles
Notes
The vanilla server.jar and its unpacked libraries//versions/ are not
included (obtain them from Mojang). The embedded data under internal/
(registries, biome parameters, the overworld datapack) is derived from vanilla
reports and is all that is required to build and run.