Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Level data and rules

Verified against Minecraft 26.2 · Reference · Who owns the seed, the spawn, the rules, the border and the dimensions, where each is saved, and what tells the client — looked up, not watched.

The facts about a world that are not blocks or entities: its seed and dimensions, its spawn, its game time, its difficulty, its rules, its border, its scoreboard and maps. In 26.2 about half of them have left level.datPrimaryLevelData is a stub and everything else is a SavedData file under a data/ folder, one server-global and one per dimension — so the question this page answers is always the same one: which file remembers this, and who is allowed to change it. The table under who owns what is the page; the sections after it are the prose behind the rows that need it, and the rest are one line each because one line is all there is.

Part IV is where these things are used; the level tick is where most of them are read.

Who owns what

datumownersaved astold to the client by
seed, structures, bonus chest, dimension listWorldGenSettings (MinecraftServer.getWorldGenSettings)data/minecraft/world_gen_settings.datthe obfuscated seed in CommonPlayerSpawnInfo, the dimension list in ClientboundLoginPacket; structures and the bonus chest, nothing
world spawnPrimaryLevelData.respawnDatalevel.datClientboundSetDefaultSpawnPositionPacket
game timePrimaryLevelData.gameTime (shared by every level)level.datClientboundSetTimePacket
day timeServerClockManagerdata/minecraft/world_clocks.datClientboundSetTimePacket
difficulty, lock, hardcoreLevelSettings.DifficultySettingslevel.datClientboundChangeDifficultyPacket — hardcore alone rides in ClientboundLoginPacket
game type, allow-commands, name, data packsLevelSettingslevel.datthe game type only as a new player’s default, through CommonPlayerSpawnInfo; the enabled features at the configuration phase; the name and allow-commands, nothing
game rulesGameRuleMap via GameRulesdata/minecraft/game_rules.datfive rules only, plus ClientboundGameRuleValuesPacket on request
weatherWeatherDatadata/minecraft/weather.datClientboundGameEventPacket
world borderWorldBorder, one per ServerLeveldimensions/minecraft/…/data/minecraft/world_border.datClientboundInitializeBorderPacket and the five ClientboundSetBorder… packets
scoreboardServerScoreboard (MinecraftServer.scoreboard), buffered by ScoreboardSaveData at save timedata/minecraft/scoreboard.datClientboundSetObjectivePacket, ClientboundSetScorePacket, ClientboundSetPlayerTeamPacket
mapsMapItemSavedData per MapId, MapIndex for the counterdata/minecraft/maps/<n>.dat, data/minecraft/maps/last_id.datClientboundMapItemDataPacket
raidsRaids per leveldimensions/minecraft/…/data/minecraft/raids.datboss bars
chunk ticketsTicketStorage per level (tickets)dimensions/minecraft/…/data/minecraft/chunk_tickets.dat
dragon fightEnderDragonFight, where DimensionType.hasEnderDragonFightdimensions/minecraft/…/data/minecraft/ender_dragon_fight.datboss bars
boss bars, scheduled functions, random sequences, stopwatches, trader timers, command storageCustomBossEvents, TimerQueue, RandomSequences, Stopwatches, WanderingTraderData, CommandStoragedata/minecraft/<id>.dat, and one data/<namespace>/command_storage.dat per namespaceboss bars only
player dataPlayerDataStorageplayers/data/<uuid>.dat

What is left in level.dat

LevelData is the read-only interface every Level exposes, and it is small: LevelData.getRespawnData, LevelData.getGameTime, LevelData.isHardcore, LevelData.getDifficulty and LevelData.isDifficultyLocked. No day time — that has left level data entirely for ServerClockManager (the server tick) — no weather, no rules, no border. LevelData.RespawnData is the world spawn as a GlobalPos with yaw and pitch — it carries a dimension, so /setworldspawn can point anywhere. WritableLevelData adds WritableLevelData.setSpawn; ServerLevelData adds eight more — the level name, the game type and its setter, ServerLevelData.isInitialized and its setter, ServerLevelData.isAllowCommands and its setter, and ServerLevelData.setGameTime; WorldData is the server-wide view (WorldData.getLevelSettings, WorldData.getDataConfiguration, WorldData.enabledFeatures, WorldData.wasModded, WorldData.getKnownServerBrands, WorldData.overworldData).

