World format
Worlds are vanilla Anvil worlds in the 26.x layout, data version 5023 (Minecraft 26.3). A world written by the vanilla server loads here, and a world saved here opens in vanilla. This was verified in both directions with the real thing: a real vanilla region (140 chunks, seed 424242) is decoded and checked against an independent Python decoder, and the same world re-written by this server’s code was opened by a real vanilla 26.3 server with block entities, fluids, leaves and a spawner intact and no errors in the log.
Older worlds (data version 3459 to 5023) are upgraded when they are loaded. See the compatibility notes below.
To use an existing world, put it in the server directory under the level-name folder (default world/) and start the server, or point --world-dir at its folder. To move one out, copy the folder. Take a backup first, and stop the server before copying.
26.x layout
Section titled “26.x layout”<world>/level.dat gzip NBT {Data:{spawn, Time, GameType, difficulty_settings, Version, DataPacks, ...}}<world>/data/<ns>/<name>.dat saved data: world_gen_settings (seed!), game_rules, weather, world_clocks, scoreboard...<world>/dimensions/<ns>/<path>/region/r.X.Z.mca chunks (overworld = dimensions/minecraft/overworld)<world>/dimensions/<ns>/<path>/{entities,poi}/ same region format, other schema (NOT implemented, see gaps)<world>/dimensions/<ns>/<path>/data/<ns>/<name>.dat per-dimension saved data (raids, world_border, chunk_tickets, ...)Seed, game rules, weather and clocks are no longer in level.dat. Block-state palette entries are a plain string
for a block’s default state or a compound {id, properties:{...}} (was Name/Properties); a palette mixing both is a
heterogeneous list ({"": "minecraft:stone"} wrappers on the wire, unwrapped by nbt::ListView/ListTag).
The three dimensions are separate levels with their own region files:
GameServer has dimension_count() slots: index 0 = the overworld (GameServerInit::level), then GameServerInit::extra_dimensions (Ramjet adds
minecraft:the_nether, minecraft:the_end), then the levels added at runtime (add_level, see “Runtime worlds”; a removed level’s slot is free and
dimension_live(dim) is false for it). ecs::DimensionRef::index is that index everywhere (physics Env, items, mobs, combat already used it).
| per dimension | where |
|---|---|
LevelInfo (key, layout, provider, chunk map, seed, dimension::Kind) |
GameServer::level(dim), dimension_index(key) |
chunk provider (own region files dimensions/minecraft/<name>/region) |
LevelInfo::provider, ticked by GameServer::chunk_integration, polled by the idle handler / chunk_pump |
ticks::LevelRuntime (scheduled ticks, fluids, random ticks; nether: fast_lava, water_evaporates, infiniburn_nether, no skylight) |
GameServer::level_runtime(dim), one `ramjet:level_ticks[_nether |
InteractionRuntime (block breaking / placing, observer packets only to its dimension’s players) |
GameServer::interaction(dim) |
ChunkWaiters (chunk -> sessions waiting for it) |
GameServer::chunk_waiters(dim) |
physics Env (collision world, fast_lava) |
GameServer::physics_env(dim) |
BlockEntityStore (furnaces, end gateways) |
CraftingService::block_entities(dim), DimensionService |
POI index (<dim>/poi/r.X.Z.mca) |
DimensionService::poi(dim) |
GameServer has no zero-argument level accessors any more: level(dim), level_runtime(dim), interaction(dim), chunk_waiters(dim), physics_env(dim), CraftingService::block_entities(dim); the overworld-only
ones are spelled overworld_level(), overworld_interaction(), … so a call site that forgets the dimension does not compile (sessions use server_.interaction(dimension())).
Players: Session::dimension() / level(). Everything the session layer did with “the” level now uses the player’s: chunk view diff / pacing / simulation
tickets, the physics env of its moves, interaction, block entity menus, the saved Dimension.
- Entity tracking (
tracker.cpp): entities carryEntityParams::dimension(stored in theHot::okbits 8..15); a viewer’s dimension is its own entity’s;visible()is false across dimensions. Players, item entities and mobs set it (mobs:MobTracking, docs/agents/mobs.md “Per dimension”). - Time / weather: one game time for every level (
LevelInfo::game_timemirrored each tick), world clocks are global (the client gets all of them in the fullSetTime). Weather exists only in the overworld (Level.canHaveWeather): the broadcasts go to its players (GameServer::broadcast_in), a joining / arriving player gets the rain events only there. - Dimension properties are constants in
dimension.hpp(dimension_portal_testchecks them against the datapack’sdimension_typeJSON): coordinate scale, ceiling, skylight, logical height, infiniburn,END_SPAWN_POINT.fast_lava,water_evaporates, the bed rules andrespawn_anchor_worksare read at run time through the environment attributes (Level::env_value, docs/agents/environment-attributes.md); the constants only seed levels built without a datapack.
Compatibility notes
Section titled “Compatibility notes”- Seed, game rules, weather and clocks are not in
level.datany more in 26.x (they moved todata/<ns>/<name>.dat). Block-state palette entries use the 26.x string/compound forms. - Older worlds are upgraded on load. Worlds from data version 3459 up to 5023 are upgraded in memory and saved at data version 5023 by the normal save paths. Worlds older than 3459 are refused with a message, and so are worlds newer than 5023.
- Backup before the upgrade. Before the first write of an upgrade, the whole world folder is copied to
<level-name>_pre_<data version>_backup(world.backup_before_upgrade, on by default). The copy needs free disk space about the size of the world; when there is not enough, the upgrade does not start. - One server per world. The world folder is locked while the server runs: a second server on the same folder is refused.
- Region files are plain
.mca: gzip, zlib, uncompressed and LZ4 are read, and the server writes zlib. Chunks of 1 MiB or more go to.mccside files like vanilla. - Player data,
ops.json,whitelist.json, ban lists andusercache.jsonuse the vanilla formats. level-type=flatis a test-only superflat world with no persistence.
Known gaps
Section titled “Known gaps”- Data fixers: no longer a gap. Chunks, level.dat, entity records, player files and structure templates older than 5023
(1.20.5 and later) are upgraded on read; see
docs/agents/datafix.mdfor the range, fix list and what is still missing (pre-1.20.5 fixers, text components, saved-data files, advancements and stats renames). poi/region storage (PoiManager schema) is read and written byvillage/poi.cpp(region files).entities/is implemented by the mob system (mobs/entity_storage.*,mobs/entity_persist.cpp: mobs, arrows, eyes of ender, the vehicles, the End’s entities, experience orbs); see docs/agents/mobs.md “Projectile persistence”.session.lockis taken bymainbefore the world is opened (world/anvil/session_lock.*: the UTF-8 snowman in<world>/session.lockand an exclusive OS lock, LockFileEx / flock, held untilmainreturns). A second server on the same world directory is refused with vanilla’s message.- LZ4 is read-only (writes fall back to zlib); custom compression (127) unsupported. No
sync-chunk-writestuning beyondAnvilOptions::sync. *_WGheightmaps (WORLD_SURFACE_WG,OCEAN_FLOOR_WG) are not primed for statuses beforeterrain(noChunkmodel); they are passed through as stored.- Light arrays of a decoded chunk are one pooled allocation: the pool lives as long as the
ChunkExtras; hand the data to the light engine (copy) rather than keeping pointers. - Block entities / ticks / structures / PostProcessing / UpgradeData / blending data are raw NBT passthrough until their owners exist.
- Non-ASCII top-level key names of unknown tags are kept as raw Modified-UTF-8 bytes interpreted as UTF-8 (ASCII is exact).
LoadResult::Status::erroris surfaced instead of vanilla’s silent regeneration; the game module decides the policy (regenerate / refuse / back up).
See also known gaps for the other modules.