Chunk storage
Verified against Minecraft 26.3 · Part IV · A chunk nobody needs any more is dropped from the world and written to disk, and the server thread never waits for it.
You walk away from your base. Soon after, the chunk you were standing in is
no longer reachable from any ticket, its loading level climbs past
ChunkLevel.MAX_LEVEL, and a queued task takes a snapshot of it, hands
that to a worker to turn into NBT, and hands that to a lane that
compresses it and finds it somewhere to live in r.X.Z.mca. Nothing about
that is surprising. What is surprising is that the chunk was almost
certainly written several times before you left, and that none of those writes
was anybody’s idea. A chunk you keep changing is written by a
background sweep roughly every ten seconds — ChunkMap.saveChunksEagerly,
at most ChunkMap.CHUNK_SAVED_EAGERLY_PER_TICK (20) chunks a tick, only
while fewer than ChunkMap.MAX_ACTIVE_CHUNK_WRITES (128) writes are in
flight, each chunk no sooner than
ChunkMap.EAGER_CHUNK_SAVE_COOLDOWN_IN_MILLIS (10 000 ms) after its last —
and the autosave everyone thinks of as the save comes every five minutes of
the server’s scheduled time, at any steady /tick rate. Almost every write
of your world is one
nobody asked for.
The cast
| class | what it decides | thread |
|---|---|---|
ChunkMap | which chunks are dirty, at which of four moments each is written, and whether a half-generated chunk may overwrite a finished one — it is the region/ store, because it extends SimpleRegionStorage | Server |
SerializableChunkData | the chunk file as a record: what gets copied while the world is frozen and what gets encoded after | Server copies, a Worker-Main-n encodes |
IOWorker | one store’s single lane, and the write-behind map that lets a read answer from a write that has not landed | any IO-Worker-n, one task at a time |
RegionFileStorage | which r.X.Z.mca files are open — an LRU of RegionFileStorage.MAX_CACHE_SIZE (256) | the IO lane |
RegionFile | the sector allocator and the two header tables of one 32×32-chunk file, and the order the bytes land in | the IO lane |
EntityStorage | the entities/ store: what a chunk’s mobs cost to write, and that they are rebuilt on the Server thread | Server builds and parses, IO lane writes |
SectionStorage | the poi/ store under PoiManager: which sections are dirty, and the one load that blocks | Server |
MinecraftServer | when the next autosave falls, and whether every region file is opened with DSYNC | Server |
Copy on the server, encode on a worker, write on the IO lane
flowchart LR
S["the Server thread:<br/>SerializableChunkData.copyOf"]:::server
W["a worker:<br/>SerializableChunkData.write"]:::worker
F["the IO lane, foreground:<br/>IOWorker.store"]:::disk
B["the IO lane, background:<br/>IOWorker.storePendingChunk"]:::disk
R["RegionFileStorage.write"]:::disk
D[("r.X.Z.mca")]:::disk
S --> W
W --> F
F --> B
B --> R
R --> D
One chunk saved, across the three lanes that touch it, coloured by which one:
the server’s share for a full chunk is the first box alone, and it ends at a snapshot, so
everything to the right of it happens after the tick that decided to save has
moved on. The two middle boxes are the same lane at two priorities — the
foreground task waits for the encode and parks the tag in
IOWorker.pendingWrites, and the background one runs only when nothing is
queued in front of it, which is why a write can sit there for a long time and
cost the Server thread nothing.
That figure is the page’s answer to why doesn’t saving lag the server. Apart
from flushing the chunk’s POI column and, for a chunk that is not full, the
guard’s read, the Server thread’s whole share of a
save is the middle of ChunkMap.save: SerializableChunkData.copyOf, which
copies every LevelChunkSection with
LevelChunkSection.copy and each non-empty block and sky DataLayer out of
LevelLightEngine.getLayerListener, clones every heightmap the chunk
holds, pulls block-entity NBT through
ChunkAccess.getBlockEntityNbtForSaving, packs the ticks through
ChunkAccess.getTicksForSerialization, and packs the structure starts.
Everything after that — the palette codecs, the deflate, the sector
arithmetic, the syscall — runs somewhere else, and ChunkMap.save returns
as soon as the copy is done.
Four folders, three of them the same shape
LevelStorageSource.LevelStorageAccess.getDimensionPath defers to
DimensionType.getStorageFolder, which puts every dimension — the
overworld included — under dimensions/<namespace>/<path>/ in the world
folder. Inside are region/, entities/, poi/ and data/. The first
three are region stores of the same shape — a folder of r.X.Z.mca files, a
RegionFileStorage, an IOWorker, and a RegionStorageInfo naming the
store (chunk, entities or poi) so that
MinecraftServer.reportChunkSaveFailure can say which one broke. Only
chunk belongs to ChunkMap itself; EntityStorage and SectionStorage
each hold a SimpleRegionStorage rather than being one. data/ is not a
region store at all, and it is the section below.
A LevelChunk’s entities are not in region/. The
SerializableChunkData.entities list is written only when the chunk’s
persisted status is a ChunkType.PROTOCHUNK — worldgen’s spawns, waiting
for the column to become full. A full chunk’s entities live in entities/,
one region entry per chunk, holding a Position and an Entities list. If
an old save still has entities inside a full chunk’s region/ entry,
ServerLevel.addLegacyChunkEntities adopts them on load.
The other store under data/
SavedDataStorage is the fourth folder and the one that is not a region
store: no sectors, no LRU of open files, one gzipped
data/<namespace>/<path>.dat per thing that has state. It keeps the
Server thread off the disk as a chunk does, and for the same reason.
SavedDataStorage.scheduleSave encodes every dirty entry on the
caller’s thread — the Server thread, inside the save it was asked for —
and hands the finished tags to Util.ioPool, at most
Util.maxAllowedExecutorThreads writes at a time, chaining each onto
SavedDataStorage.pendingWriteFuture so that two saves of one file cannot
race. SavedDataStorage.saveAndJoin is the only place anything waits, and
only a flush save, shutdown or the world upgrader reaches it. Encode while the
world is still, write while it moves: half the bargain the chunk path makes,
over a much smaller object. Which file holds what belongs to level data and
rules.
The four moments a chunk is written
| the moment | what runs it | which chunks | what holds it back |
|---|---|---|---|
| an unload | the task ChunkMap.scheduleUnload queued, drained by ChunkMap.processUnloads | the one chunk being dropped, at whatever status it reached | the tick’s budget, except for the oldest tasks past 2,000 in ChunkMap.unloadQueue, which drain regardless; no cooldown |
| the eager sweep | ChunkMap.saveChunksEagerly, the last statement of that same ChunkMap.processUnloads | everything in ChunkMap.chunksToEagerlySave | 20 a tick, fewer than 128 writes outstanding, the tick’s time budget, and ten seconds per chunk |
| an autosave | MinecraftServer.autoSave → ServerLevel.save → ServerChunkCache.save → ChunkMap.saveAllChunks, without flush | every holder in ChunkMap.visibleChunkMap | only the per-chunk gates: ChunkMap.saveAllChunks clears ChunkMap.nextChunkSaveTime, but ChunkMap.saveChunkIfNeeded still wants an accessible, ready, unsaved LevelChunk (an ImposterProtoChunk passes its type test but is never unsaved) |
| a flush save | /save-all flush, /stop, ServerChunkCache.close | every accessible holder, over and over until a pass saves none | it blocks the Server thread instead |
The dirty set behind the second row is narrower than it looks.
ChunkMap.setChunkUnsaved is installed as WorldGenContext.unsavedListener
and handed to a chunk by ChunkStatusTasks at the moment it becomes full,
and LevelChunk.markUnsaved fires that listener only on the false→true
edge. So ChunkMap.chunksToEagerlySave holds full chunks that have
changed since their last write, each added once, and a chunk still being
generated is never in it.
Turning saving off is not as total as it sounds. ChunkMap.tick ticks
PoiManager first and unconditionally, and only then asks
ServerLevel.noSave whether to run ChunkMap.processUnloads — so a no-save
world still writes village data through SectionStorage.tick, and stops
letting go of chunks until something forces the issue. The only other drain
of ChunkMap.unloadQueue and ChunkMap.toDrop is inside
ChunkMap.saveAllChunks with flush, which runs ChunkMap.processUnloads on
an always-true budget — so /save-all flush unloads them, since
MinecraftServer.saveAllChunks suppresses ServerLevel.noSave when force
is set. Shutdown needs neither: it clears ServerLevel.noSave on every level,
which reopens the tick’s drain, and ticks until ChunkMap.hasWork is false
before its flush save. An explicit save is a
different question again:
MinecraftServer.saveAllChunks passes ServerLevel.noSave on to
ServerLevel.save only when its force flag is clear, and /save-all
sets that flag while MinecraftServer.autoSave does not.
The guard against writing a half-made chunk over a finished one, and where it leaks
The cast row promises this one, so here it is: ChunkMap.save refuses to
write a non-full chunk over a full one on disk, and the refusal is best
effort in two ways. ChunkMap.isExistingChunkFull returns false — meaning
go ahead — whenever the read throws or comes back empty, so an IO error
licenses exactly the clobber the guard exists to prevent. And
ChunkAccess.tryMarkSaved clears the unsaved flag before the guards run,
so a chunk the guard turns away has already been marked clean and will not be
offered again.
Two shapes never reach the write at all. A proto chunk still at
ChunkStatus.EMPTY with no valid structure start is dropped by the same
block, and an ImposterProtoChunk refuses outright:
ImposterProtoChunk.tryMarkSaved and ImposterProtoChunk.canBeSerialized are
flat falses (so is ImposterProtoChunk.isUnsaved), where
ImposterProtoChunk.markUnsaved, ImposterProtoChunk.isLightCorrect and
ImposterProtoChunk.setLightCorrect defer to the chunk underneath (chunk
anatomy). The imposter’s
reason is that the LevelChunk under it is what the saver will be handed
instead.
A chunk nobody needs any more
The save above is the middle of a longer story, which ordinarily runs inside one tick and stretches over several only when the budget runs out or a save dependency is pending. This is the whole of it, from the level change that drops the chunk to the write that finally records it.
sequenceDiagram
participant DM as DistanceManager
participant CM as ChunkMap
participant CH as ChunkHolder
participant SCD as SerializableChunkData
participant IOW as IOWorker
participant SL as ServerLevel
participant PESM as PersistentEntity<br/>SectionManager
DM->>CM: the level climbs past ChunkLevel.MAX_LEVEL, so the key joins ChunkMap.toDrop
Note over CM,CH: the unload phase of the same ServerChunkCache.tick
CM->>CM: processUnloads moves<br/>the holder to<br/>ChunkMap.<br/>pendingUnloads
CM->>CH: getSaveSyncFuture, and the unload task is hung off it
CH-->>CM: the future is complete, or completes later, and the task joins unloadQueue
Note over CM,CH: the same drain while the budget says yes, or a later one, or among the oldest past 2,000
CM->>CH: getSaveSyncFuture again — the same one, or the task rearms and stops
CM->>CM: this exact holder<br/>leaves ChunkMap.<br/>pendingUnloads, or<br/>a ticket took it back
CM->>CM: setLoaded false, then save — PoiManager.flush and the proto-over-full guard
CM->>SCD: copyOf takes the snapshot, a worker turns it into a CompoundTag
CM->>IOW: store, handed the encode future, on the chunk lane
CM->>SL: unload clears the block entities and the tick containers
Note over SL,PESM: in a walk-away, the entities left rings earlier, when the chunk fell below FULL
IOW-->>CM: the IOWorker.PendingStore future completes, one fewer active write
A chunk nobody needs, from the level that stopped needing it to the write that records it, ordinarily inside one tick: the two note bars mark where the budget or a pending save can push the rest to a later one, and the two dotted arrows are completions the Server thread does not wait for. The entities are not in it — in a walk-away they left by a different road, earlier, when the chunk fell below full.
Three things there are load-bearing. The first is that nothing happens until
ChunkHolder.saveSync is done: every promotion future is chained into it by
ChunkHolder.addSaveDependency, and so is
GenerationChunkHolder.generationSaveSyncFuture for as long as a generation
step holds a reference, so a chunk mid-promotion or mid-generation cannot be
saved or unloaded at all.
The second is the guard. ChunkMap.pendingUnloads is removed by identity:
if a ticket re-adopted the position while the task waited,
ChunkMap.updateChunkScheduling has already pulled the holder back out of
that map, the removal fails, and the task quietly does nothing — nothing is
lost and nothing is written twice. And if the sync future changed while
waiting, the task rearms itself on the new one rather than proceeding.
The third is that entities go by a different road, and earlier.
PersistentEntitySectionManager.updateChunkStatus saw the chunk fall below
full, rings before it was dropped, and queued the position in
PersistentEntitySectionManager.chunksToUnload.
If the chunk’s entity file is still being read,
PersistentEntitySectionManager.storeChunkSections returns false and the
whole thing is retried next tick, so a half-loaded set never clobbers the
file. Otherwise each entity EntityAccess.shouldBeSaved accepts is serialised
with Entity.save on the Server thread, its passengers inside it, the tag
goes to the entities lane, and those entities and their passengers are removed with Entity.RemovalReason.UNLOADED_TO_CHUNK. The three types besides Player built never to be saved — a leash knot, a lightning bolt, a fishing bobber — pass the filter, write nothing in Entity.save, and are removed with the rest. The filter runs before
the removal, not after it, so what it turns away on its own account — a
Player, a vehicle carrying exactly one player — is neither written nor
removed. The last of those is not an oddity: ServerPlayer writes its own
root vehicle into the player file under RootVehicle under exactly that
condition, so a boat with one rider travels in the player’s file rather than
the chunk’s.
Two other things leave with the chunk, both after the snapshot is taken:
ServerLevel.unload clears its block entities and unregisters its tick
containers, and ThreadedLevelLightEngine.updateChunkStatus queues the
light engine to forget its layers (lit before you ever see
it).
Why the server thread never waits
IOWorker is not a thread. It holds a PriorityConsecutiveExecutor over
Util.ioPool — a cached pool whose threads are named IO-Worker-n — and
its guarantee is that one task at a time runs for that store, not that the
same thread runs them. Its three priorities are strictly ordered:
IOWorker.Priority.FOREGROUND for IOWorker.store and
IOWorker.loadAsync, IOWorker.Priority.BACKGROUND for
IOWorker.storePendingChunk — the task that touches the disk — and
IOWorker.Priority.SHUTDOWN last. The lowest priority has exactly one user
in the whole game: the barrier IOWorker.waitForShutdown parks behind
everything else when the store closes. A flush is not one of them —
IOWorker.synchronize submits it at foreground priority like a store — but
it still lands behind the writes, because before it flushes it waits on
every IOWorker.PendingStore future, and those complete only when the
background tasks have run.
IOWorker.pendingWrites is what that buys. It is a sequenced map from
ChunkPos to IOWorker.PendingStore, and a second store for a position
already in it overwrites that entry’s data in place without moving it, so
N saves of one chunk before the lane drains become one disk write and
one shared future. IOWorker.loadAsync looks in the same map first and
returns a copy of the pending tag, so a chunk re-loaded while its write is
still pending never touches the region file — read-your-writes by lane order
rather than by any lock. IOWorker.STORE_EMPTY is the null supplier that
means delete, and IOWorker.scanChunk is the streaming ChunkScanAccess
that StructureCheck uses to peek into chunks nobody has loaded.
The three places that do wait
Three places besides a synchronous chunk load and a flush save make the Server
thread wait on a disk. ChunkMap.isExistingChunkFull,
the guard that stops a ProtoChunk overwriting a finished chunk, answers
from ChunkMap.chunkTypeCache when it can but joins the read future inline
on a cold entry — the IO lane, then a datafix pass on the worker pool. And
SectionStorage.getOrLoad joins too, for a POI section that
SectionStorage.prefetch has not fetched. The third is not a chunk-storage
method at all: StructureCheck.tryLoadFromStorage joins IOWorker.scanChunk
to peek at a chunk it will not load, which is what an eye of ender, a
dolphin, an explorer map and /locate all end up doing on the Server thread.
Only the first is on the save path, and only for a chunk that is not full,
which is why a full chunk’s save costs a copy.
Inside a region file
The last box of the first figure is a file format, and it is where the ordering that makes a half-finished save survivable lives.
flowchart TD
A["IOWorker.storePendingChunk pops the oldest entry of IOWorker.pendingWrites"] --> B["RegionFileStorage.getRegionFile, an LRU of 256 regions"]
B --> C["RegionFile.getChunkDataOutputStream wraps a RegionFile.ChunkBuffer in the compressor, NbtIo writes in"]
C --> D["closing the buffer back-patches the length and calls RegionFile.write"]
D --> E{"how many sectors"}
E -- "under 256 sectors" --> F1["RegionBitmap.allocate takes the first free run"]
F1 --> F2["the compressed chunk goes to those new sectors — the content, first"]
F2 --> F3["offsets and timestamps updated, then RegionFile.writeHeader"]
F3 --> F4["any stale sidecar for this chunk is deleted"]
F4 --> Z["and only now are the old sectors freed"]
E -- "256 sectors or more" --> G1["one sector is allocated for a stub"]
G1 --> G2["a five-byte EXTERNAL_STREAM_FLAG stub, and the payload to a temp file"]
G2 --> G3["offsets and timestamps updated, then RegionFile.writeHeader"]
G3 --> G4["the temp file is moved onto c.X.Z.mcc — the content, last"]
G4 --> Z
Two ways to write one chunk, putting the content and the pointer to it in opposite orders: on the left the bytes are on disk before the header that names them, and on the right the header is written first, pointing at a stub, and the real payload only lands when the temp file is moved. Both arms meet at the last box, which is the rule that makes either safe: the old sectors are not freed until the new ones can be found.
A RegionFile is one r.X.Z.mca: two header sectors
(RegionFile.SECTOR_BYTES is 4096) holding a 1024-entry offset table,
RegionFile.offsets, packed as sector number ≪ 8 with the sector count in
the low byte, and a 1024-entry RegionFile.timestamps. Free space is a
RegionBitmap, with the header’s two sectors forced used at construction
and RegionBitmap.allocate handing out the first run big enough. Nothing
ever reads the timestamp table back: RegionFile.write stamps each entry
with epoch seconds from RegionFile.getTimestamp, and the only clock that
spaces one chunk’s writes is the monotonic Util.getMillis behind
ChunkMap.nextChunkSaveTime, while the autosave counts server ticks. None of
them is game time, which the chunk carries only as data. Each
stored chunk starts with RegionFile.CHUNK_HEADER_SIZE (5) bytes — a length
and a compression id — and both RegionFile.write and
RegionFile.getChunkDataInputStream are synchronised on the RegionFile
itself rather than on the channel, though in practice only one lane ever
drives a given folder.
Read the two branches of the figure against each other and the page’s best
fact falls out. For an ordinary chunk the new bytes are on disk before
the header points at them, and the old bytes are released after — so a
crash at any point leaves either the old chunk or the new one, and a chunk
never overwrites itself in place. For an oversized chunk the ordering is
reversed. Anything needing RegionFile.EXTERNAL_CHUNK_THRESHOLD (256)
sectors or more cannot be described by an eight-bit count field at all, so
it goes to a .mcc sidecar and the region file keeps only a stub carrying
RegionFile.EXTERNAL_STREAM_FLAG; and the sidecar is moved into place
after RegionFile.writeHeader has already committed the pointer to it,
destroying the previous copy at a fixed path. The in-file case is
content-then-pointer. The sidecar case is pointer-then-content. Either way
the ordering only buys anything if the writes reach the platter in that
order, which is what DSYNC is for:
MinecraftServer.forceSynchronousWrites returns true as the base default,
and two of the three servers override it — DedicatedServer from
DedicatedServerProperties.syncChunkWrites (sync-chunk-writes, default
true) and IntegratedServer from Options.syncWrites, whose default is
true only on Windows. GameTestServer keeps the base answer.
The compression byte is per chunk, not per file.
RegionFileVersion.selected — set once by RegionFileVersion.configure
from DedicatedServerProperties.regionFileComression (Mojang’s spelling;
the property is region-file-compression) — decides only what new writes
use, choosing between RegionFileVersion.VERSION_DEFLATE (the
RegionFileVersion.DEFAULT), RegionFileVersion.VERSION_NONE and
RegionFileVersion.VERSION_LZ4. Reads honour whatever byte each chunk
carries, including RegionFileVersion.VERSION_GZIP, which has no option
name and so can be read but never chosen, and
RegionFileVersion.VERSION_CUSTOM, which exists so that
RegionFile.createChunkInputStream can recognise it and refuse.
The way back in
Loading is the same road driven backwards, in four stages across three lanes.
ChunkMap.scheduleChunkLoad starts with IOWorker.loadAsync on the IO
lane; ChunkMap.readChunk then hops to Util.backgroundExecutor under the
name upgradeChunk for ChunkMap.upgradeChunkTag, which is
where datafixing happens; SerializableChunkData.parse runs on the same
pool under parseChunk, so those two stages share a lane; and
SerializableChunkData.read runs on the Server thread, where the sections are
installed, the saved light is queued into the
light engine, and PoiManager.checkConsistencyWithBlocks re-derives the
points of interest of any section whose stored record is missing or not marked valid.
Running beside all of it,
SectionStorage.prefetch pulls the POI file in, and the two are joined
before the server-thread step — which is exactly why that step’s
SectionStorage.getOrLoad calls do not block. From there the generation
pipeline
takes over — including what happens when the bytes will not parse.
Entities come back the same shape but land differently: EntityStorage
schedules both the datafix and EntityType.loadEntitiesRecursive on
EntityStorage.entityDeserializerQueue, a ConsecutiveExecutor over the
server main-thread executor, so only the NBT read is off-thread.
Doing all of it at once, with no server running
Optimize World in the world-select screen is the same read and the same
write with the game in between removed. WorldUpgrader starts a single daemon
thread, World Upgrader #n, and hands each of the three stores to a
RegionStorageUpgrader, which walks every r.X.Z.mca file in the folder,
datafixes each chunk tag and writes back the ones that changed — or every one,
into fresh region files, which is what compacts a save whose sectors have
fragmented, though only the dedicated server’s –recreateRegionFiles asks
for that and the button never does.
UpgradeProgress is the counter the screen reads. Nothing here loads a chunk,
generates one, or consults a status: the world is a folder of tags, and the
button’s whole promise is that every tag is at the current data version before
a server ever opens the save.
Questions players ask
Does the game stall when it saves? The chunk stores wait for the disk only
on a flush, though an autosave still copies every unsaved chunk, serialises
the loaded entities, and writes level.dat and every player’s file itself, on
the Server thread, in the tick it falls.
ChunkMap.saveAllChunks with flush loops over the accessible holders,
blocking the main-thread executor on each ChunkHolder.isReadyForSaving
until a whole pass saves nothing, then flushes POIs with
SectionStorage.flushAll, runs ChunkMap.processUnloads with an
always-true budget, and finally joins IOWorker.synchronize with flush.
That, with the entity store’s and the saved data’s own joins in the same save,
is where waiting for the disk is the point rather than an accident, and it is
what /save-all flush and /stop do.
Does lowering the tick rate push out my autosave? Only the one already
counting down, and never past five minutes. The interval is counted in ticks:
MinecraftServer.computeNextAutosaveInterval is the tick rate times 300 — or,
while the server is sprinting, 300 times the rate its recent tick times imply
— floored at MinecraftServer.MIMINUM_AUTOSAVE_TICKS (100, and the typo is
Mojang’s), and the very first is MinecraftServer.AUTOSAVE_INTERVAL (6000
ticks), so each autosave is five minutes of the server’s scheduled time at any
rate. MinecraftServer.onTickRateChanged recomputes it on every /tick rate
but assigns it only when it is smaller than the ticks left: a slower rate
stretches those ticks, up to five minutes from the change, and a faster one
can only bring the save forward. The server
tick has the rest of
that
loop.
Why is my entities/ folder full of files with nothing in them? Because
nothing deletes a region file once its entries are cleared — and emptying a
chunk costs one write. EntityStorage.storeEntities
with an empty set only writes when EntityStorage.emptyChunks did not
already contain the position, and that write is IOWorker.STORE_EMPTY,
which zeroes the region entry and deletes any sidecar. The first time a
chunk goes empty costs a write. Every later save of it costs nothing.
Where to look
ChunkMap.tick · ChunkMap.processUnloads · ChunkMap.scheduleUnload ·
ChunkMap.save · ChunkMap.saveChunksEagerly · ChunkMap.saveChunkIfNeeded ·
ChunkMap.saveAllChunks · SerializableChunkData.copyOf ·
SerializableChunkData.write · IOWorker.store ·
IOWorker.storePendingChunk · IOWorker.loadAsync ·
RegionFileStorage.write · RegionFile.write · RegionBitmap.allocate ·
PersistentEntitySectionManager.storeChunkSections ·
EntityStorage.storeEntities · SectionStorage.writeChunk ·
SectionStorage.prefetch · ChunkMap.scheduleChunkLoad ·
SerializableChunkData.read · MinecraftServer.computeNextAutosaveInterval ·
DimensionType.getStorageFolder
Next door: tickets and loading raises the level,
chunk anatomy owns what LevelChunkSection.copy copies,
lighting owns the layers the unload throws away, points of
interest owns the poi/ store, the server
tick owns
the budget every method here is handed,
how a server dies is the save that does
not happen, and entity
lifecycle is
what
Entity.RemovalReason.UNLOADED_TO_CHUNK means to a mob.
Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes tools/verify_names.py.