PrimaryLevelData implements all of them — the overworld’s level data and the whole server’s — with about ten fields: PrimaryLevelData.settings (a LevelSettings: name, GameType, a LevelSettings.DifficultySettings of difficulty, hardcore and lock, allow-commands, WorldDataConfiguration), PrimaryLevelData.respawnData, PrimaryLevelData.gameTime, PrimaryLevelData.initialized, PrimaryLevelData.knownServerBrands, PrimaryLevelData.wasModded, PrimaryLevelData.removedFeatureFlags, PrimaryLevelData.singlePlayerUUID, PrimaryLevelData.version, and PrimaryLevelData.specialWorldProperty (PrimaryLevelData.SpecialWorldProperty.FLAT / PrimaryLevelData.SpecialWorldProperty.DEBUG / none). That is the whole stub: seed, dimensions, rules, border, weather, dragon fight, boss bars, scheduled events and trader timers are all SavedData files. PrimaryLevelData.createTag builds the payload — flat, no wrapper — and it is LevelStorageSource.LevelStorageAccess.saveDataTag that nests it under Data; PrimaryLevelData.parse reads it back.

DerivedLevelData is what every other ServerLevel gets, built in MinecraftServer.createLevels: spawn, game time and initialised state forwarded to the overworld’s data, and everything the whole server shares — name, game type, hardcore, allow-commands, difficulty and its lock — read straight off the WorldData. Only DerivedLevelData.setSpawn writes anything; DerivedLevelData.setGameTime, DerivedLevelData.setGameType, DerivedLevelData.setAllowCommands and DerivedLevelData.setInitialized are no-ops. That forwarding is how game time comes to be shared by every level.

The file is written by LevelStorageSource.LevelStorageAccess.saveDataTagLevelStorageSource.LevelStorageAccess.saveLevelData: a temp file, then Util.safeReplaceFile renames the old level.dat to level.dat_old and the temp into place, ten retries per step with a rollback. LevelSummary (the world-select row) is read from it by LevelStorageSource.readLevelSummary. LevelResource names the paths: LevelResource.LEVEL_DATA_FILE, LevelResource.DATA, LevelResource.PLAYER_DATA_DIR (players/data/, a new sub-folder), LevelResource.LOCK_FILE.

The spawn every level reports is the server’s, not each level’s

ServerLevel.getRespawnData forwards to MinecraftServer.getRespawnData, which returns MinecraftServer.effectiveRespawnData — recomputed by MinecraftServer.updateEffectiveRespawnData through Level.getWorldBorderAdjustedRespawnData, which relocates a spawn that has fallen outside the border to the border centre’s surface, and by MinecraftServer.findRespawnDimension, which falls back to the overworld when the stored dimension no longer exists. So every level reports the same spawn, and it need not be the one level.dat holds: it is the stored one wherever that is still inside the border and its dimension still exists, and a recomputed one where it is not.

Two saved-data storages, neither of them the overworld’s

SavedData is one flag, SavedData.dirty (SavedData.setDirty); a SavedDataType is an id, a constructor, a Codec and a DataFixTypes. SavedDataStorage — the class that was DimensionDataStorage — caches them per folder (SavedDataStorage.computeIfAbsent, SavedDataStorage.get, SavedDataStorage.set), writes <id>.dat as { data, DataVersion }, gzip-compressed, and saves through SavedDataStorage.scheduleSave: dirty entries are encoded on the caller’s thread and written on Util.ioPool in at most Util.maxAllowedExecutorThreads tasks, chained through SavedDataStorage.pendingWriteFuture; SavedDataStorage.saveAndJoin waits.

The id is an Identifier, so every saved-data file lives under a namespace folder — the path is data/<namespace>/<path>.dat — and vanilla’s are all under data/minecraft/. Command storage is the one place the namespace is not minecraft: each gets its own data/<namespace>/command_storage.dat.

There are two storages. MinecraftServer.savedDataStorage (MinecraftServer.getDataStorage) is server-global at <world>/data/; maps, scoreboard, rules, weather and clocks are server-wide. ServerChunkCache.savedDataStorage (ServerChunkCache.getDataStorage, forwarded by ServerLevel.getDataStorage) is per dimension at dimensions/<namespace>/<path>/data/ — the overworld included (chunk storage); raids, tickets, the border and the dragon fight are per dimension, the overworld’s under dimensions/ like everyone else’s. Neither is “the overworld’s”.

Game rules are a registry

GameRule is a registry entry in Registries.GAME_RULE / BuiltInRegistries.GAME_RULE, bootstrapped by GameRules.bootstrap: GameRule.category (a GameRuleCategoryGameRuleCategory.PLAYER, GameRuleCategory.MOBS, GameRuleCategory.SPAWNING, GameRuleCategory.DROPS, GameRuleCategory.UPDATES, GameRuleCategory.CHAT, GameRuleCategory.MISC), GameRule.gameRuleType (GameRuleType.INT or GameRuleType.BOOL), GameRule.argument (a Brigadier type), GameRule.valueCodec, GameRule.defaultValue and GameRule.requiredFeatures. Ids are snake_case and namespaceable — GameRules.ADVANCE_TIME, GameRules.SPAWN_MOBS, GameRules.SPAWN_MONSTERS, GameRules.KEEP_INVENTORY, GameRules.RANDOM_TICK_SPEED (3), GameRules.PLAYERS_SLEEPING_PERCENTAGE (100), GameRules.RESPAWN_RADIUS (10), GameRules.MAX_ENTITY_CRAMMING (24), GameRules.MAX_SNOW_ACCUMULATION_HEIGHT (1), GameRules.MAX_MINECART_SPEED (feature-gated) … fifty-nine of them, all in the reference. The 1.21 names (doDaylightCycle, doMobSpawning) and the GameRules.BooleanValue / IntegerValue / Key classes are gone.

The values are saved data, not level data: a GameRuleMapSavedData, game_rules.dat, server-global — wrapped by the GameRules instance in MinecraftServer.gameRules. ServerLevel.getGameRules returns the server’s: one set for every dimension, and Level has no rules accessor at all, so no ClientLevel can read a rule — the client’s only GameRules objects belong to the two rules screens, and neither drives gameplay. The accessors are GameRules.get, GameRules.set (which calls MinecraftServer.onGameRuleChanged) and GameRules.visitGameRuleTypes (how GameRuleCommand.register builds two literals per rule — the bare id and the namespaced one).

What the client hears

Five rules, and everything else is server-only. All three of GameRules.REDUCED_DEBUG_INFO, GameRules.LIMITED_CRAFTING and GameRules.IMMEDIATE_RESPAWN ride in ClientboundLoginPacket at join (the last inverted, as showDeathScreen); a change afterwards goes as a ClientboundEntityEventPacket for the first and a ClientboundGameEventPacket for the other two (ClientboundGameEventPacket.LIMITED_CRAFTING, ClientboundGameEventPacket.IMMEDIATE_RESPAWN); GameRules.LOCATOR_BAR through ServerWaypointManager; and GameRules.ADVANCE_TIME, which broadcasts a full clock sync because a paused clock is expressed on the wire as rate 0 (environment attributes). MinecraftServer.updateMobSpawningFlags sends nothing; it only calls Level.setSpawnSettings, which forwards to ServerChunkCache.setSpawnSettings.

New is an in-game editor: ServerboundClientCommandPacket.Action.REQUEST_GAMERULE_VALUESServerGamePacketListenerImpl.sendGameRuleValuesClientboundGameRuleValuesPacketInWorldGameRulesScreen, and edits back as ServerboundSetGameRulePacketServerGamePacketListenerImpl.handleSetGameRule (gated on Permissions.COMMANDS_GAMEMASTER).

The border is per dimension

WorldBorder is SavedDataworld_border.dat, per dimension, fetched by ServerLevel.getWorldBorder through the cache on every call. Nothing scales the Nether’s border by DimensionType.coordinateScale — each dimension’s file has its own values, so the Nether has the same numbers unless someone sets them.

WorldBorder.settings (WorldBorder.Settings: centre, damage per block, safe zone, warning blocks and time, size, lerp time and target; WorldBorder.Settings.DEFAULT is 0,0 / 0.2 / 5 / 5 / 300 / WorldBorder.MAX_SIZE) is the loaded snapshot, never written again; WorldBorder.applyInitialSettings pushes it into the live fields once, restarting a lerp in progress, and saving reads the live fields back out. The live defaults are not the persisted ones — a fresh WorldBorder starts with a warning time of 15, not 300.

The live extent is a WorldBorder.BorderExtentWorldBorder.StaticBorderExtent or WorldBorder.MovingBorderExtent, which WorldBorder.tick advances (the level tick). A moving border re-saves itself every tick: WorldBorder.MovingBorderExtent marks the saved data dirty on every advance, and a stationary one never does. WorldBorder.MAX_SIZE is 59,999,968; MinecraftServer.getAbsoluteMaxWorldSize is applied to every level’s border in MinecraftServer.createLevels — 29,999,984 on the integrated server, but DedicatedServer overrides it with max-world-size. WorldBorder.isWithinBounds, WorldBorder.clampToBounds, WorldBorder.getDistanceToBorder, WorldBorder.getCollisionShape are the readers; BorderStatus colours the client’s wall.

For sync, PlayerList.addWorldborderListener registers a BorderChangeListener per level that broadcasts, dimension-scoped, ClientboundSetBorderSizePacket, ClientboundSetBorderLerpSizePacket, ClientboundSetBorderCenterPacket, ClientboundSetBorderWarningDelayPacket and ClientboundSetBorderWarningDistancePacket; PlayerList.sendLevelInfo sends ClientboundInitializeBorderPacket on join and dimension change. ClientLevel.worldBorder is a plain WorldBorder ticked in ClientLevel.tick.

Dimensions and the seed

DimensionType is a record: DimensionType.hasSkyLight, DimensionType.hasCeiling, DimensionType.hasFixedTime, DimensionType.hasEnderDragonFight (the dragon fight is a dimension flag now, not hard-wired to Level.END), DimensionType.coordinateScale, DimensionType.minY, DimensionType.height, DimensionType.logicalHeight, DimensionType.infiniburn, DimensionType.ambientLight, DimensionType.monsterSettings, DimensionType.skybox (DimensionType.Skybox), DimensionType.cardinalLightType, DimensionType.attributes (an EnvironmentAttributeMap — where ultrawarm, natural, bed_works, respawn_anchor_works, piglin_safe, has_raids and the fast-lava flag went), DimensionType.timelines and DimensionType.defaultClock (a WorldClock holder; WorldClocks.OVERWORLD, WorldClocks.THE_END). That is where DimensionType lost its booleans: to the attribute map and the timelines. DimensionType.getStorageFolder names the on-disk folder. Defaults are DimensionDefaults (DimensionDefaults.OVERWORLD_MIN_Y −64, DimensionDefaults.OVERWORLD_LEVEL_HEIGHT 384, DimensionDefaults.NETHER_LOGICAL_HEIGHT 128); the built-in keys are BuiltinDimensionTypes.OVERWORLD, BuiltinDimensionTypes.NETHER, BuiltinDimensionTypes.END, BuiltinDimensionTypes.OVERWORLD_CAVES.

LevelStem is a DimensionType holder plus a ChunkGenerator (LevelStem.OVERWORLD, LevelStem.NETHER, LevelStem.END). Registries.LEVEL_STEM and Registries.DIMENSION share the id dimension (identifiers and registries); Level.OVERWORLD, Level.NETHER, Level.END are ResourceKeys under the latter, and Level.dimension / Level.dimensionType are the accessors. Level.canHaveWeather is sky light, no ceiling, not the End.

The seed and the dimension list are WorldGenSettingsSavedData, world_gen_settings.dat, server-global, and written not at world creation but by the MinecraftServer constructor, which pushes it into the storage on every boot (creating a world is where it comes from) — holding WorldOptions (WorldOptions.seed, WorldOptions.generateStructures, WorldOptions.generateBonusChest) and WorldDimensions (the stem map; WorldDimensions.bake produces the registry). It is read before the server exists by LevelStorageSource.getLevelDataAndDimensions and pushed in with SavedDataStorage.set; if the file is missing or unreadable the loader logs an error and substitutes a whole default WorldGenSettings — a random seed, structures on, no bonus chest, and the data packs’ dimension list in place of the saved one — then carries on loading the world. MinecraftServer.createLevels makes one ServerLevel per stem; MinecraftServer.levelKeys go out in ClientboundLoginPacket, the dimension type via registry sync, and the biome-zoom-obfuscated seed in CommonPlayerSpawnInfo.

Difficulty and weather

Difficulty (Difficulty.PEACEFULDifficulty.HARD) sits in LevelSettings.DifficultySettings. MinecraftServer.setDifficulty writes it, MinecraftServer.updateMobSpawningFlags, and MinecraftServer.sendDifficultyUpdateClientboundChangeDifficultyPacket. DedicatedServer.forceDifficulty applies server.properties at boot with the lock ignored; there is no getForcedDifficulty. DifficultyInstance — local difficulty — is built by ServerLevel.getCurrentDifficultyAt from ChunkAccess.getInhabitedTime, Level.getOverworldClockTime and the moon phase (an environment attribute, EnvironmentAttributes.MOON_PHASE, indexed into DimensionType.MOON_BRIGHTNESS_PER_PHASE — the one piece of the old moon logic still on DimensionType; see environment attributes).

WeatherData — server-global SavedData, MinecraftServer.getWeatherData — was covered in the level tick. PrimaryLevelData stores no rain fields.

Where to look

PrimaryLevelData.setTagData · DerivedLevelData · LevelSettings · LevelStorageSource.getLevelDataAndDimensions · LevelStorageSource.LevelStorageAccess.saveDataTag · SavedDataStorage.scheduleSave · SavedDataType · MinecraftServer.createLevels · MinecraftServer.getDataStorage · ServerChunkCache.getDataStorage · GameRules.bootstrap · GameRules.set · MinecraftServer.onGameRuleChanged · GameRuleMap · GameRuleCommand.register · WorldBorder.applyInitialSettings · WorldBorder.tick · PlayerList.addWorldborderListener · DimensionType · LevelStem · WorldGenSettings · WorldDimensions.bake · MinecraftServer.setDifficulty · ServerLevel.getCurrentDifficultyAt


Rules: names, never code · how the system works, not how the code reads · newest version only · every backticked name passes tools/verify_names.py.