# MinecraftDocs — how Java Minecraft works
One file, every page, in reading order. Source: https://minecraftdocs.dev
---
# Introduction
> Verified against **Minecraft 26.2**
Java Minecraft is one codebase that runs as two programs. The **server** is
the world: a loop that ticks twenty times a second, owns every chunk, entity
and block, and is the only copy allowed to decide what they are. The **client** is a
window, a loop that draws a frame as often as it can, and a copy of the
world it is *told about* — which it will happily change on its own and be
overruled about. They talk over a real Netty connection even when
both run in the same process — in singleplayer the connection never touches
a socket, but the packets are real, and an `IntegratedServer` is a
`MinecraftServer` with the client half attached rather than absent. Almost
everything a player experiences is a consequence of that split: the
client predicts and the server overrules; a chunk exists on the server long
before the client is sent it; a sword swing is a packet, a hit is a reply.
```mermaid
flowchart LR
subgraph Client["the client (Render thread)"]
MC["Minecraft: a frame, and 0 to 10 ticks inside it"]
CL["ClientLevel: the copy of the world"]
MC --> CL
end
subgraph Wire["the wire"]
Conn["Connection: Netty, a socket or an in-process channel"]
end
subgraph Server["the server (Server thread)"]
MS["MinecraftServer: a tick every 50 ms"]
SL["ServerLevel: the world, one per dimension"]
MS --> SL
end
CL -- "serverbound: what the player did" --> Conn
Conn -- "clientbound: what the world became" --> CL
Conn --> SL
SL --> Conn
Worker["Worker-Main-n: chunk generation, lighting, meshing"] -.-> SL
Worker -.-> CL
```
The whole thing is 7,055 classes and about 720,000 lines of Java 25. Just
under a third of it is client-only; the rest ships in both jars, and the
picture below is the split — orange is the client's, blue is everything the
dedicated server also runs, and the hatched boxes are the corners this book
leaves out.
*(figure: packages-treemap.svg — a generated SVG, not reproduced here)*
The two jars. Every box is a package, its area is lines of decompiled source; the atlas walks through it. Click to enlarge.
Four threads carry nearly all of it — the Render thread, which is also the
client's game thread; the Server thread; the Netty event loop; and a shared
worker pool — and the first lecture of the book,
[Anatomy](systems/anatomy/anatomy.md), is those four threads and the two
loops.
## How the book is read
The site is a book in three tiers, and the sidebar is its table of
contents.
**Parts** are watched in order. Thirteen of them, I to XIII, each a system
— the server tick, the world, blocks, entities, the player, networking, the
client, rendering, world generation, commands — and each opening on a
landing page that says what shape the part is, what it assumes from earlier
parts, and which of its pages to watch in which order. A page is one
lecture's notes: it follows one scenario through the system (a player walks
east across a chunk boundary; a server is clicked in the list) and its
figure is the artefact — a sequence diagram whose lanes are class names, a
state machine, a flowchart of a decision. Every diagram enlarges on click.
Each part only assumes the ones before it, and the
[lecture map](lectures.md) says where that is not quite true. The picture
below is the whole of that: an arrow is *watch before*, the two
dependencies every part shares — Part I for the threads, Part II for codecs
and registries — are left off because their arrows would reach almost every
box, and the two dashed arrows are the only places a part reaches forward,
each cut on purpose rather than solved by reordering.
```mermaid
flowchart TB
P1["I · Anatomy"]
P2["II · Foundations"]
P3["III · The server"]
P4["IV · The world"]
P5["V · Blocks"]
P6["VI · Entities"]
P7["VII · Items and inventories"]
P8["VIII · The player"]
P9["IX · Networking"]
P10["X · The client"]
P11["XI · Rendering"]
P12["XII · World generation"]
P13["XIII · Commands and data packs"]
P1 --> P2 --> P3
P3 --> P4
P3 --> P5
P3 --> P6
P3 --> P7
P3 --> P8
P3 --> P9
P3 --> P13
P4 --> P5
P4 --> P6
P4 --> P11
P4 --> P12
P5 --> P6
P5 --> P7
P5 --> P10
P6 --> P8
P6 --> P9
P6 --> P10
P7 --> P8
P7 --> P13
P9 --> P10
P9 --> P13
P10 --> P11
P4 -. "tickets and loading, environment attributes" .-> P3
P10 -. "prediction and acknowledgement, cut at Part V" .-> P5
```
**Maps** are looked at once. The [atlas](maps/README.md) is four views
generated from the decompile on every deploy — [where the code
is](maps/packages.md), [where the mass is](maps/biggest.md), [what
everything imports](maps/fanin.md) and [what extends
what](maps/hierarchy.md) — each with a page of prose and the table it was
drawn from. It is the "where is everything" answer a newcomer wants before
any system page makes sense.
**Reference** is looked up. Every packet, registry, data component, game
rule and thread, the coordinate spaces and the random sources, the
[glossary](reference/glossary.md), the [naming drift](reference/naming-drift.md)
table, a class index that answers "which page talks about `ChunkMap`", and
the [diagram lanes](reference/lanes.md). The rule for what
belongs here is *would a viewer pause the video to read this*.
For agents, the whole site is one file:
[llms-full.txt](https://minecraftdocs.dev/llms-full.txt), regenerated on
every deploy.
## The rules the book keeps
**Names, never code.** A page names classes, methods, fields and packages so
that anyone with the decompiled source can find them in a minute, and
explains what they own, when they run and how they interact. It never
reproduces the source — not a method body, not a snippet. Anyone who needs
the code decompiles the game themselves, which is also the line Mojang's
mappings licence draws.
**Mojang's names.** Every identifier is Mojang's official mapping, which the
decompile uses. Fabric's Yarn names differ; where a modder would not
recognise a class under its official name, the Yarn name is noted once.
Names have moved since 1.21 — `Identifier` was *ResourceLocation*,
`Lightmap` was *LightTexture*, `DeltaTracker` was *Timer* — and the
[naming drift](reference/naming-drift.md) table is the list.
**Newest version only.** Every page says what it was verified against, and
it is one version: 26.2. There are no version-difference sections and no
"in 1.x this was". When a release lands, every page is re-read against it.
**Verified means tested.** Every backticked name on every page is checked
against the decompile before the site publishes, every diagram is parsed
by the same mermaid the site ships, every lane in every diagram is
checked against the one [key](reference/lanes.md) the whole book uses, and
every link and anchor between pages is checked to land. A
page that fails any of those does not go up, and neither does a change
that puts the landing pages, the lecture map and the dependency figure out
of step with each other. That is a narrow
guarantee, and it is worth stating narrowly: it proves the names are real
and current in 26.2, not that the sentence around them is true. The
sentences are what the passes are for.
Every claim has been fact-checked against the decompile twice — once as
drafted, and once again after the pages were restructured into the book you are
reading. Both times, every page had something wrong on it. What is left is a
correction waiting to be filed, and the [repository](https://github.com/AlexanderjFraser/MinecraftDocs)
is where to file it.
**How the system works, not how the code reads.** Object-level: this class
owns that state, this call happens on that thread, this packet is sent
then. Never line-level. Code makes boring video and dates fast.
## What this book skips
Save migration (the `util/datafix` and `util/filefix` trees, which are
version-difference code by definition), Realms, telemetry, the profiler, the
management server, RCON, the data generators, statistics and the recipe book,
the OpenAL audio backend and two packages nobody will
recognise are all in the jar and not in the parts.
[What this book skips](systems/anatomy/what-this-book-skips.md), the closing
page of Part I, draws that boundary honestly — what each thing is, how big, whether the dedicated
server ships it, and the two or three class names to start at if you need
it anyway — so a viewer knows the edge of the map before investing in
thirteen parts.
## Unofficial, and free to reuse
This is an independent description of how the game works. It is not
endorsed by, sponsored by or associated with Mojang Studios or Microsoft,
and *Minecraft* is a trademark of Mojang Synergies AB.
The book is [CC BY-SA
4.0](https://creativecommons.org/licenses/by-sa/4.0/): take it, adapt it,
teach from it — credit [minecraftdocs.dev](https://minecraftdocs.dev) and
keep what you build under the same licence. That covers the writing and the
figures, which are the only things here that are anyone's to license. It
does not cover the game, its source, its assets or Mojang's mappings; none
of those are in this book, which is why it names identifiers and never
reproduces code. Corrections, and the repository the book is written in,
are on [GitHub](https://github.com/AlexanderjFraser/MinecraftDocs).
---
# The atlas
> Verified against **Minecraft 26.2** · Maps · Four views of the whole decompile, drawn by `tools/map_source.py` from the source tree on every deploy.
Before any system page makes sense you want the answer to a newcomer's
question: *where is everything?* The atlas is that answer, looked at once.
Each map is a figure drawn from the decompile, a page of prose saying what
the figure shows, and then the table the figure was drawn from. Nothing here
is hand-counted: the tool reads the 7,055 files, and the pages are
regenerated with the figures each time the site is built, so a number on a
map cannot drift from the source it describes.
## The four maps
| map | the question it answers | the figure |
|---|---|---|
| [Where the code is](packages.md) | how big is each package, which jar ships it, and which parts of the book cover it | the jar as a treemap of packages, area by lines |
| [Where the mass is](biggest.md) | which classes are the largest, and what kind of thing gets that big | the thirty largest classes as bars |
| [What everything imports](fanin.md) | which Mojang classes the rest of the code cannot be written without — the vocabulary Part II teaches | the thirty most-imported Mojang classes as bars |
| [What extends what](hierarchy.md) | which inheritance trees are widest, and what shape they are | four trees with the descendant count on every node |
The treemap is also the book's picture of the *two jars*: the introduction
uses it to show how much of the code the dedicated server ships, and Part
I's *what this book skips* uses its hatching to draw the boundary of the
book.
## How the numbers are counted
The decompile is the **client jar**, which is a strict superset of the
server jar; beside it sits `server-classes.txt`, the list of classes the
dedicated server also ships. From those two things every number on these
pages follows.
| number | how it is counted |
|---|---|
| a class | one `.java` file in the decompile — nested types are not counted as classes, and the 542 four-line `package-info.java` markers are counted like any other file |
| a line | one line of the decompiled file, so counts are comparable with each other and not with Mojang's own source |
| client-only | the file is not listed in `server-classes.txt`; *shared* means it is |
| fan-in | how many files have a *net.minecraft* or *com.mojang* import statement naming the class — the JDK is not counted, and same-package use needs no import |
| descendants | every type reachable from a root through *extends* and *implements*, nested types included; a parent written as *outer dot inner* resolves inside the outer class's own file, so two types with the same simple name stay two types |
The line counts include what the decompiler adds — braces on their own
lines, expanded switches — which is why a table-of-constants class can be
longer than a class that does something. Read length as *where the reading
is*, not where the difficulty is.
---
*Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes `tools/verify_names.py`.*
---
# Where the code is
> Verified against **Minecraft 26.2** · Maps · The jar as a treemap of packages: area is lines of decompiled source, colour is which jar ships the package, hatching is what this book skips.
Java Minecraft is 7,055 classes and 719,302 lines of decompiled Java 25,
and the first surprise in it is which half is bigger. Everything a player
sees — every screen, the HUD, the whole renderer, the entity models, both
GPU back-ends, the sound engine, Realms — is the orange near-third of this
picture. The blue seven tenths ship in both jars, and the biggest box
of all, `world/level`, is a fifth of the game by itself: blocks, block
states, chunks, lighting and world generation, all of it code the
dedicated server runs with no window attached.
*(figure: packages-treemap.svg — a generated SVG, not reproduced here)*
The 26.2 decompile as a treemap. Each outer box is a package directly under net/minecraft or com/mojang, labelled with its share of the lines; the boxes inside are its sub-packages. Hover a box for its counts; click the figure to enlarge it.
## Two jars, one tree
The client jar is a strict superset of the server jar, and the split is
clean: the 2,206 client-only classes live in exactly four packages —
`net/minecraft/client` (1,864 classes), `com/mojang/blaze3d` (211),
`com/mojang/realmsclient` (127) and `net/minecraft/realms` (4). Every other
package in the table below ships in both jars, class for class; no
sub-package at this depth is mixed. The client-only side is 212,242
lines, 29.5% of the total, which is the "just under a third" the
introduction quotes.
The consequence for reading this book: when a page says a class is
*client-only* it is saying which package the class lives in, and when a
page in Part IV or V names a class, the dedicated server has it. The class
index in Reference says which side each named class is on; the oracle
behind it is `server-classes.txt`.
## What the big boxes are
**world — 42%.** `world/level` (146,417 lines, 1,312 classes) is Parts
IV, V and XII: chunks, block states, lighting and, under
`world/level/levelgen`, most of world generation — `world/level/biome` is
its sibling rather than part of it. `world/entity`
(109,061 lines, 716 classes) is Parts VI and VIII: the entity hierarchy,
its AI and the player. `world/item` and `world/inventory` (36,363 lines
between them) are Part VII.
**client — 24%.** `client/gui` (59,057 lines) is the screens and the HUD,
Part X; `client/renderer` and `client/model` (61,108 lines together) are
the frame, section meshing and the entity models, Part XI;
`client/multiplayer` (11,169) holds `ClientPacketListener` and
`ClientLevel`, the client's copy of the world. The 41 classes directly in
`net/minecraft/client` — `Minecraft`, `Options`, `KeyMapping`,
`MouseHandler` — are 10,709 lines on their own.
**util — 7%, two thirds of it skipped.** `util/datafix`, `util/filefix`
and `util/profiling` are 34,176 of the package's 53,275 lines and are all
outside this book; the hatched box is the save-migration history. What is
left is the toolbox every part uses — `Mth`, `RandomSource`, `Util` — and
the parsing and debug packages.
**server — 7%.** `server/commands` (12,781) is Part XIII's command
implementations; `server/level` is 42 classes and 11,977 lines — 285 lines a class, nearly
three times the map's average, in a package small enough to list
(`ServerLevel`, `ChunkMap`, `ServerPlayer`), Parts III and IV; `server/network` is the serverbound
packet handlers, Part IX; `server/packs` is the pack system, Part II;
`server/jsonrpc`, the management server, is skipped.
**blaze3d — 4%.** The GPU abstraction has two back-ends behind
`GpuDevice`, and the Vulkan one (`blaze3d/vulkan`, 7,477 lines) is larger
than the OpenGL one (`blaze3d/opengl`, 5,627).
**network — 3%, in 411 classes.** `network/protocol` is 293 classes in
12,934 lines: the packet catalogue is many tiny classes, one per packet.
`network/chat` (4,818) is `Component` and chat signing.
Below 3% the boxes are Part II's foundations — `net/minecraft/core`,
`net/minecraft/nbt`, `net/minecraft/tags`, `net/minecraft/resources`,
`net/minecraft/commands` (the command source, the argument types and the
execution engine, Part XIII) and `net/minecraft/advancements`, plus
`com/mojang/realmsclient` and the skipped `net/minecraft/data`. That last one
is the program that writes the vanilla data pack — and it is not build-time
only: the dedicated server ships all 163 classes, and `Blocks` and
`MinecraftServer` both read `data/worldgen` constants at run time ([what this
book skips](../systems/anatomy/what-this-book-skips.md) has the three
exceptions).
## Where each part lives
The parts follow the tree, but not one box each. This table is the map
from the book's order to the jar's, and it is the one table on this page
that is a decision rather than a measurement: the packages are assigned by
hand, in the tool that draws the atlas, and the counts follow. A package
under `net/minecraft` is written without that prefix; *itself only* means
the files directly in the package and not its sub-packages; a package two
parts share is counted in both, which is why the total row is more than the
jar. A part's landing page quotes its own row of this table as its size,
and everything the book skips is left out of every row.
| part | packages | classes | client-only | lines |
|---|---|---:|---:|---:|
| I · Anatomy | `client/main`, `Minecraft`, `Main`, `MinecraftServer` | 7 | 5 | 6,770 |
| II · Foundations | `core`, `resources`, `tags`, `nbt`, `server/packs`, `util`, `world/flag` | 453 | 0 | 47,222 |
| III · The server | `server` (itself only), `server/level`, `server/players`, `server/dedicated` | 95 | 0 | 21,779 |
| IV · The world | `world/level/chunk`, `world/level/lighting`, `world/ticks`, `world/level/gameevent`, `world/level/entity`, `world/level/material`, `world/attribute`, `world/timeline`, `world/clock`, `world/level/border`, `server/level` | 218 | 0 | 30,950 |
| V · Blocks | `world/level/block`, `world/level/redstone` | 478 | 0 | 60,403 |
| VI · Entities | `world/entity`, minus `world/entity/player`, `network/syncher`, `world/level/pathfinder`, `world/damagesource`, `world/effect` | 755 | 0 | 110,407 |
| VII · Items and inventories | `world/item`, `world/inventory`, `world/level/storage/loot` | 507 | 0 | 45,289 |
| VIII · The player | `world/entity/player`, `world/food`, `ServerPlayer`, `client/player` | 29 | 9 | 8,135 |
| IX · Networking | `network`, minus `network/syncher`, `server/network`, `client/multiplayer` | 496 | 63 | 39,434 |
| X · The client | `client` (itself only), `client/gui`, `client/multiplayer`, `client/sounds`, `client/resources`, `client/player`, `client/input`, `client/server` | 689 | 689 | 93,640 |
| XI · Rendering | `client/renderer`, `client/model`, `client/particle`, `com/mojang/blaze3d` | 1,254 | 1,254 | 93,012 |
| XII · World generation | `world/level/levelgen`, `world/level/biome` | 451 | 0 | 45,749 |
| XIII · Commands and data packs | `commands`, `server/commands`, `server/dialog`, `server/permissions`, `server/bossevents`, `advancements`, `gametest`, `world/scores`, `client/gui/screens/dialog` | 470 | 17 | 43,126 |
| **the thirteen parts, with the shared packages counted twice** | | 5,902 | 2,037 | 645,916 |
## The table
Depth three is the outer boxes of the treemap; depth four is the inner
ones. *client-only* counts the classes not in `server-classes.txt`.
| package (depth 3) | classes | client-only | lines |
|---|---:|---:|---:|
| `net/minecraft/world` | 2585 | 0 | 304,897 |
| `net/minecraft/client` | 1864 | 1864 | 172,711 |
| `net/minecraft/util` | 717 | 0 | 53,275 |
| `net/minecraft/server` | 420 | 0 | 52,720 |
| `com/mojang/blaze3d` | 211 | 211 | 26,111 |
| `net/minecraft/network` | 411 | 0 | 23,378 |
| `net/minecraft/data` | 163 | 0 | 15,587 |
| `com/mojang/realmsclient` | 127 | 127 | 13,217 |
| `net/minecraft/commands` | 122 | 0 | 13,001 |
| `net/minecraft/core` | 110 | 0 | 11,239 |
| `net/minecraft/nbt` | 43 | 0 | 8,001 |
| `net/minecraft/advancements` | 116 | 0 | 7,735 |
| `net/minecraft/gametest` | 47 | 0 | 5,514 |
| `net/minecraft/sounds` | 6 | 0 | 2,101 |
| `net/minecraft/tags` | 29 | 0 | 1,788 |
| `net/minecraft/resources` | 15 | 0 | 1,764 |
| `net/minecraft` | 19 | 0 | 1,665 |
| `net/minecraft/references` | 5 | 0 | 1,434 |
| `com/mojang/math` | 10 | 0 | 922 |
| `net/minecraft/stats` | 10 | 0 | 873 |
| `net/minecraft/gizmos` | 15 | 0 | 569 |
| `net/minecraft/recipebook` | 3 | 0 | 350 |
| `net/minecraft/locale` | 3 | 0 | 247 |
| `net/minecraft/realms` | 4 | 4 | 203 |
| **total** | 7055 | 2206 | 719,302 |
| package (depth 4) | classes | client-only | lines |
|---|---:|---:|---:|
| `net/minecraft/world/level` | 1312 | 0 | 146,417 |
| `net/minecraft/world/entity` | 716 | 0 | 109,061 |
| `net/minecraft/client/gui` | 444 | 444 | 59,057 |
| `net/minecraft/client/renderer` | 701 | 701 | 48,412 |
| `net/minecraft/world/item` | 314 | 0 | 29,281 |
| `net/minecraft/util/datafix` | 396 | 0 | 26,372 |
| `net/minecraft/network/protocol` | 293 | 0 | 12,934 |
| `net/minecraft/server/commands` | 102 | 0 | 12,781 |
| `net/minecraft/client/model` | 267 | 267 | 12,696 |
| `net/minecraft/util` | 91 | 0 | 12,067 |
| `net/minecraft/server/level` | 42 | 0 | 11,977 |
| `net/minecraft/client/multiplayer` | 63 | 63 | 11,169 |
| `net/minecraft/client` | 41 | 41 | 10,709 |
| `net/minecraft/commands/arguments` | 71 | 0 | 8,847 |
| `net/minecraft/client/resources` | 101 | 101 | 7,612 |
| `net/minecraft/nbt` | 36 | 0 | 7,489 |
| `com/mojang/blaze3d/vulkan` | 40 | 40 | 7,477 |
| `net/minecraft/world/inventory` | 64 | 0 | 7,082 |
| `net/minecraft/client/particle` | 87 | 87 | 6,806 |
| `net/minecraft/client/data` | 28 | 28 | 6,176 |
| `com/mojang/realmsclient/gui` | 40 | 40 | 5,830 |
| `com/mojang/blaze3d/opengl` | 28 | 28 | 5,627 |
| `net/minecraft/gametest/framework` | 45 | 0 | 5,495 |
| `net/minecraft/core` | 38 | 0 | 5,493 |
| `net/minecraft/server/network` | 28 | 0 | 5,409 |
| `net/minecraft/data/worldgen` | 56 | 0 | 5,369 |
| `net/minecraft/server` | 27 | 0 | 5,227 |
| `net/minecraft/server/packs` | 55 | 0 | 4,975 |
| `net/minecraft/network/chat` | 63 | 0 | 4,818 |
| `net/minecraft/util/profiling` | 70 | 0 | 4,260 |
| `net/minecraft/server/jsonrpc` | 65 | 0 | 4,094 |
| `com/mojang/blaze3d/platform` | 29 | 29 | 3,896 |
| `net/minecraft/network` | 42 | 0 | 3,732 |
| `net/minecraft/util/filefix` | 57 | 0 | 3,544 |
| `net/minecraft/advancements/predicates` | 54 | 0 | 3,459 |
| `net/minecraft/world/phys` | 28 | 0 | 3,020 |
| `net/minecraft/data/loot` | 21 | 0 | 2,883 |
| `net/minecraft/advancements/triggers` | 49 | 0 | 2,809 |
| `net/minecraft/server/players` | 19 | 0 | 2,766 |
| `net/minecraft/core/component` | 30 | 0 | 2,668 |
| `com/mojang/realmsclient/client` | 19 | 19 | 2,534 |
| `com/mojang/blaze3d/vertex` | 16 | 16 | 2,420 |
| `com/mojang/blaze3d/systems` | 26 | 26 | 2,308 |
| `net/minecraft/data/recipes` | 17 | 0 | 2,227 |
| `net/minecraft/client/sounds` | 17 | 17 | 2,152 |
| `net/minecraft/sounds` | 6 | 0 | 2,101 |
| `net/minecraft/util/parsing` | 29 | 0 | 1,879 |
| `net/minecraft/commands` | 14 | 0 | 1,873 |
| `com/mojang/realmsclient/dto` | 39 | 39 | 1,866 |
| `net/minecraft/client/player` | 9 | 9 | 1,821 |
| `net/minecraft/server/dedicated` | 7 | 0 | 1,809 |
| `net/minecraft/tags` | 29 | 0 | 1,788 |
| `net/minecraft/resources` | 15 | 0 | 1,764 |
| `net/minecraft/world/attribute` | 28 | 0 | 1,705 |
| `net/minecraft` | 19 | 0 | 1,665 |
| `net/minecraft/world` | 25 | 0 | 1,664 |
| `net/minecraft/data/tags` | 29 | 0 | 1,660 |
| `com/mojang/realmsclient` | 4 | 4 | 1,524 |
| `net/minecraft/advancements` | 13 | 0 | 1,467 |
| `net/minecraft/world/scores` | 16 | 0 | 1,442 |
| `net/minecraft/references` | 5 | 0 | 1,434 |
| `com/mojang/realmsclient/util` | 20 | 20 | 1,399 |
| `net/minecraft/network/codec` | 7 | 0 | 1,372 |
| `net/minecraft/util/debug` | 19 | 0 | 1,370 |
| `net/minecraft/world/effect` | 20 | 0 | 1,306 |
| `net/minecraft/data` | 10 | 0 | 1,264 |
| `net/minecraft/client/telemetry` | 18 | 18 | 1,221 |
| `net/minecraft/core/dispenser` | 14 | 0 | 1,094 |
| `com/mojang/blaze3d/audio` | 12 | 12 | 1,013 |
| `com/mojang/blaze3d/pipeline` | 11 | 11 | 954 |
| `net/minecraft/util/worldupdate` | 6 | 0 | 937 |
| `net/minecraft/world/damagesource` | 12 | 0 | 936 |
| `com/mojang/math` | 10 | 0 | 922 |
| `net/minecraft/client/color` | 18 | 18 | 915 |
| `net/minecraft/commands/synchronization` | 12 | 0 | 895 |
| `net/minecraft/server/dialog` | 35 | 0 | 886 |
| `net/minecraft/data/advancements` | 10 | 0 | 874 |
| `net/minecraft/stats` | 10 | 0 | 873 |
| `net/minecraft/world/ticks` | 14 | 0 | 869 |
| `net/minecraft/core/particles` | 21 | 0 | 860 |
| `net/minecraft/util/thread` | 9 | 0 | 842 |
| `net/minecraft/server/rcon` | 9 | 0 | 839 |
| `net/minecraft/client/server` | 6 | 6 | 838 |
| `net/minecraft/core/registries` | 4 | 0 | 817 |
| `net/minecraft/commands/execution` | 18 | 0 | 756 |
| `net/minecraft/world/waypoints` | 9 | 0 | 677 |
| `net/minecraft/client/tutorial` | 10 | 10 | 671 |
| `net/minecraft/util/valueproviders` | 18 | 0 | 670 |
| `net/minecraft/commands/functions` | 7 | 0 | 630 |
| `net/minecraft/gizmos` | 15 | 0 | 569 |
| `net/minecraft/data/info` | 8 | 0 | 530 |
| `net/minecraft/client/main` | 4 | 4 | 523 |
| `net/minecraft/network/syncher` | 6 | 0 | 522 |
| `net/minecraft/nbt/visitors` | 7 | 0 | 512 |
| `net/minecraft/client/animation` | 23 | 23 | 509 |
| `net/minecraft/client/searchtree` | 8 | 8 | 505 |
| `net/minecraft/data/structures` | 5 | 0 | 469 |
| `com/mojang/blaze3d` | 12 | 12 | 462 |
| `net/minecraft/util/eventlog` | 4 | 0 | 459 |
| `net/minecraft/world/timeline` | 5 | 0 | 447 |
| `com/mojang/blaze3d/framegraph` | 3 | 3 | 437 |
| `com/mojang/blaze3d/font` | 7 | 7 | 432 |
| `net/minecraft/server/permissions` | 12 | 0 | 402 |
| `net/minecraft/util/random` | 4 | 0 | 369 |
| `net/minecraft/world/flag` | 7 | 0 | 356 |
| `net/minecraft/server/chase` | 3 | 0 | 355 |
| `net/minecraft/world/clock` | 10 | 0 | 351 |
| `net/minecraft/recipebook` | 3 | 0 | 350 |
| `net/minecraft/server/bossevents` | 3 | 0 | 328 |
| `com/mojang/blaze3d/buffers` | 6 | 6 | 327 |
| `net/minecraft/server/gui` | 4 | 0 | 310 |
| `net/minecraft/core/cauldron` | 3 | 0 | 307 |
| `net/minecraft/server/notifications` | 5 | 0 | 295 |
| `net/minecraft/client/quickplay` | 3 | 3 | 284 |
| `net/minecraft/world/food` | 5 | 0 | 283 |
| `net/minecraft/client/input` | 8 | 8 | 282 |
| `net/minecraft/data/registries` | 5 | 0 | 248 |
| `net/minecraft/locale` | 3 | 0 | 247 |
| `net/minecraft/client/entity` | 4 | 4 | 228 |
| `net/minecraft/realms` | 4 | 4 | 203 |
| `net/minecraft/util/debugchart` | 8 | 0 | 199 |
| `com/mojang/blaze3d/util` | 2 | 2 | 189 |
| `net/minecraft/util/context` | 4 | 0 | 187 |
| `com/mojang/blaze3d/resource` | 6 | 6 | 185 |
| `net/minecraft/server/waypoints` | 2 | 0 | 180 |
| `com/mojang/blaze3d/preprocessor` | 2 | 2 | 164 |
| `com/mojang/blaze3d/textures` | 6 | 6 | 150 |
| `net/minecraft/util/monitoring` | 2 | 0 | 120 |
| `net/minecraft/server/advancements` | 2 | 0 | 87 |
| `net/minecraft/client/profiling` | 2 | 2 | 81 |
| `com/mojang/blaze3d/shaders` | 5 | 5 | 70 |
| `com/mojang/realmsclient/exception` | 5 | 5 | 64 |
| `net/minecraft/data/metadata` | 2 | 0 | 63 |
| `net/minecraft/client/waypoints` | 2 | 2 | 44 |
| `net/minecraft/gametest` | 2 | 0 | 19 |
| **total** | 7055 | 2206 | 719,302 |
---
*Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes `tools/verify_names.py`.*
---
# Where the mass is
> Verified against **Minecraft 26.2** · Maps · The thirty largest classes by lines of decompiled source, coloured by which jar ships them.
The two largest classes in the game are the top of one hierarchy: `Entity`
and `LivingEntity`, 8,785 lines between them, the thing every mob and
player is before it is anything else. Third is `Minecraft`, the client
itself. And then the list turns odd: two of the top ten never run while
anyone is playing. `BlockModelGenerators` runs only inside the data
generator — its single caller is `ModelProvider` — and writes the block
model JSON that ships in the jar; `BlockStateData` is a table that the
save-migration fixes consult when a world from an older version is opened,
and nothing outside `util/datafix` reads it. Size is where the reading is,
not where the game is.
*(figure: biggest.svg — a generated SVG on the site; its data follows)*
| class | lines | side |
|---|---:|---|
| `net/minecraft/world/entity/Entity` | 4,464 | shared |
| `net/minecraft/world/entity/LivingEntity` | 4,321 | shared |
| `net/minecraft/client/Minecraft` | 3,274 | client |
| `net/minecraft/client/data/models/BlockModelGenerators` | 3,090 | client |
| `net/minecraft/client/multiplayer/ClientPacketListener` | 3,051 | client |
| `net/minecraft/server/MinecraftServer` | 2,632 | shared |
| `net/minecraft/server/network/ServerGamePacketListenerImpl` | 2,499 | shared |
| `net/minecraft/server/level/ServerPlayer` | 2,445 | shared |
| `net/minecraft/util/datafix/fixes/BlockStateData` | 2,270 | shared |
| `net/minecraft/server/level/ServerLevel` | 2,126 | shared |
| `net/minecraft/world/entity/player/Player` | 2,053 | shared |
| `net/minecraft/sounds/SoundEvents` | 2,000 | shared |
| `net/minecraft/world/level/levelgen/structure/structures/OceanMonumentPieces` | 1,983 | shared |
| `net/minecraft/client/Options` | 1,972 | client |
| `net/minecraft/world/level/block/Blocks` | 1,969 | shared |
| `net/minecraft/world/item/CreativeModeTabs` | 1,706 | shared |
| `net/minecraft/world/item/Items` | 1,694 | shared |
| `net/minecraft/server/level/ChunkMap` | 1,668 | shared |
| `net/minecraft/world/entity/animal/fox/Fox` | 1,625 | shared |
| `net/minecraft/world/entity/Mob` | 1,612 | shared |
| `net/minecraft/util/datafix/DataFixers` | 1,582 | shared |
| `net/minecraft/network/FriendlyByteBuf` | 1,546 | shared |
| `net/minecraft/client/gui/Hud` | 1,478 | client |
| `net/minecraft/world/entity/animal/bee/Bee` | 1,451 | shared |
| `net/minecraft/world/level/levelgen/DensityFunctions` | 1,445 | shared |
| `net/minecraft/world/level/levelgen/structure/structures/StrongholdPieces` | 1,436 | shared |
| `net/minecraft/util/Util` | 1,415 | shared |
| `net/minecraft/client/player/LocalPlayer` | 1,392 | client |
| `com/mojang/realmsclient/RealmsMainScreen` | 1,376 | client |
| `net/minecraft/data/loot/packs/VanillaBlockLoot` | 1,360 | shared |
| `net/minecraft/world/level/block/state/BlockBehaviour` | 1,357 | shared |
| `net/minecraft/gametest/framework/GameTestHelper` | 1,353 | shared |
| `net/minecraft/client/multiplayer/ClientLevel` | 1,320 | client |
| `net/minecraft/world/level/levelgen/structure/structures/NetherFortressPieces` | 1,317 | shared |
| `net/minecraft/world/item/ItemStack` | 1,253 | shared |
| `net/minecraft/world/level/levelgen/structure/structures/WoodlandMansionPieces` | 1,185 | shared |
| `net/minecraft/world/entity/animal/panda/Panda` | 1,121 | shared |
| `net/minecraft/world/entity/animal/equine/AbstractHorse` | 1,114 | shared |
| `net/minecraft/client/gui/screens/inventory/CreativeModeInventoryScreen` | 1,101 | client |
| `net/minecraft/world/entity/monster/cubemob/SulfurCube` | 1,082 | shared |
The thirty largest classes of 26.2. Blue ships in both jars, orange is client-only; the small grey text is the package under net/minecraft. Click to enlarge.
**The number:** 62,935 — the lines in these thirty classes, 8.7% of the
game in 0.4% of its files.
## Three kinds of big
Read down the bars and the thirty sort themselves into three kinds, and
the kind tells you how to read the class.
**The god objects.** `Entity`, `LivingEntity`, `Player`, `ServerPlayer`,
`LocalPlayer` and `Mob` are one chain of inheritance, and each level adds
a thousand lines or more because each is the base of everything below it.
`Minecraft` and `MinecraftServer` are the two programs' roots; `ServerLevel`
is the world; `ChunkMap` is the world's loader. Only two concrete mobs make
the list — `Fox` and `Bee`, the two with the most bespoke behaviour — and
they are the pages a reader of Part VI should expect to be long.
**The switchboards.** `ClientPacketListener` is a handler method for every
clientbound play packet, and `ServerGamePacketListenerImpl` is the same for
every serverbound one — the packets both phases share are handled one class
up, in `ClientCommonPacketListenerImpl` and `ServerCommonPacketListenerImpl`; between them they are the whole of what the wire
can say, which is why Part IX's pages keep coming back to them.
`FriendlyByteBuf` is the buffer both read from.
**The catalogues written as code.** `SoundEvents`, `Blocks`, `Items` and
`CreativeModeTabs` are registries populated one constant per line;
`DensityFunctions` is the node types the vanilla noise graph is built from —
the graph itself is `NoiseRouterData` and the worldgen JSON; `DataFixers` is the
migration history; `OceanMonumentPieces` and `StrongholdPieces` are
structures built by hand, room by room, in Java rather than in a template;
`Options` is every setting the client has, and `Hud` is the in-world overlay
— the crosshair, the hotbar, the bars — inside the wider `Gui` that also
draws screens, toasts and the loading overlay. These are long because they are lists. None is hard.
## The table
Forty rows, of which the figure draws thirty. *side* is which jar ships
the class.
| class | lines | side |
|---|---:|---|
| `net/minecraft/world/entity/Entity` | 4,464 | shared |
| `net/minecraft/world/entity/LivingEntity` | 4,321 | shared |
| `net/minecraft/client/Minecraft` | 3,274 | client |
| `net/minecraft/client/data/models/BlockModelGenerators` | 3,090 | client |
| `net/minecraft/client/multiplayer/ClientPacketListener` | 3,051 | client |
| `net/minecraft/server/MinecraftServer` | 2,632 | shared |
| `net/minecraft/server/network/ServerGamePacketListenerImpl` | 2,499 | shared |
| `net/minecraft/server/level/ServerPlayer` | 2,445 | shared |
| `net/minecraft/util/datafix/fixes/BlockStateData` | 2,270 | shared |
| `net/minecraft/server/level/ServerLevel` | 2,126 | shared |
| `net/minecraft/world/entity/player/Player` | 2,053 | shared |
| `net/minecraft/sounds/SoundEvents` | 2,000 | shared |
| `net/minecraft/world/level/levelgen/structure/structures/OceanMonumentPieces` | 1,983 | shared |
| `net/minecraft/client/Options` | 1,972 | client |
| `net/minecraft/world/level/block/Blocks` | 1,969 | shared |
| `net/minecraft/world/item/CreativeModeTabs` | 1,706 | shared |
| `net/minecraft/world/item/Items` | 1,694 | shared |
| `net/minecraft/server/level/ChunkMap` | 1,668 | shared |
| `net/minecraft/world/entity/animal/fox/Fox` | 1,625 | shared |
| `net/minecraft/world/entity/Mob` | 1,612 | shared |
| `net/minecraft/util/datafix/DataFixers` | 1,582 | shared |
| `net/minecraft/network/FriendlyByteBuf` | 1,546 | shared |
| `net/minecraft/client/gui/Hud` | 1,478 | client |
| `net/minecraft/world/entity/animal/bee/Bee` | 1,451 | shared |
| `net/minecraft/world/level/levelgen/DensityFunctions` | 1,445 | shared |
| `net/minecraft/world/level/levelgen/structure/structures/StrongholdPieces` | 1,436 | shared |
| `net/minecraft/util/Util` | 1,415 | shared |
| `net/minecraft/client/player/LocalPlayer` | 1,392 | client |
| `com/mojang/realmsclient/RealmsMainScreen` | 1,376 | client |
| `net/minecraft/data/loot/packs/VanillaBlockLoot` | 1,360 | shared |
| `net/minecraft/world/level/block/state/BlockBehaviour` | 1,357 | shared |
| `net/minecraft/gametest/framework/GameTestHelper` | 1,353 | shared |
| `net/minecraft/client/multiplayer/ClientLevel` | 1,320 | client |
| `net/minecraft/world/level/levelgen/structure/structures/NetherFortressPieces` | 1,317 | shared |
| `net/minecraft/world/item/ItemStack` | 1,253 | shared |
| `net/minecraft/world/level/levelgen/structure/structures/WoodlandMansionPieces` | 1,185 | shared |
| `net/minecraft/world/entity/animal/panda/Panda` | 1,121 | shared |
| `net/minecraft/world/entity/animal/equine/AbstractHorse` | 1,114 | shared |
| `net/minecraft/client/gui/screens/inventory/CreativeModeInventoryScreen` | 1,101 | client |
| `net/minecraft/world/entity/monster/cubemob/SulfurCube` | 1,082 | shared |
---
*Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes `tools/verify_names.py`.*
---
# What everything imports
> Verified against **Minecraft 26.2** · Maps · The thirty most-imported Mojang classes: how many files name each one in an import statement.
One file in six imports `BlockPos`. That is the least surprising fact on
this page; the next one is not. The chart counts Mojang imports only —
*net.minecraft* and *com.mojang* — because the JDK's `List` and `Optional`
and the nullability annotation outrank everything on it and say nothing
about the game. Under that rule the second most-imported type is not
Minecraft's: it is `Codec`, from Mojang's DataFixerUpper
library, imported by 987 files, with `MapCodec` third and
`RecordCodecBuilder` sixth. Three of the six classes the game most depends
on are the serialisation vocabulary that turns objects into NBT and JSON
and back, which is why Part II teaches codecs before anything a player can
see.
*(figure: fanin.svg — a generated SVG on the site; its data follows)*
| class | package | imported by | side |
|---|---|---:|---|
| `BlockPos` | `net.minecraft.core` | 1221 | shared |
| `Codec` | `com.mojang.serialization` | 987 | library |
| `MapCodec` | `com.mojang.serialization` | 917 | library |
| `Identifier` | `net.minecraft.resources` | 882 | shared |
| `BlockState` | `net.minecraft.world.level.block.state` | 862 | shared |
| `RecordCodecBuilder` | `com.mojang.serialization.codecs` | 804 | library |
| `RandomSource` | `net.minecraft.util` | 773 | shared |
| `Component` | `net.minecraft.network.chat` | 766 | shared |
| `Level` | `net.minecraft.world.level` | 750 | shared |
| `ServerLevel` | `net.minecraft.server.level` | 726 | shared |
| `ItemStack` | `net.minecraft.world.item` | 682 | shared |
| `Mth` | `net.minecraft.util` | 677 | shared |
| `Entity` | `net.minecraft.world.entity` | 667 | shared |
| `Holder` | `net.minecraft.core` | 597 | shared |
| `Vec3` | `net.minecraft.world.phys` | 575 | shared |
| `Direction` | `net.minecraft.core` | 570 | shared |
| `LivingEntity` | `net.minecraft.world.entity` | 533 | shared |
| `StreamCodec` | `net.minecraft.network.codec` | 508 | shared |
| `LogUtils` | `com.mojang.logging` | 470 | library |
| `Util` | `net.minecraft.util` | 454 | shared |
| `Player` | `net.minecraft.world.entity.player` | 449 | shared |
| `Registries` | `net.minecraft.core.registries` | 448 | shared |
| `ResourceKey` | `net.minecraft.resources` | 402 | shared |
| `Schema` | `com.mojang.datafixers.schemas` | 389 | library |
| `SoundEvents` | `net.minecraft.sounds` | 316 | shared |
| `BlockBehaviour` | `net.minecraft.world.level.block.state` | 311 | shared |
| `Blocks` | `net.minecraft.world.level.block` | 310 | shared |
| `Packet` | `net.minecraft.network.protocol` | 287 | shared |
| `Minecraft` | `net.minecraft.client` | 280 | client |
| `DSL` | `com.mojang.datafixers` | 278 | library |
| `ByteBufCodecs` | `net.minecraft.network.codec` | 275 | shared |
| `EntityType` | `net.minecraft.world.entity` | 259 | shared |
| `Block` | `net.minecraft.world.level.block` | 257 | shared |
| `BuiltInRegistries` | `net.minecraft.core.registries` | 257 | shared |
| `DataComponents` | `net.minecraft.core.component` | 253 | shared |
| `PacketType` | `net.minecraft.network.protocol` | 241 | shared |
| `SoundEvent` | `net.minecraft.sounds` | 232 | shared |
| `RegistryFriendlyByteBuf` | `net.minecraft.network` | 229 | shared |
| `Dynamic` | `com.mojang.serialization` | 229 | library |
| `ServerPlayer` | `net.minecraft.server.level` | 223 | shared |
| `LevelReader` | `net.minecraft.world.level` | 220 | shared |
| `BlockGetter` | `net.minecraft.world.level` | 217 | shared |
| `Items` | `net.minecraft.world.item` | 216 | shared |
| `SoundSource` | `net.minecraft.sounds` | 210 | shared |
| `Pair` | `com.mojang.datafixers.util` | 208 | library |
| `VoxelShape` | `net.minecraft.world.phys.shapes` | 206 | shared |
| `Item` | `net.minecraft.world.item` | 204 | shared |
| `EntityTypes` | `net.minecraft.world.entity` | 204 | shared |
| `FriendlyByteBuf` | `net.minecraft.network` | 202 | shared |
| `GuiGraphicsExtractor` | `net.minecraft.client.gui` | 195 | client |
| `Registry` | `net.minecraft.core` | 192 | shared |
| `ModelPart` | `net.minecraft.client.model.geom` | 191 | client |
| `PoseStack` | `com.mojang.blaze3d.vertex` | 191 | client |
| `ExtraCodecs` | `net.minecraft.util` | 189 | shared |
| `InteractionResult` | `net.minecraft.world` | 188 | shared |
| `ValueOutput` | `net.minecraft.world.level.storage` | 184 | shared |
| `BlockTags` | `net.minecraft.tags` | 183 | shared |
| `ValueInput` | `net.minecraft.world.level.storage` | 183 | shared |
| `DamageSource` | `net.minecraft.world.damagesource` | 182 | shared |
| `MemoryModuleType` | `net.minecraft.world.entity.ai.memory` | 182 | shared |
The thirty most-imported Mojang classes of 26.2; JDK and annotation imports are not counted. Blue ships in both jars, orange is client-only, grey is a library outside the decompile. Click to enlarge.
## The vocabulary Part II teaches
The thirty hubs are not thirty ideas; they are seven, and Part II is the
first six of them. The world's nouns are the seventh, and the rest of the
book is about them.
| idea | the hubs | where the book teaches it |
|---|---|---|
| a position | `BlockPos`, `Vec3`, `Direction`, `Mth` | [Math and primitives](../reference/math-and-primitives.md) |
| a name and a registry | `Identifier`, `ResourceKey`, `Registries`, `BuiltInRegistries`, `Holder` | [Identifiers and registries](../systems/foundations/identifiers-and-registries.md) |
| a shape on disk | `Codec`, `MapCodec`, `RecordCodecBuilder` | [Codecs, NBT and JSON](../systems/foundations/codecs-nbt-json.md) |
| a shape on the wire | `StreamCodec`, `ByteBufCodecs`, `RegistryFriendlyByteBuf`, `Packet`, `PacketType` | [Packets and stream codecs](../systems/networking/packets-and-stream-codecs.md) |
| text | `Component` | [Text components](../systems/foundations/text-components.md) |
| chance | `RandomSource` | [Math and primitives](../reference/math-and-primitives.md) |
| the world's nouns | `Level`, `ServerLevel`, `BlockState`, `Block`, `Blocks`, `Entity`, `LivingEntity`, `Player`, `EntityType`, `ItemStack`, `SoundEvents`, `SoundEvent`, `DataComponents` | Parts IV to VIII |
Three rows of the table are worth a second look. `ServerLevel` (726) is
imported by nearly as many files as `Level` (750): most code that touches
the world knows it is on the server, and says so in its types. `Minecraft`
(280) is the only client-only class in the thirty, and it is twenty-ninth —
the client's hub is a hub for a quarter of the code, and the shared three
quarters never name it. And `Schema` (389) and `DSL` (278), the other
library classes on the chart, are the migration tree talking to itself:
all but ten of the files that import `Schema` are in `util/datafix`.
*LogUtils*, at 470, is Mojang's logging library — the one line at the top
of nearly every class that does anything.
## What the count misses
An import is counted once per file, so the chart says *how many files
name the class*, not how often. It also undercounts every class used
inside its own package, because that needs no import: the 257 files that
import `Block` are the files outside `world/level/block` that use it, and
the same is true of `BlockBehaviour` and `Minecraft`. A class's true
reach is this number plus its package.
## The table
Sixty rows, of which the figure draws thirty. *side* is which jar ships
the class, or *library* for a class outside the decompile.
| class | package | imported by | side |
|---|---|---:|---|
| `BlockPos` | `net.minecraft.core` | 1221 | shared |
| `Codec` | `com.mojang.serialization` | 987 | library |
| `MapCodec` | `com.mojang.serialization` | 917 | library |
| `Identifier` | `net.minecraft.resources` | 882 | shared |
| `BlockState` | `net.minecraft.world.level.block.state` | 862 | shared |
| `RecordCodecBuilder` | `com.mojang.serialization.codecs` | 804 | library |
| `RandomSource` | `net.minecraft.util` | 773 | shared |
| `Component` | `net.minecraft.network.chat` | 766 | shared |
| `Level` | `net.minecraft.world.level` | 750 | shared |
| `ServerLevel` | `net.minecraft.server.level` | 726 | shared |
| `ItemStack` | `net.minecraft.world.item` | 682 | shared |
| `Mth` | `net.minecraft.util` | 677 | shared |
| `Entity` | `net.minecraft.world.entity` | 667 | shared |
| `Holder` | `net.minecraft.core` | 597 | shared |
| `Vec3` | `net.minecraft.world.phys` | 575 | shared |
| `Direction` | `net.minecraft.core` | 570 | shared |
| `LivingEntity` | `net.minecraft.world.entity` | 533 | shared |
| `StreamCodec` | `net.minecraft.network.codec` | 508 | shared |
| `LogUtils` | `com.mojang.logging` | 470 | library |
| `Util` | `net.minecraft.util` | 454 | shared |
| `Player` | `net.minecraft.world.entity.player` | 449 | shared |
| `Registries` | `net.minecraft.core.registries` | 448 | shared |
| `ResourceKey` | `net.minecraft.resources` | 402 | shared |
| `Schema` | `com.mojang.datafixers.schemas` | 389 | library |
| `SoundEvents` | `net.minecraft.sounds` | 316 | shared |
| `BlockBehaviour` | `net.minecraft.world.level.block.state` | 311 | shared |
| `Blocks` | `net.minecraft.world.level.block` | 310 | shared |
| `Packet` | `net.minecraft.network.protocol` | 287 | shared |
| `Minecraft` | `net.minecraft.client` | 280 | client |
| `DSL` | `com.mojang.datafixers` | 278 | library |
| `ByteBufCodecs` | `net.minecraft.network.codec` | 275 | shared |
| `EntityType` | `net.minecraft.world.entity` | 259 | shared |
| `Block` | `net.minecraft.world.level.block` | 257 | shared |
| `BuiltInRegistries` | `net.minecraft.core.registries` | 257 | shared |
| `DataComponents` | `net.minecraft.core.component` | 253 | shared |
| `PacketType` | `net.minecraft.network.protocol` | 241 | shared |
| `SoundEvent` | `net.minecraft.sounds` | 232 | shared |
| `RegistryFriendlyByteBuf` | `net.minecraft.network` | 229 | shared |
| `Dynamic` | `com.mojang.serialization` | 229 | library |
| `ServerPlayer` | `net.minecraft.server.level` | 223 | shared |
| `LevelReader` | `net.minecraft.world.level` | 220 | shared |
| `BlockGetter` | `net.minecraft.world.level` | 217 | shared |
| `Items` | `net.minecraft.world.item` | 216 | shared |
| `SoundSource` | `net.minecraft.sounds` | 210 | shared |
| `Pair` | `com.mojang.datafixers.util` | 208 | library |
| `VoxelShape` | `net.minecraft.world.phys.shapes` | 206 | shared |
| `Item` | `net.minecraft.world.item` | 204 | shared |
| `EntityTypes` | `net.minecraft.world.entity` | 204 | shared |
| `FriendlyByteBuf` | `net.minecraft.network` | 202 | shared |
| `GuiGraphicsExtractor` | `net.minecraft.client.gui` | 195 | client |
| `Registry` | `net.minecraft.core` | 192 | shared |
| `ModelPart` | `net.minecraft.client.model.geom` | 191 | client |
| `PoseStack` | `com.mojang.blaze3d.vertex` | 191 | client |
| `ExtraCodecs` | `net.minecraft.util` | 189 | shared |
| `InteractionResult` | `net.minecraft.world` | 188 | shared |
| `ValueOutput` | `net.minecraft.world.level.storage` | 184 | shared |
| `BlockTags` | `net.minecraft.tags` | 183 | shared |
| `ValueInput` | `net.minecraft.world.level.storage` | 183 | shared |
| `DamageSource` | `net.minecraft.world.damagesource` | 182 | shared |
| `MemoryModuleType` | `net.minecraft.world.entity.ai.memory` | 182 | shared |
---
*Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes `tools/verify_names.py`.*
---
# What extends what
> Verified against **Minecraft 26.2** · Maps · The widest inheritance trees, four of them drawn with the number of descendants on every node.
`Block` has 293 subclasses and `Entity` 191, and those are the two trees a
reader of this book will climb most often. But neither is the widest
hierarchy in the game. That is `FeatureElement`, with 386 descendants from
seven implementers — and it is not a hierarchy at all. It is a marker
interface for *anything that can be put behind a feature flag*, and its
seven implementers are `BlockBehaviour`, `Item`, `EntityType`, `MenuType`,
`MobEffect`, `Potion` and `GameRule`, so it inherits the whole block tree
and the whole item tree at once. `ItemLike`, second at 366, is `Block` plus
`Item`. The interface table is a list of which mix-ins reach furthest; the
class table is where the real trees are, and four of them are drawn below.
## Entity
*(figure: tree-Entity.svg — a generated SVG, not reproduced here)*
The Entity tree to three levels. The number is how many types descend from the node; subclasses with no subclasses of their own are folded into one italic line per parent. Click to enlarge.
The shape is a spine with a few branches. `LivingEntity` holds 124 of the
191, `Mob` 114 of those, `PathfinderMob` 108 of those: a mob is four
classes deep before it is a species, and Part VI's
[entity anatomy](../systems/entities/entity-anatomy.md) is that spine.
The non-living entities are two families and a scattering — `Projectile`
(26) and `VehicleEntity` (15), then hanging things, displays, and
thirteen direct subclasses of `Entity` with no children of their own, from
`ItemEntity` to `LightningBolt`.
## Block
*(figure: tree-Block.svg — a generated SVG, not reproduced here)*
The Block tree to three levels; 61 of its 92 direct subclasses have no subclasses of their own and are folded into the last line. Click to enlarge.
`Block` is wide and shallow: 92 direct subclasses, most of them terminal.
The one deep branch is `BaseEntityBlock` (64), the blocks that own a
block entity, which is Part V's
[block entities](../systems/blocks/block-entities.md) page in tree form.
The table's first row, `BlockBehaviour` at 294, is the same tree seen from
one class higher — `Block` is its only subclass, and it exists so that a
block's behaviour and its registry identity can be separate classes.
## Item
*(figure: tree-Item.svg — a generated SVG, not reproduced here)*
The Item tree to three levels. Click to enlarge.
Seventy-one subclasses for over a thousand registered items. The tree is
small because an item's behaviour mostly is not in its class: what a stack
does is in its data components, and `Items` registers most of the game
as a plain `Item` with a `Item.Properties` describing it. Part VII's
[items and stacks](../systems/items/items-and-stacks.md) is why the tree
is this shape.
## Screen
*(figure: tree-Screen.svg — a generated SVG, not reproduced here)*
The Screen tree to three levels; 60 of its 72 direct subclasses have no subclasses of their own. Click to enlarge.
`Screen` (158) is `Block`'s shape again — 72 direct subclasses, 60 of
them terminal — with one deep branch, `AbstractContainerScreen` (27), the
screens that show a menu, and one branch that is not in this book,
`RealmsScreen` (23). The row above it in the table,
`AbstractContainerEventHandler` at 159, is `Screen`'s parent with `Screen`
as its only subclass, the same one-class-higher effect as
`BlockBehaviour`.
## Two trees the table shows and the figures do not
`Goal` has 200 descendants from 99 direct subclasses, and 130 of the 200
are nested classes inside the mob they serve — a fox's goals are declared
in `Fox`, not in `world/entity/ai/goal`. `Packet` is an interface with 236 descendants from 227
direct implementers, and almost nothing below them: the packet catalogue is
flat, and the [packets](../reference/packets.md) reference is its list.
## The tables
Class roots first, then interface roots; a root needs fifteen descendants
to appear. *direct* is the number of immediate subclasses or implementers;
*where* is the package of the root.
| root | descendants | direct | kind | where |
|---|---:|---:|---|---|
| `BlockBehaviour` | 294 | 1 | class | `net/minecraft/world/level/block/state` |
| `Block` | 293 | 92 | class | `net/minecraft/world/level/block` |
| `Goal` | 200 | 99 | class | `net/minecraft/world/entity/ai/goal` |
| `Entity` | 191 | 18 | class | `net/minecraft/world/entity` |
| `Model` | 170 | 14 | class | `net/minecraft/client/model` |
| `AbstractContainerEventHandler` | 159 | 1 | class | `net/minecraft/client/gui/components/events` |
| `Screen` | 158 | 72 | class | `net/minecraft/client/gui/screens` |
| `EntityModel` | 153 | 70 | class | `net/minecraft/client/model` |
| `EntityRenderer` | 132 | 27 | class | `net/minecraft/client/renderer/entity` |
| `LivingEntity` | 124 | 3 | class | `net/minecraft/world/entity` |
| `Mob` | 114 | 5 | class | `net/minecraft/world/entity` |
| `PathfinderMob` | 108 | 5 | class | `net/minecraft/world/entity` |
| `NamespacedSchema` | 103 | 103 | class | `net/minecraft/util/datafix/schemas` |
| `EntityRenderState` | 98 | 22 | class | `net/minecraft/client/renderer/entity/state` |
| `LivingEntityRenderer` | 96 | 3 | class | `net/minecraft/client/renderer/entity` |
| `AbstractWidget` | 95 | 12 | class | `net/minecraft/client/gui/components` |
| `MobRenderer` | 93 | 39 | class | `net/minecraft/client/renderer/entity` |
| `Particle` | 83 | 4 | class | `net/minecraft/client/particle` |
| `SingleQuadParticle` | 73 | 46 | class | `net/minecraft/client/particle` |
| `Item` | 71 | 51 | class | `net/minecraft/world/item` |
| `LivingEntityRenderState` | 70 | 44 | class | `net/minecraft/client/renderer/entity/state` |
| `Feature` | 66 | 60 | class | `net/minecraft/world/level/levelgen/feature` |
| `BaseEntityBlock` | 64 | 40 | class | `net/minecraft/world/level/block` |
| `StructurePiece` | 63 | 8 | class | `net/minecraft/world/level/levelgen/structure` |
| `Entry` | 59 | 2 | class | `net/minecraft/client/gui/components` |
| `Behavior` | 58 | 53 | class | `net/minecraft/world/entity/ai/behavior` |
| `BlockEntity` | 52 | 35 | class | `net/minecraft/world/level/block/entity` |
| `AgeableMob` | 52 | 4 | class | `net/minecraft/world/entity` |
| `RenderLayer` | 48 | 39 | class | `net/minecraft/client/renderer/entity/layers` |
| `NamedEntityFix` | 46 | 46 | class | `net/minecraft/util/datafix/fixes` |
| root | descendants | direct | kind | where |
|---|---:|---:|---|---|
| `FeatureElement` | 386 | 7 | interface | `net/minecraft/world/flag` |
| `ItemLike` | 366 | 2 | interface | `net/minecraft/world/level` |
| `TabOrderedElement` | 323 | 2 | interface | `net/minecraft/client/gui/components` |
| `GuiEventListener` | 321 | 5 | interface | `net/minecraft/client/gui/components/events` |
| `Renderable` | 262 | 7 | interface | `net/minecraft/client/gui/components` |
| `TypedInstance` | 251 | 5 | interface | `net/minecraft/core` |
| `DebugValueSource` | 247 | 3 | interface | `net/minecraft/util/debug` |
| `Packet` | 236 | 227 | interface | `net/minecraft/network/protocol` |
| `SlotProvider` | 230 | 2 | interface | `net/minecraft/world/entity` |
| `ContainerEventHandler` | 219 | 3 | interface | `net/minecraft/client/gui/components/events` |
| `Nameable` | 211 | 6 | interface | `net/minecraft/world` |
| `DataComponentGetter` | 199 | 4 | interface | `net/minecraft/core/component` |
| `ItemOwner` | 194 | 3 | interface | `net/minecraft/world/entity` |
| `UniquelyIdentifyable` | 193 | 1 | interface | `net/minecraft/world/level/entity` |
| `EntityAccess` | 192 | 1 | interface | `net/minecraft/world/level/entity` |
| `ScoreHolder` | 192 | 1 | interface | `net/minecraft/world/scores` |
| `SyncedDataHolder` | 192 | 1 | interface | `net/minecraft/network/syncher` |
| `LayoutElement` | 165 | 4 | interface | `net/minecraft/client/gui/layouts` |
| `StringRepresentable` | 163 | 162 | interface | `net/minecraft/util` |
| `NarrationSupplier` | 138 | 3 | interface | `net/minecraft/client/gui/narration` |
| `Waypoint` | 131 | 2 | interface | `net/minecraft/world/waypoints` |
| `Attackable` | 126 | 2 | interface | `net/minecraft/world/entity` |
| `WaypointTransmitter` | 125 | 1 | interface | `net/minecraft/world/waypoints` |
| `Leashable` | 121 | 2 | interface | `net/minecraft/world/entity` |
| `ParticleProvider` | 118 | 113 | interface | `net/minecraft/client/particle` |
| `Targeting` | 116 | 2 | interface | `net/minecraft/world/entity` |
| `EquipmentUser` | 115 | 1 | interface | `net/minecraft/world/entity` |
| `Validatable` | 114 | 9 | interface | `net/minecraft/world/level/storage/loot` |
| `NarratableEntry` | 99 | 3 | interface | `net/minecraft/client/gui/narration` |
| `RenderLayerParent` | 97 | 1 | interface | `net/minecraft/client/renderer/entity` |
---
*Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes `tools/verify_names.py`.*
---
# I · Anatomy
> Verified against **Minecraft 26.2** · Part I · The whole program at once: which threads exist, which loop each runs, and how the two halves talk.
Part I is the program the other twelve parts run inside. Java Minecraft is
one codebase running as two programs on four threads worth memorising — a
server whose whole life is a tick loop, and a client whose life is a frame
loop with ticks inside it — and almost every surprise later in this book is
one of those two waiting on the other. A player recognises the split by its
symptom: the game that keeps drawing while the world stands still, because
the two are not the same loop and never were. Nearly every
[lane](../../reference/lanes.md) in the sequence diagrams after this part is
a class, and the handful that are not stand for a thread — which is the same
reason for reading this part first either way.
## The shape of the part
Two pages: what the program is, and where it ends. The first is a trace and
the second is a boundary, and the boundary is drawn second because a reader
decides whether to go on once they can see the whole program.
```mermaid
flowchart LR
A["Anatomy: four threads, two loops, one wire — from main to a running singleplayer world"]
B["What this book skips: the fourteen packages the parts do not reach, and why"]
A -- "now that you can see the whole program, here is the part of it this book does not teach" --> B
```
## Before you start
Nothing. This is the first part, and it assumes only that you have played
the game.
## Watch in this order
1. [Anatomy](anatomy.md) — from *main* to a running singleplayer world:
the Render thread that is also the game thread, the Server thread that
is the world, the Netty threads that run more than bytes, and the one CPU
pool that chunk generation, lighting and section meshing all share.
2. [What this book skips](what-this-book-skips.md) — the boundary, drawn
honestly on the treemap of the jar: save migration, Realms, telemetry,
the profiler, the management server, the data generators, and where to
start if you need one anyway. It is the second lecture and not the last
because a boundary is worth drawing before the investment, not after.
## Reference this part uses
[Threads](../../reference/threads.md) — every thread, who makes it and what
may run on it; Part I names the four to memorise and this is the rest.
[Diagram lanes](../../reference/lanes.md) — the abbreviations every sequence
diagram uses, needed from the first figure of the first lecture onward.
[Naming drift](../../reference/naming-drift.md) — the 1.21-era names that
have moved. The [atlas](../../maps/packages.md) — where everything is, and
the treemap the second lecture reads.
## Where the part stops
Part I says which threads exist and hands each of them on. What the Server
thread *does* with a tick is [Part III](../server/README.md); what the frame
loop does between ticks is [Part X](../client/README.md); what crosses the
wire between them is [Part IX](../networking/README.md). This part owes you
only enough of each to read a lane.
---
*Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes `tools/verify_names.py`.*
---
# Anatomy
> Verified against **Minecraft 26.2** · Part I · Clicking Singleplayer, picking a world, and standing in it a few seconds later.
A player clicks Singleplayer, picks a world from the list and waits. One
thread has been running since *main*; by the time the world appears there
are two that matter, and the second was created by the first, mid-frame,
while the first went on drawing. They are two programs sharing a JVM. The
server is the world — every chunk, entity and block, and the only thing
allowed to change them; the client is a window, a frame loop and a copy of
the world it is told about. Between them runs a Netty channel that never
touches a socket, and over it the client walks the same handshake, login,
configuration and play state machine it would walk against a server on the
other side of the planet. The packets are real. What leaks between the two
halves is not world state but a setting: pause is *decided* on the client,
by `Minecraft.isPaused`, and *enforced* on the server, by
`IntegratedServer.tickServer` running `IntegratedServer.tickPaused` instead
of the world — which is why a world published to LAN never pauses, however
deep in the options menu you are.
## The cast
| class | what it decides | thread |
|---|---|---|
| `Minecraft` | the client: the `Window`, the resource system, the renderers, input and `Options` — and, in three fields, whether we are in a world at all | Render |
| `MinecraftServer` | the world and the loop that advances it. Abstract, with three concrete subclasses: `IntegratedServer`, `DedicatedServer`, and `GameTestServer`, the headless harness the gametest entry point launches | Server |
| `IntegratedServer` | everything singleplayer does differently: the pause, LAN publishing, the player cap, the relaxed limits | Server |
| `BlockableEventLoop` | the queue-and-thread pairing both loops are — `Minecraft` and `MinecraftServer` each extend `ReentrantBlockableEventLoop` | one per queue, and a thread may own more than one |
| `Connection` | one channel, and which `PacketListener` is currently on it | Netty |
| `ServerConnectionListener` | which channels the server listens on, including the in-memory one singleplayer uses | Server, binding into Netty |
| `PacketProcessor` | which decoded packets are waiting to be handled on the thread that owns their state | filled from Netty, drained by the owner |
| `Util` | the pools everything else is serialised onto: `Util.backgroundExecutor`, `Util.ioPool`, `Util.nonCriticalIoPool` | — |
Three of `Minecraft`'s nullable fields between them mean "we are in a
world": `Minecraft.level` (a `ClientLevel`), `Minecraft.player` (a
`LocalPlayer`) and `Minecraft.gameMode` (a `MultiPlayerGameMode`). A fourth,
`Minecraft.singleplayerServer`, holds the `IntegratedServer` when one is
running, and is the client's answer to "am I the host".
> **For a 1.21-era reader.** The client's clock is `DeltaTracker`, which was
> *Timer*, and the partial tick is a `DeltaTracker.Timer` you ask rather than
> a float you are handed — it appears in the frame loop below and on every
> renderer in Part XI. The rest of the drift a 1.21 reader will trip on is
> [naming drift](../../reference/naming-drift.md).
## From *main* to a world
```mermaid
sequenceDiagram
participant Main as Main
participant RS as RenderSystem
participant MC as Minecraft
participant MS as MinecraftServer
participant IS as IntegratedServer
participant SCL as ServerConnectionListener
participant Conn as Connection
Main->>Main: tryDetectVersion, loadLibraries, DataFixers.optimize in the background, bootStrap, ClientBootstrap, validate
Main->>RS: initRenderThread — this thread is the Render thread from here on
Main->>MC: the constructor — initBackendSystem, a backend, a Window, every reload listener registered
MC->>MC: the first ReloadInstance — prepare on the workers, apply here, LoadingOverlay on screen
Main->>MC: run — pollEvents, then runTick, until running goes false
Note over Main,MC: one thread so far. The next line makes the second.
MC->>MS: doWorldLoad calls spin — the IntegratedServer is built here, the Server thread is started
MS->>IS: runServer calls initServer, which loads the level and prepares its chunks
MC->>MC: managedBlock — draw a frame, drain the queue, repeat, until MinecraftServer.isReady
MC->>SCL: startMemoryChannel — a Netty local address, no socket anywhere
MC->>Conn: connectToLocalServer — the client's end of that same channel
Conn->>SCL: handshake, then login — the handlers run on Netty, the login tick on the Server thread
Conn->>SCL: configuration, then play — from here the client is a client like any other
Note over MC,IS: two loops, one wire
```
That is the book's first sequence diagram, and its lanes are abbreviated the
way every later one is: two or more letters of a class name, one meaning
throughout. The key is [diagram lanes](../../reference/lanes.md).
**Bootstrap before anything exists.** `SharedConstants.tryDetectVersion`
reads *version.json* as the first statement of both *main* methods, before
the option parser exists; `NativeLibrariesBootstrap.loadLibraries` unpacks
the natives, `CrashReport.preload` warms the reporter, and
`Bootstrap.bootStrap` builds and freezes the static registries — blocks,
items, entity types, the things that cannot be data-driven because the data
loader itself needs them
([identifiers and registries](../foundations/identifiers-and-registries.md#before-the-game-exists)
is what the freeze proves). `ClientBootstrap` does the client-only equivalents
between that and `Bootstrap.validate`, which checks the result.
`DataFixers.optimize` is kicked off concurrently before the registries are
built and joined much later. That ordering is why nothing in `world/` can be
touched from a static initialiser.
**The GPU backend is chosen in the constructor.**
`RenderSystem.initBackendSystem` runs first and returns GLFW's clock, which
`Minecraft` installs through `Util.setTimeSource` — on the client, the game's
entire notion of time comes from the windowing library. Then a `GpuBackend` is
chosen by trying candidates in an order `Options` sets until one of them
makes a `Window` — there are two, `GlBackend` and `VulkanBackend`
([the window](../rendering/the-window.md#trying-backends-until-one-of-them-makes-a-window))
— and from then on the renderer only ever sees the `GpuDevice` abstraction
in `com/mojang/blaze3d`.
**Construction registers, it does not load.** The constructor creates each
manager and registers it on the `ReloadableResourceManager`; the loading is
one `ReloadInstance` whose *prepare* phases run on `Util.backgroundExecutor`
and whose *apply* phases run on the Render thread, with the `LoadingOverlay`
on screen. Pressing F3+T re-runs exactly that path — see
[the resource system](../foundations/resource-system.md#f3t-end-to-end).
**Opening a world spins a server.** `Minecraft.doWorldLoad` calls
`MinecraftServer.spin`, which constructs the `IntegratedServer` *on the
caller's thread* before starting the new one
([starting a server](../server/starting-a-server.md#minecraftserverspin-and-the-last-thing-main-does)
has that order in full); the new thread's body is `MinecraftServer.runServer`,
which calls `IntegratedServer.initServer` and enters the loop. Meanwhile the Render
thread keeps drawing frames and draining its own queue through
`BlockableEventLoop.managedBlock` until `MinecraftServer.isReady` — the
textbook case of *waiting drains*.
**The client connects like any other client.**
`ServerConnectionListener.startMemoryChannel` binds a Netty local address and
`Connection.connectToLocalServer` connects to it; the client then walks
handshake, login, configuration and play through
`ClientHandshakePacketListenerImpl` exactly as it would against a remote
server. Almost nothing in the play path knows it is singleplayer: the
exceptions are the handful of places that ask `Connection.isMemoryConnection`
directly, among them `ClientPacketListener.handleUpdateTags`, which skips
applying the tags it was sent because the server's registries are already
the client's. [Protocol phases](../networking/protocol-phases.md) is that
walk in full.
## Two loops, and a wire between them
The client's loop is a frame loop with ticks inside it; the server's is a
tick loop with no frames at all. They are not the same shape, and no page
later in this book is readable until that difference is fixed in mind.
```mermaid
flowchart LR
subgraph Client["the Render thread"]
direction TB
CR["Minecraft.run: RenderSystem.pollEvents"] --> CD["runTick: the DeltaTracker says how many whole ticks are owed"]
CD --> CP["PacketProcessor.processQueuedPackets"]
CP --> CQ["BlockableEventLoop.runAllTasks: this thread's own queue"]
CQ --> CT["Minecraft.tick, run 0 to 10 times"]
CT --> CF["renderFrame, interpolating by the leftover partial tick"]
CF --> CR
end
subgraph Wire["the Netty event loop"]
direction TB
N["Connection.channelRead0 decodes and calls the PacketListener. PacketUtils.ensureRunningOnSameThread queues it on the owner and aborts the handler"]
end
subgraph Server["the Server thread"]
direction TB
SR["MinecraftServer.runServer: the next deadline is set first"] --> SP["processPacketsAndTick: PacketProcessor.processQueuedPackets"]
SP --> SS["MinecraftServer.tickServer: every ServerLevel, then the connections"]
SS --> SW["waitUntilNextTick: run queued tasks, then park until the deadline"]
SW --> SR
end
N -- "a clientbound packet" --> CP
N -- "a serverbound packet" --> SP
CT -- "Connection.send" --> N
SS -- "Connection.send" --> N
```
**The frame loop.** `Minecraft.run` polls GLFW events and calls
`Minecraft.runTick` once per **frame**, as fast as vsync or the frame-rate
limit allow. Inside each frame a `DeltaTracker.Timer` running at twenty ticks
a second says how many whole game ticks have elapsed since the last frame —
usually zero or one, at most ten are run — and `Minecraft.tick` is called that
many times. The fractional remainder is the partial tick the renderers
interpolate with. So the client *has* a 20 Hz tick, but it is a sub-step of
the frame loop rather than a loop of its own; [the client
loop](../client/the-client-loop.md#the-ten-and-the-arithmetic-behind-it) is the
arithmetic in detail.
**The tick loop.** `MinecraftServer.runServer` re-reads this tick's length
every iteration from `TickRateManager.nanosecondsPerTick` — 50 ms by default,
whatever `/tick rate` says otherwise, and zero while sprinting — and calls
`MinecraftServer.processPacketsAndTick`, which drains the `PacketProcessor`
and then runs `MinecraftServer.tickServer`. Afterwards
`MinecraftServer.waitUntilNextTick` spends the slack running queued tasks and
then parks until the next tick is due. There is no frame and no partial tick
here at all. The "Can't keep up!" warning is `MinecraftServer.runServer`'s
own, decided
before either call from how far behind the deadline already is; [the server
tick](../server/server-tick.md#what-minecraftservertickchildren-runs-and-in-what-order)
owns what is inside them — the tick budget,
the deferrable work and the flush bracket around outbound packets.
**Both are event loops first and game loops second.** `Minecraft` and
`MinecraftServer` both extend `ReentrantBlockableEventLoop` — the same base
class, not an analogy — so each is an `Executor` whose queue drains on its own
thread, and any other thread that wants to touch that half's state submits a
task and waits. `BlockableEventLoop.managedBlock` is the blocking form, and
the reason the owning thread can wait for a future without deadlocking: it
keeps draining its own queue while it waits.
**A packet is decoded on one thread and handled on another.** A packet
arrives on a Netty IO thread and `Connection.channelRead0` hands it to the
current `PacketListener`; a handler that touches game state calls
`PacketUtils.ensureRunningOnSameThread`, which, when it is off-thread, queues
the packet on the owning side's `PacketProcessor` instead of running it —
[the connection](../networking/the-connection.md#the-threads-underneath-it)
is that crossing in both directions. What matters here is *when* the queue is
drained, because the two loops do not agree: first thing in
`MinecraftServer.processPacketsAndTick`, but on the client early in
`Minecraft.runTick`, after the delta tracker advances and before the ticks —
so a client at 200 frames a second takes the server's packets ten times more
often than it ticks.
## Four threads worth memorising
| thread | made by | runs | notes |
|---|---|---|---|
| **Render thread** | the JVM main thread, renamed in `client/main/Main` | `Minecraft.run` | Also the client's game thread: `Minecraft.gameThread` is this thread. Priority 10 on machines with more than four cores. |
| **Server thread** | `MinecraftServer.spin` | `MinecraftServer.runServer` | One per server, so singleplayer has exactly one. Priority 8, on the same more-than-four-cores condition. |
| **Netty IO** | `EventLoopGroupHolder` | the `Connection` pipeline | Named *Netty NIO IO n* — Epoll or Kqueue when native transport is on, *Netty Local IO n* for the in-process singleplayer channel. Decode, decrypt, decompress — and, unlike the play phase, the handshake and login *handlers*, which never call `PacketUtils.ensureRunningOnSameThread`; the first handler that hops is in configuration. The login state machine is still advanced from the Server thread, because `ServerLoginPacketListenerImpl` is a `TickablePacketListener` and `MinecraftServer.tickConnection` ticks it. |
| **Worker-Main-n** | `Util.backgroundExecutor` | a `ForkJoinPool` sized to the JDK's available-processor count minus one | `Util.maxAllowedExecutorThreads` clamps it, and `Util.getMaxThreads` reads a *max.bg.threads* system property that overrides the ceiling. The shared CPU pool: chunk generation and lighting (`ChunkMap` through `ChunkTaskDispatcher`), section meshing (`SectionRenderDispatcher`), resource-reload *prepare* phases, chunk serialisation. |
That is the set worth memorising, not the set that exists. The IO workers,
the sound engine's event loop, the dedicated server's watchdog, console,
RCON, query and management threads, the timer hack thread and the
situational ones — authentication, chat filtering, server pinging, telemetry,
world upgrades, the shutdown hooks — are all in
[Threads](../../reference/threads.md), with who makes each and what it is
allowed to touch.
There is no fifth thread hiding on the client. The Render thread *is* the
game thread: `Minecraft.gameThread` and the thread `RenderSystem` guards with
`RenderSystem.assertOnRenderThread` are the same one, and there is no
*initGameThread* and no *isOnGameThread* anywhere in the tree — only
`RenderSystem.isOnRenderThread`. A slow client tick costs frames directly.
What that thread does with a world is animate and predict one — `Minecraft.tick`
calls `ClientLevel.tickEntities`, and block entities tick too — but nothing it
concludes is authoritative, and the server's packets overwrite whatever the
prediction got wrong — which is a claim about the *world*, and the client's
own player is the exception the book spends Part VIII and
[prediction and acknowledgement](../client/prediction-and-acks.md#two-state-machines-running-against-each-other)
on.
Everything else that matters is *serialised onto* a pool rather than given a
thread. The two `ConsecutiveExecutor` classes in `util/thread` are the
mechanism: a queue that promises to run its tasks one at a time on a pool
that otherwise runs many, which is how "worldgen" and "light" stay ordered on
the worker pool, and how the `IOWorker` stays ordered on `Util.ioPool` —
which is its own pool of *IO-Worker-n* threads, not one of the four. `PriorityConsecutiveExecutor` adds a priority to
the same idea. And `ServerChunkCache.MainThreadExecutor` is a further event
loop layered on the server thread, which is why a tick that waits on a chunk
does not deadlock the chunk that needs the tick.
## What singleplayer shares by direct call
Nothing on the client writes server world state and nothing on the server
writes client world state. Every block, entity and inventory change crosses
as a packet, even in one process. But the two halves share a JVM, and a
handful of things do cross by direct call — every one of them a setting
rather than world state. The server reads `Minecraft.isPaused` and the
client's render and simulation distances every tick;
`IntegratedServer.updateCommandsAllowedForOtherPlayers` reaches into
`LocalPlayer.setPermissions`; the options screens call
`IntegratedServer.publishServer` and its siblings straight from the Render
thread; and `IntegratedServer.latestTicksGizmos` is a volatile list the
server thread writes and the client reads. Treat "everything crosses as a
packet" as a rule about the *world*, not about the process.
Singleplayer differs in more than pausing, too. Beyond the pause and the
distances following `Options`, `IntegratedServer` caps the player list at
eight, owns LAN publishing and the `LanServerPinger`, drops the chat and
command spam thresholds to zero where a dedicated server defaults to ten,
takes native transport from the client's own option rather than a server
property, and answers the operator-permission questions differently.
## Questions players ask
**Does a dedicated server pause?** Yes — an empty server stops ticking on
its own ([the server tick](../server/server-tick.md#an-empty-server-stops-ticking)
has the counter and what still runs). Pausing is not a singleplayer concept;
only the client-decides-it half is.
**Is twenty ticks a second a constant?** No, it is a server field.
`ServerTickRateManager`, over the shared `TickRateManager`, owns the
nanoseconds-per-tick, the freeze and the sprint state that `/tick`
manipulates, and the client mirrors it in `ClientLevel` so the
`DeltaTracker` can freeze too.
**Does a busy server skip work?** Less than the budget's name suggests.
`MinecraftServer.haveTime` travels from `MinecraftServer.tickServer` down
through every level, and what it actually gates is a short list that does
not include loading or generating a chunk. [The server
tick](../server/server-tick.md#what-the-budget-actually-gates) has that list,
and the sprint's inverted effect on it.
**What happens when something throws?** It is collected, not thrown. Both
loops catch everything and wrap it in a `CrashReport`; a background thread
that dies has its report parked for a loop to pick up
([how a server dies](../server/how-a-server-dies.md#the-crash-that-saves)
owns that relay, and
[the client loop](../client/the-client-loop.md#starting-and-the-three-ways-of-stopping)
the client's own three exits). The Part I consequence is the asymmetry: only
a loop constructed to propagate crashes rethrows a parked report, and
`IntegratedServer` is not one — so a worker that dies in singleplayer
surfaces on the *client*, not on the server thread whose work it was doing.
**Which entry point starts all this?** One of five. `client/main/Main` for the
client, `server/Main` for the dedicated server, `data/Main` for the data
generator, `client/data/Main` for the generated client assets — models,
atlases, equipment assets, waypoint styles — and `gametest/Main` for
`GameTestServer`. The tree holds a sixth *main*, `SnbtDatafixer`, which
converts files and starts nothing.
Each parses its own command line — the client's into a `GameConfig` the
`Minecraft` constructor is built from — and then reads its own settings file:
*options.txt* through `Options` on the client, *server.properties* through
`DedicatedServerProperties` on the dedicated server, and *version.json*
through `SharedConstants` on both. The two generator entry points are
build-time programs and
[what this book skips](what-this-book-skips.md#the-data-generators-and-why-data-driven-is-both-true-and-misleading)
says how far that is true.
## Where to look
`client/main/Main` · `GameConfig` · `Minecraft` · `DeltaTracker` ·
`MinecraftServer` · `IntegratedServer` · `server/Main` · `DedicatedServer` ·
`GameTestServer` · `BlockableEventLoop` · `ReentrantBlockableEventLoop` ·
`Util` (the executors) · `PacketProcessor` · `PacketUtils` ·
`EventLoopGroupHolder` · `ServerConnectionListener` · `Connection` ·
`PreferredGraphicsApi` · `GpuBackend`
---
*Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes `tools/verify_names.py`.*
---
# What this book skips
> Verified against **Minecraft 26.2** · Part I · A reader opens the atlas, sees fourteen packages hatched, and asks what is in them and why they are not taught.
Open the atlas and part of the jar is drawn hatched. That hatching is this
page. Java Minecraft is 7,055 classes and 719,302 lines, and the parts do
not reach all of it. Some of what is left out is excluded on purpose by
[rule three](../../introduction.md) — save migration is version-difference
code, and a book that documents only the current version has nothing to say
about it. Some is out of scope because it is a client for a service this
book cannot read. And one hatched box is not skipped code so much as skipped
*ground*: `net/minecraft/data` is the program that writes vanilla's own
content as a data pack, it ships in the dedicated server jar — all 163
classes of it — and the running game compiles against it and calls into it.
`Blocks` names `TreeFeatures` keys while it constructs mushroom blocks;
`MinecraftServer` reaches for a `MiscOverworldFeatures` key for the bonus
chest; the F3 screen's biome line runs through
`NoiseRouterData.peaksAndValleys` into `TerrainProvider`. The boundary this
page draws is honest, and part of drawing it honestly is showing where it
leaks.
*(figure: packages-treemap.svg — a generated SVG, not reproduced here)*
The jar by package, area by lines of decompiled source. Hatched boxes are the packages this page tours and the parts do not. Click to enlarge.
## The sizes, and which jar ships them
Each entry below says what the thing is, roughly how big it is, whether the
dedicated server ships it, one fact worth knowing, and where to start
reading. The treemap deliberately does not hatch `net/minecraft/gametest`:
Part XIII covers it, so it is a gap that closed rather than a skip. The
counts in the table are files, so a *package-info.java* counts as a class
there and not in the prose below.
| package | classes | lines | side |
|---|---:|---:|---|
| `net/minecraft/util/datafix` | 396 | 26,372 | both |
| `net/minecraft/util/filefix` | 57 | 3,544 | both |
| `net/minecraft/client/telemetry` | 18 | 1,221 | client |
| `net/minecraft/util/profiling` | 70 | 4,260 | both |
| `net/minecraft/server/jsonrpc` | 65 | 4,094 | dedicated server |
| `net/minecraft/server/rcon` | 9 | 839 | dedicated server |
| `com/mojang/realmsclient` | 127 | 13,217 | client |
| `net/minecraft/realms` | 4 | 203 | client |
| `net/minecraft/stats` | 10 | 873 | both |
| `net/minecraft/gizmos` | 15 | 569 | both |
| `net/minecraft/references` | 5 | 1,434 | both |
| `net/minecraft/data` | 163 | 15,587 | both — see below |
| └ `net/minecraft/data/worldgen` | 56 | 5,369 | both — see below |
| `net/minecraft/client/data` | 28 | 6,176 | client |
| `com/mojang/blaze3d/audio` | 12 | 1,013 | client |
| `net/minecraft/client/multiplayer/chat/report` | 12 | 952 | client |
The oracle for "server or client" throughout is the list of classes the
dedicated server jar ships, which lives beside the decompile. It answers
exactly one question — *does the dedicated server have this class* — so it
can prove "client-only" and it can prove "both jars", and it cannot prove
"dedicated server only". Two rows are labelled that way on the strength of a
different check: nothing under `net/minecraft/client` or `com/mojang/blaze3d`
references them. *rcon* is reached from `DedicatedServer` alone; *jsonrpc*
from six files, and two of them — `BuiltInRegistries` and `Registries` — are
classes the client loads at bootstrap, so the package's *types* are on the
client and the server it configures is not.
## Save migration, and the fixer that moves files
**`net/minecraft/util/datafix`** — the largest thing on this page and the
most explicitly out of scope. `DataFixers` is one static class whose whole
body is the migration history of the game written out longhand: three
hundred schema registrations and four hundred-odd fixes, from schema 99 up
to the current world version. The rewriting machinery itself is Mojang's
external DataFixerUpper library; what lives here is the vanilla catalogue —
`util/datafix/schemas` describing the *shape* of the data at each version,
and `util/datafix/fixes` doing the individual rewrites.
A version number becomes a chain of fixes through `DataFixTypes`, an enum
of about thirty type references (level, chunk, player, entity chunk, POI
chunk, options, stats, advancements, and a long tail of saved-data kinds).
`DataFixTypes.updateToCurrentVersion` takes the data version as an
argument — every one of its thirteen callers reads the version itself — and
asks the fixer to compose every rule from there to now.
`DataFixTypes.wrapCodec` is the one that reads the version *out of the tag*:
it wraps an ordinary codec so decoding pulls the data version, runs the
chain, and encoding stamps the current version back in. It is the rarer
door — two callers, `PlayerAdvancements` and `DebugScreenEntryList` — while
[chunk storage](../world/chunk-storage.md) and player data take the first
one and read the version themselves. The rules are
pre-compiled on a dedicated bootstrap thread, and that thread is built with
some care to cost nothing: one thread, daemon, at minimum priority, with a
single caller in the client's entry point, optimising exactly one type (the
level-summary schema, so the world list opens fast). The dedicated server
never asks for it at all.
**`net/minecraft/util/filefix`** does what the other cannot. A data fixer
rewrites the *contents* of a tag after it has been read, so it can never
move, rename, split or delete a file. `FileFixerUpper` operates on the
world **directory**: its operations are moves, regex moves, group moves,
deletions, content modifications and one composite that scopes a nested list
of operations to matching folders, and the concrete fixes do things like
relocate dimension storage, split player storage and pull data out of
*level.dat* into saved data
([level data and rules](../../reference/level-data-and-rules.md)).
It stays safe by working somewhere else: the whole upgrade runs against a
**custom copy-on-write file system** rooted at a scratch directory, and the
result is swapped in at the end. Exactly how safe depends on the filesystem
underneath. Where hard links are available it uses them. Where they are not,
it writes one file, *upgrade_in_progress.json*, recording the moves, and an
interrupted upgrade resumes from it while an aborted one reverts. And where atomic move is
unavailable it refuses to run at all rather than risk a half-moved world.
The client does not grey out a world that needs the upgrade — it relabels
the button. `LevelSummary.primaryActionMessage` turns Play into *Upgrade and
Play* while leaving it active; what is disabled is Edit and Recreate, which
would otherwise touch a directory the fixer is about to rearrange.
## Telemetry writes to disk before it writes to the network
**`net/minecraft/client/telemetry`**, client-only. Exactly seven event
types: world loaded, world unloaded, graphics capabilities (which now
carries the backend name and the reason a backend failed — see
[Blaze3D](../rendering/blaze3d.md)), and four opt-in ones covering
performance metrics, world load times, advancements and game load times.
`TelemetryProperty` is the vocabulary; each property carries both an
internal name and a different export key.
Opting out is two-tier and neither tier is a plain checkbox.
`Minecraft.allowsTelemetry` reads an *account-level* flag the game only
reports; the in-game control only chooses whether the four opt-in events
are sent, and is only offered when the account carries the flag that allows
it. Everything sent is **also written locally** as a JSON event log with a
seven-day expiry — and the send is nested *inside* the log write, so a
failed log suppresses the send. A player can read their own outgoing
telemetry, though not in the game: the telemetry screen renders the
*catalogue* of event types and their properties, and a button next to it
opens the log directory in the platform's file manager.
Start at `ClientTelemetryManager`, `TelemetryEventType`.
## Four profilers, two of them in this package
**`net/minecraft/util/profiling`** holds four profiling systems, though
only two of them are self-contained here.
The **tick profiler** is the familiar one: `Profiler` is a thread-local
holder of a `ProfilerFiller`, `ActiveProfiler` records the push/pop tree of
named sections that every page in this book quotes, and `/debug start`
drives it.
**Tracy** is the surprise, and it is one class bridging out to Mojang's
Tracy binding. `TracyZoneFiller` implements the same interface, and
`Profiler.get` falls back to the Tracy filler rather than the inactive one
when Tracy is available — and `Profiler.decorateFiller` *combines* the two,
so an attached Tracy build and a running `/debug start` both see every
section. With a Tracy build attached, every profiler section in the game
streams out with no command run. Tracy reaches outside this package too,
into Blaze3D's frame capture and GPU profiler and into the executor
wrappers.
**JFR** (`util/profiling/jfr`) registers ten custom flight-recorder events
under a Minecraft category — chunk generation, region reads and writes,
packets sent and received, network summaries, server tick time, client FPS,
structure generation, world load. Packet events are emitted straight from
the packet codecs, so a recording gives a per-packet-type byte breakdown
that this book's [packet reference](../../reference/packets.md) cannot.
Start with *--jfrProfile* or `/jfr start`.
**Metrics** (`util/profiling/metrics`) is `/perf`: sampling by nine
`MetricCategory` values — pathfinding, event loops, consecutive executors,
the tick loop, JVM, chunk rendering, chunk-rendering dispatch, CPU and GPU —
written out as CSVs that `PerfCommand` zips.
## The management server is not RCON
**`net/minecraft/server/jsonrpc`**, dedicated server only, and genuinely
new. It is JSON-RPC 2.0 over a WebSocket, served by its own Netty bootstrap
with an HTTP codec, an authentication handler, the WebSocket handshake and
optional TLS. It is disabled by default; when enabled, TLS is on unless
explicitly turned off, and the server refuses to start without a
forty-character alphanumeric secret, generating one if absent.
What it exposes is the administrator's surface, not the game's: allow-list,
bans and IP bans, players and kicks, operators, game rules, server status,
save and stop, system messages, and a family of live server settings —
including the idle-pause window, whose actual behaviour is
[the server tick](../server/server-tick.md#an-empty-server-stops-ticking)'s,
because a dedicated server pauses too.
Implementations sit behind service interfaces so the wire layer never
touches the server object directly, and an executor service marshals calls
onto the server thread.
The description of the API cannot drift from the handlers, because it is
derived from them: every method is registered with a description and typed
parameter and response schemas, and a discovery method returns an
**OpenRPC 1.3.2** document built by walking the two method registries and
filtering on a per-method discoverable flag. There is also an outgoing
direction for server-initiated notifications. The audience is panel and
hosting operators.
Start at `JsonRpc`, `ManagementServer`.
## RCON, query, and the pre-1.7 ping that removes itself
**`net/minecraft/server/rcon`** is seven classes of pre-Netty blocking
socket code on its own threads. `RconThread` speaks Valve's Source RCON
framing; commands execute as a `RconConsoleSource`, a command source that
accumulates output into a string rather than a chat feed
([Brigadier and commands](../commands/brigadier-and-commands.md)).
`QueryThreadGs4` speaks the GameSpy4 UDP query protocol with a
challenge-token handshake and a five-second response cache.
The **pre-1.7 ping is not in that package**. `LegacyQueryHandler` sits in
the server's network package and is installed into the Netty pipeline
*before* the length-prefix splitter and the packet codec, right after the
read timeout ([the connection](../networking/the-connection.md)). It peeks
at the first byte; if it is the legacy ping marker it answers in the old
format and closes, and otherwise it resets the reader index, **removes
itself from the pipeline**, and re-fires the bytes downstream. It costs one
byte comparison per connection and then vanishes. The same encoding is used
client-side so the server list can still ping ancient servers.
## Realms is a client for a server nobody here can read
**`com/mojang/realmsclient`**, client-only, 127 classes and 13,217 lines —
about the size of the whole packet catalogue in `network/protocol`. Roughly
sixty per cent is screens and the records behind them — subscriptions, world
slots, templates, invites, backups, minigames, upload and download — and the
rest is a task framework, the world-upload pipeline and the HTTP layer, a
list of REST paths with a small request wrapper. Three classes and a
*package-info.java* in `net/minecraft/realms` are the only part of vanilla
the Realms UI extends.
Out of scope because it is a service client: its behaviour is defined by a
server this book cannot read. One fact anyway. The environment is chosen
from an environment variable falling back to a system property, defaulting
to production, in a static final field of the release client — and there are
three environments, not two, because the third points at *localhost*.
## Statistics, the scoreboard, and the recipe book
**`net/minecraft/stats`** is nine classes covering two concerns, plus a link
into a third package that is the reason it is worth a paragraph.
**Statistics**: `Stats` declares eight registry-backed stat types — mined,
crafted, used, broken, picked up, dropped, killed, killed by — plus a
custom type holding the seventy-odd hand-declared counters (play time,
distances by every mode of travel, damage dealt and blocked), each bound to
a formatter that affects display only. A stat type is a lazily-populated
map over a registry, so stat objects are interned; `ServerStatsCounter`
adds the per-player file and a dirty set, and only dirty stats are sent.
Statistics are one of **two** parts of the save that go through the data
fixer as *JSON* rather than NBT — the other is advancement progress
([advancements](../commands/advancements.md)), and they are the only two.
**The scoreboard link** is why the package is worth a paragraph — and note
that the class doing the linking is not in it: `ObjectiveCriteria` lives in
`net/minecraft/world/scores/criteria`, which is why a scoreboard objective
can name a statistic and why this package is reachable from a command at all
([scores, teams and stored data](../commands/scoreboard-and-data.md#what-a-criterion-can-be-which-is-nearly-anything)
is how the name is parsed).
**The recipe book** is the second concern, and it is in this package for
historical reasons rather than architectural ones — `RecipeBook`,
`RecipeBookSettings` and `ServerRecipeBook` are not skipped, they are
[recipes](../items/recipes.md)', with [advancements](../commands/advancements.md)
reaching in from the other side. The address is the only thing surprising
about them.
## Two packages nobody will recognise
**`net/minecraft/gizmos`** is a **debug-drawing API**, in the game-engine
sense of the word: the immediate-mode "draw me a box in the world for one
frame" facility most engines have and Minecraft did not. `Gizmos` is a
static façade over a thread-local `GizmoCollector`; calling a shape method
outside a collector scope throws. The shapes are small records; a style is
a stroke and fill; the returned handle can pin a shape on top, persist it
for a duration or fade it out.
Every one of the debug renderers — chunk borders, hitboxes, pathfinding,
brains, points of interest, raids, light sections — is now written against
it. The part that surprises is that it is **server-side too**: there are
four collectors — three on the client (per-tick, the extract pass, the
render thread) and one on the integrated server, which wraps its whole
packet-and-tick step in a collector scope and publishes the result for the
client to drain. Server tick code can draw into the singleplayer world; a
dedicated server installs no collector at all, so the same calls there would
throw. A headless test server installs a no-op collector so the same calls
cost nothing.
**`net/minecraft/references`** is not "references" in the data-fixer sense.
It is a set of **id-constant tables**, and the split is not the one the
package names suggest: `BlockIds` holds the keys for blocks with **no item
form** (water, lava, wall torches, piston heads, wall signs), `ItemIds` the
items with no block, and `BlockItemIds` — seven times `BlockIds` and not
quite twice `ItemIds` — the pairs. Look for stone in `BlockIds` and it is not there. They exist to break
a class-initialisation cycle: exactly **ten** files outside the package name
it, and they are precisely the ones that need to name a block or item
*before* the block and item classes are loaded — `Blocks` and `Items`
themselves, `GrassBlock` and `MyceliumBlock`, which name another block
during that initialisation, `DecoratedPotPatterns` beside them, and the five
tag providers. A resource key is a registry plus
an [identifier](../foundations/identifiers-and-registries.md), so it can be
built with nothing loaded. Practically, it is the canonical machine-readable
list of *block and item* ids, and a better starting point than the block and
item holder classes if that is what you want — but not the id list: five
sibling tables for entity types, block-entity types, potions, fluids and
atlases live outside the package, in the trees they belong to.
## The data generators, and why "data-driven" is both true and misleading
Most of **`net/minecraft/data`** is a build-time program: a second entry
point with its own options, a generator that groups providers into packs,
and a hash cache that skips unchanged files. `net/minecraft/client/data` is
its client half, generating block and item models and the atlas definitions.
The significance is a genuine paradox worth stating plainly. **Vanilla's own
content is a data pack.** `net/minecraft/data/worldgen` is the entire
vanilla worldgen data pack written as Java — the biome feature lists, the
surface rules, the noise settings, the carvers, the jigsaw pools, the
structures and structure sets, the processor lists — and the loot, recipe,
tag and advancement packages do the same for their domains, all serialised
through the *same* codecs the game uses to read a pack.
So "Minecraft is data-driven" is true: the running game only ever sees JSON
parsed by codecs, with no vanilla-specific path
([the resource system](../foundations/resource-system.md)). And "you cannot
change it without a data pack" is *nearly* true — which is the more useful
statement, because the exceptions are load-bearing and a reader who believes
the absolute version will misread three other pages.
**The package is not build-time only, and the dedicated server ships all 163
classes of it.** Three kinds of exception:
- **Plain id tables.** `AtlasIds` is read at runtime by the model manager,
the atlas manager, the map, sky, painting and particle renderers, and by a
chat component. Nothing build-time about it.
- **The bootstrap interface itself.** `BootstrapContext` — in
`net/minecraft/data/worldgen` — is what every vanilla registry bootstrap in
the game is written against, from damage types and enchantments to chat
types, dialogs and world clocks. It is the most-imported type in the
package by a wide margin.
- **Constants and math the running game calls.** `Blocks` itself names
`TreeFeatures` and `CaveFeatures` keys while constructing mushroom and
fungus blocks; `MinecraftServer` reaches for a `MiscOverworldFeatures` key
for the bonus chest; a jigsaw block entity defaults to a `Pools` key;
`NoiseRouterData` and `NoiseGeneratorSettings`, both shipped worldgen
classes, are compiled against `TerrainProvider` and `SurfaceRuleData`; and
the F3 screen's biome line calls `NoiseRouterData.peaksAndValleys`, one
line that delegates straight into `TerrainProvider`
([density functions](../worldgen/density-functions.md)).
Vanilla's density functions and noise settings still reach the running game
as JSON. `NoiseRouterData.bootstrap` and `NoiseGeneratorSettings.bootstrap`
are collected by `VanillaRegistries`, which the data-generator entry point
runs and which `Commands.validate` borrows for its ambiguity check; the game
itself reads the generated files out of the built-in pack. Editing
`TerrainProvider` changes terrain by changing what that generator writes.
So `net/minecraft/data` holds a build-time program *and* a handful of tables
and functions the shipped game compiles against and executes. The generator
half really is inert at runtime, and it is the half worth reading — it is
the fastest way to understand what a vanilla biome or structure declares,
because it is typed and cross-referenced where the JSON is not, a point
[biomes](../worldgen/biomes.md) and
[structure placement](../worldgen/structure-placement.md) both depend on. And the report
providers are how you get machine-readable dumps of exactly the tables this
book's own [reference layer](../../reference/README.md) covers.
## The audio backend lives in Blaze3D, and is not skipped
**`com/mojang/blaze3d/audio`** is the one package in this tour that is
hatched for its *address* rather than for being unread. It wraps OpenAL, and
it sits inside Blaze3D, beside the GPU abstraction, rather than in the
client's sound package where the engine, the manager, the channel pool and
the Ogg decoding live. That is the boundary fact: Blaze3D is the platform
layer for both devices, not only the graphics one, and a reader looking for
the sound code under `client/sounds` will not find the half that talks to the
driver. Everything the package does — the device and context, the channel
pools, binaural rendering, hot-plugging a headset mid-game — is taught, in
Part X, by [the sound engine](../client/sound-engine.md).
## Player reporting
**`net/minecraft/client/multiplayer/chat/report`**, client-only.
`ReportingContext` holds the sender, the environment (which server or
realm), a log of the last thousand-odd received messages, and at most one
draft report. There are three report kinds — chat, skin, name — and an
eleven-value reason enum.
The piece worth naming is the context builder: a chat report does not send
just the offending line, it walks the log backwards to assemble surrounding
**signed** context, which is what makes the report verifiable at the other
end. The report machinery is the consumer of the chat-signing system that
[chat and signing](../networking/chat-and-signing.md) documents. Neither the
transport nor the policy is in the game — both come from the account
service library.
## Gaps, and the ruling on each
These were never excluded on principle — they were simply not written when
the book reached them. Each carries one of four rulings: **covered** (a page
now owns it), **absorbed** (a paragraph or a section on a page that already
exists), **reframed** (the gap was described wrongly, and the description is
what changed), or **declined** with a reason. A decline is a promise that a
reader will not miss it, not a shrug.
| what | size | ruling | where |
|---|---|---|---|
| `net/minecraft/gametest` | 47 classes, 5,514 lines | covered, which is why the treemap does not hatch it | [game tests](../commands/game-tests.md) |
| the debug cluster | four packages' worth | covered: the F3 entry registry, and the server-push subscriptions, sample loggers and debug renderers | [the HUD](../client/hud.md), [debugging the running game](../client/debugging-the-running-game.md) |
| `com/mojang/blaze3d/platform` | 29 classes, 3,896 lines | covered | [the window](../rendering/the-window.md) |
| `PostChain`, `PostChainConfig`, `PostPass`, `UniformValue` | 4 classes, 996 lines, and six shipped chains | covered — it was the only place in the game where user-authored shaders are first class | [post-processing](../rendering/post-processing.md) |
| `net/minecraft/client/renderer/item`, its item-properties subtree included | 63 classes | covered as a section rather than a page: the trace starts at an `ItemStack` but everything it touches is Part XI's | [models and atlases](../rendering/models-and-atlases.md#how-an-item-picks-its-model) |
| the scoreboard, teams and command storage | 32 classes, ~3,830 lines | covered — it was the largest coherent system in the book with no page at all | [scores, teams and stored data](../commands/scoreboard-and-data.md) |
| `net/minecraft/util/parsing` | 29 classes, 1,879 lines | absorbed — Mojang's own packrat parser-combinator framework, and the question it answers, why the client can complete mid-token, is that page's question | [Brigadier and commands](../commands/brigadier-and-commands.md), and [codecs, NBT and JSON](../foundations/codecs-nbt-json.md) for its largest consumer, the SNBT reader |
| `net/minecraft/client/animation` | 23 classes, 509 lines | absorbed for its five framework classes, declined for the sixteen pure-keyframe definitions | [entity rendering](../rendering/entity-rendering.md) |
| `net/minecraft/server/packs` | 55 classes, 4,975 lines | absorbed — mostly covered already, two corners owed a sentence each | [the resource system](../foundations/resource-system.md) |
| `net/minecraft/client/resources` | 101 classes, 7,612 lines | reframed — it is not one system, and five pages own its parts | [models and atlases](../rendering/models-and-atlases.md), [what makes a sound happen](../client/what-makes-a-sound.md), [entity rendering](../rendering/entity-rendering.md), [the HUD](../client/hud.md), [the resource system](../foundations/resource-system.md) |
| `com/mojang/blaze3d/vulkan` | 40 classes, 7,477 lines | declined — a faithful second implementation of an interface already documented, and the abstraction is the lecture | [Blaze3D](../rendering/blaze3d.md) |
| `net/minecraft/client/data` | 28 classes, 6,176 lines | declined — build-time model and atlas generators, the same category as the generator half of `net/minecraft/data`, but big enough that a reader trips over it | named here and nowhere else |
| the catalogues | ~230 mob models, ~73 particles, 101 render states, 50 render layers, 16 animation definitions, 61 of 63 worldgen features, 50 tree kits, the entity sub-predicates | declined — each is one shape repeated, and the shape is on the page that owns the framework | [the reference layer](../../reference/README.md) |
| `client/quickplay`, `client/profiling`, `client/renderer/gizmos` | a few classes each | declined — no mechanism a lecture needs | — |
| `net/minecraft/data/worldgen` as content | 56 classes, 5,369 lines | declined *as content*: it is the datagen bootstrap that emits vanilla's JSON | the runtime exceptions named above, which are not a decline |
Three of those rows need a sentence more. `client/animation` comes with a
warning owed to anyone who measures it, because *lines* is the wrong unit
for that package: 509 lines and 674 KB, with one file whose single longest
line is thirty thousand characters, because the decompiler renders each
animation as one builder chain. Four things inside `blaze3d/vulkan` are not
backend detail and are named before the decline — `GlslCompiler` and the
`vulkan/glsl` shaderc and spirv-cross pair, because Minecraft still authors
GLSL and cross-compiles it to SPIR-V, which is the whole reason one shader
source can feed two backends; `DestructionQueue`, the deferred-free
discipline OpenGL needs no equivalent of, which is the clearest illustration
of what the device seam hides; and `vulkan/checkpoints`, vendor breadcrumb
extensions for GPU crash reports. The interiors of `blaze3d/opengl` are
declined on the same grounds.
The `client/resources` reframing is the third. The old entry called it "the
client reload" with "no page owning the client half end to end", and that
overstates it: models and atlases, sound instances, skins, waypoint styles,
and the pack source, splashes, language and metadata all have owners. What
no page walks is the client *reload* as one sequence, which is a question
about the shape of the documentation rather than a hole in it. The one
substantively uncovered corner is `client/resources/server`, the
server-resource-pack prompt and download flow — and it pairs with the two
corners of `net/minecraft/server/packs` that the resource-system page owes a
sentence each: *linkfs*, a synthetic read-only file system that lets a
development checkout's scattered directories present as one pack root, and
`DownloadQueue` with `DownloadCacheCleaner`, the server-resource-pack
download queue and its cache eviction.
**Named, and not yet written.** These are real systems with real lectures
in them, found by the coverage sweeps and not covered by any ruling above:
the carver tunnel walk; the dragon fight (`EnderDragonFight`); the
advancements screen; and `client/multiplayer`'s joining-a-server tail. They
are named here so that a reader who wants one knows the book knows it is
missing, and knows where to start.
## Where to look
If you need one of these the entry points are named in each section; if you
want a *list* rather than a system, start at `net/minecraft/references` for
ids and the report providers in `net/minecraft/data` for everything else,
and the [atlas](../../maps/packages.md) for the shape of the whole jar.
---
*Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes `tools/verify_names.py`.*
---
# II · Foundations
> Verified against **Minecraft 26.2** · Part II · The machinery every later part assumes: how anything becomes data, gets a name and a number, is loaded, and reaches into code.
Part II is the vocabulary the other twelve parts speak without pausing to
define it. Nothing here is a thing a player does; everything here is what
happens underneath the things a player does. A player recognises this part
by its symptom: the square brackets after an item name in a `/give`, the
`#minecraft:logs` in a recipe file, the *type* line at the top of most of
the JSON files in a data pack, and the fact that a world made of JSON loads
in seconds.
## The shape of the part
Part II is not a stack but a fan. Codecs and registries are underneath
everything else here, and the five pages above them are largely independent
of one another — which is why the figure has two roots and no single column.
Watch it bottom-up all the same: every arrow points from the machinery to
what takes it for granted, and the last page is the pattern the rest exists
to make possible.
```mermaid
flowchart BT
C["Codecs, NBT and JSON: one object, four formats"]
R["Identifiers and registries: a name, a number, a Holder, and the freeze"]
S["The resource system: a stack of packs, a snapshot, prepare then apply"]
T["Tags: data-pack JSON reaching into hard-coded behaviour"]
D["Data components: a prototype on the registry entry, a patch on the stack"]
X["Text components: text as data, worded on the client"]
P["The data-driven type pattern: a type field is a lookup in a registry packs cannot extend"]
C -- "every data-pack registry element is decoded by a codec" --> R
R -- "dynamic registries are loaded from the pack stack" --> S
S -- "tags are read from the same stack, before the listeners" --> T
R -- "the tag table is swapped on a registry already frozen" --> T
R -- "prototypes bind onto Holder.Reference at reload" --> D
C -- "every persistent component value has a codec" --> D
C -- "ComponentSerialization holds the whole text matrix in one class" --> X
T -- "a tag-shaped HolderSet in any file" --> P
D -- "a component type is a key in a file, not a kind" --> P
R -- "the type registry is built-in, the elements come from the packs" --> P
```
## Before you start
[Anatomy](../anatomy/anatomy.md#four-threads-worth-memorising), for the
threads: registries are frozen
before either program exists, data-pack loading runs on the worker pool
with hops back to the owning thread, and a reload's *apply* phase runs on
whichever thread owns the state being replaced.
## Watch in this order
1. [Codecs, NBT and JSON](codecs-nbt-json.md) — one `ItemStack` written
four ways: into a chunk file, into a packet, as a checksum in a click,
and out of the text of a `/give`. The click sends no component data at
all, only hashes.
2. [Identifiers and registries](identifiers-and-registries.md) — how
`minecraft:diamond_sword` becomes an `Item` before the game exists, and
how a data-pack biome becomes a `Holder` the client is told about. The
wire id of a sword is the line number of its registration.
3. [The resource system](resource-system.md) — F3+T as a pipeline: a stack
of packs, a snapshot of the list, every listener preparing at once,
applying in order; `/reload` as the same pipeline on the server. On the
client a failed reload deselects every pack, not the bad one.
4. [Tags](tags.md) — `#minecraft:logs` from a JSON file to the set a parrot
checks before it perches. A frozen registry's contents never change, and
yet `/reload` changes what the tag contains.
5. [Data components](data-components.md) — the prototype an item type
supplies and the patch a stack carries. The prototype is built on every
reload, with the world's registries in hand, not in the constructor.
6. [Text components](text-components.md) — a death message built on the
server, sent as a translation key, and worded by the client's language
file. The client receives it before anyone knows what it says.
7. [The data-driven type pattern](data-driven-types.md) — the *type* field
at the top of a data-pack file is a lookup in a built-in registry of
kinds, and the fifty-six registries of that shape are why a pack can
compose the game's behaviours without adding one.
## Reference this part uses
[Math and primitives](../../reference/math-and-primitives.md) — the
coordinate spaces, packings, shapes and random sources every page assumes;
it was a Part II page and is now looked up, not watched.
[Registries](../../reference/registries.md) — every registry key: built-in,
data-pack, synced. [Data components](../../reference/components.md) — every
`DataComponentType`. [Naming drift](../../reference/naming-drift.md) —
`Identifier` was *ResourceLocation*. [Diagram lanes](../../reference/lanes.md).
The [glossary](../../reference/glossary.md) is worth more here than in any
other part: this is where most of the book's vocabulary is defined, and it is
the page to check when a later part uses one of these words in a second
sense.
---
*Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes `tools/verify_names.py`.*
---
# Codecs, NBT and JSON
> Verified against **Minecraft 26.2** · Part II · One `ItemStack` written four ways: into a chest's chunk file, into a container packet, as a checksum in a click, and out of the text of a `/give`.
A player types `/give @s diamond_sword[damage=5]`, drops the sword into a
chest, logs out, comes back and clicks the slot. In those few seconds the
same sword has been written four times into four different shapes: parsed
out of the square brackets, written into the chunk file the chest lives in,
sent down a socket as bytes, and sent back up when the slot was clicked.
The fourth is the one worth stopping on. **The click carries no component
data at all.** It names the item and the count in the clear —
`HashedStack.ActualItem` is a `Holder- `, an int and a hashed patch —
and for each component on the sword it sends one 32-bit checksum,
produced by running that component's own codec into a `DynamicOps` whose
output is a hash rather than a document — `HashOps`. And it is the same
codec every time. The codec that hashed the damage value is the codec that
wrote it into the chunk file and the codec that parsed it out of the square
brackets. One description of a type, and the format is an argument.
## The cast
| class | what it decides | thread |
|---|---|---|
| `Codec` | DataFixerUpper's description of a type: encode to and decode from *any* format, given the ops for it | any |
| `DynamicOps` | what a format's map, list, string and number are made of — the argument a codec takes | any |
| `NbtOps` | tags: the binary tree on disk, and what SNBT text parses into | any |
| `RegistryOps` | the registry lookup a `Holder`-valued codec needs, wrapped around another ops | any |
| `HashOps` | the format whose finished document is a hash; nothing is serialised on the way | Render on the client, Server on the comparison |
| `StreamCodec` | the exception: hand-laid bytes on a Netty `ByteBuf`, written once, read once | Netty |
| `TagValueOutput` · `TagValueInput` | the only `ValueOutput` and `ValueInput` there are — a `CompoundTag` plus its ops, and what save code actually sees | whichever thread saves |
| `ProblemReporter` | that a codec failure inside a save is a logged path, not an exception in the tick | the failing thread |
All of it ships in both jars. The thread column is the four of
[anatomy](../anatomy/anatomy.md#four-threads-worth-memorising), and which one
holds a codec matters only where the same codec runs on two of them.
## The four paths, side by side
| | into the chunk file | onto the wire | back as a checksum | out of the text |
|---|---|---|---|---|
| **who starts it** | `ChestBlockEntity.saveAdditional`, inside chunk serialisation | `ClientboundContainerSetSlotPacket`, from `AbstractContainerMenu.broadcastChanges` | `MultiPlayerGameMode.handleContainerInput`, on the click | `GiveCommand` through `ItemArgument` |
| **the ops** | `RegistryOps` over `NbtOps` | none — a `RegistryFriendlyByteBuf` and nothing else | `RegistryOps` over `HashOps.CRC32C_INSTANCE` | `RegistryOps` over `NbtOps`, held by a `TagParser` |
| **the codec** | `ItemStack.MAP_CODEC`, inside `ItemStackWithSlot.CODEC` | `ItemStack.OPTIONAL_STREAM_CODEC` | each component's own codec, through `TypedDataComponent.encodeValue` | `DataComponentType.codecOrThrow`, one component at a time |
| **what is carried** | a document — *id*, *count*, *components* | a count, an item id, then a `DataComponentPatch` | one int per added component, and the bare names of the removed ones | SNBT text, then a `Tag` |
| **the thread** | Server, then an IO worker for the file itself | Netty | Render on the client, Server on the comparison | Server |
| **when it fails** | a problem is recorded on a `ProblemReporter` and logged when the scope closes | the decoder throws, `Connection.exceptionCaught` sees it, and the connection drops | the hash disagrees, and the server sends the slot back | a `CommandSyntaxException` with the cursor position in it |
Four columns, four diagrams. Read them as four answers to the same question.
### Disk: a chest writes a list of slots
```mermaid
sequenceDiagram
participant CBE as ChestBlockEntity
participant CHelp as ContainerHelper
participant TVO as TagValueOutput
participant NbtIo as NbtIo
Note over CBE: the server thread, inside chunk serialisation
CBE->>TVO: saveWithFullMetadata, createWithContext with a ProblemReporter and the registries
CBE->>CHelp: saveAdditional hands the ValueOutput straight on
CHelp->>TVO: list Items with ItemStackWithSlot.CODEC, one entry per occupied slot
TVO->>TVO: ItemStack.MAP_CODEC writes id, count and components
TVO-->>CBE: buildResult gives a CompoundTag, and ScopedCollector.close logs any problem
Note over NbtIo: an IO worker, later
CBE->>NbtIo: SerializableChunkData through RegionFileStorage.write
```
`ItemStack` has no NBT method — there is no *save* and no *parse* on it.
`ChestBlockEntity.saveAdditional` receives a `ValueOutput` and calls
`ContainerHelper.saveAllItems`, which opens a typed list under *Items* with
`ItemStackWithSlot.CODEC`, a record of slot plus the stack's own
`ItemStack.MAP_CODEC` fields inlined. That map codec writes *id*, *count*
(defaulting to 1) and *components*, the last being
`DataComponentPatch.CODEC`, which spells a removal as *!minecraft:foo*.
`NbtUtils.addCurrentDataVersion` stamps the data version on the way out.
Load is the mirror: `BlockEntity.loadStatic` reads the id,
`ChestBlockEntity.loadAdditional` calls `ContainerHelper.loadAllItems` over
`ValueInput.listOrEmpty`.
### Wire: a count, an id and a patch
```mermaid
sequenceDiagram
participant PEnc as PacketEncoder
participant IStack as ItemStack
participant DCP as DataComponentPatch
participant PDec as PacketDecoder
Note over PEnc: Netty, clientbound
PEnc->>IStack: OPTIONAL_STREAM_CODEC, a varint count where anything non-positive means empty
IStack->>DCP: Item.STREAM_CODEC for the id, then STREAM_CODEC for the patch
DCP->>PEnc: added count, removed count, each type id with its value, then the removed ids
Note over PDec: Netty, serverbound, the creative slot alone
PDec->>IStack: validatedStreamCodec over OPTIONAL_UNTRUSTED_STREAM_CODEC
IStack->>IStack: re-encode through ItemStack.CODEC into NullOps, keeping only the errors
```
Nothing on this path is a `Codec`. `ItemStack.OPTIONAL_STREAM_CODEC` writes
a varint count where anything non-positive means empty and nothing else
follows, then a registry id that resolves because the buffer is a
`RegistryFriendlyByteBuf`, then `DataComponentPatch.STREAM_CODEC`.
`ItemStack.STREAM_CODEC` is the same codec that refuses an empty stack.
Serverbound is a different animal:
`ServerboundSetCreativeModeSlotPacket` uses `ItemStack.validatedStreamCodec`
over `ItemStack.OPTIONAL_UNTRUSTED_STREAM_CODEC`, where
`DataComponentPatch.DELIMITED_STREAM_CODEC` length-prefixes every component
value, and the decoded stack is then re-encoded through `ItemStack.CODEC`
into `NullOps` — output thrown away, only the errors kept — to prove that
the persistent codec would have accepted it.
### Checksum: a hash instead of a stack
```mermaid
sequenceDiagram
participant CPL as ClientPacketListener
participant HS as HashedStack
participant ACM as AbstractContainerMenu
Note over CPL: the client, once, when configuration ends
CPL->>CPL: createSerializationContext over HashOps.CRC32C_INSTANCE, a RegistryOps whose document is a hash
Note over HS: the render thread, on the click
CPL->>HS: create, one int per added component through TypedDataComponent.encodeValue
HS->>ACM: ServerboundContainerClickPacket, the changed slots and the cursor
Note over ACM: the server thread, after the click has been re-run
ACM->>ACM: RemoteSlot.Synchronized re-hashes the server's own stack and compares
```
`HashOps` is a `DynamicOps` like any other, and a codec cannot tell the
difference: it builds maps and lists and strings as usual, and what comes
back at the end is a hash code rather than a tree. There is no intermediate
byte form to hash. Only one instance is ever built,
`HashOps.CRC32C_INSTANCE`, and **both sides wrap it in a `RegistryOps`** —
`ClientPacketListener` builds one from the registries it received during
configuration, `ServerPlayer` from the server's own — because a component
value can name a registry entry, and a hash of an unresolvable name is no
hash at all. The server's is behind a 256-entry cache keyed on the
`TypedDataComponent`, so the common components are hashed once per player
and then looked up. Removals are not hashed: `HashedPatchMap` is a map of
added component type to int plus a plain set of removed types. What the
server does with the comparison — the either-or in
`RemoteSlot.Synchronized`, and the promotion to a concrete stack when the
hash agrees — is [containers and menus](../items/containers-and-menus.md).
### Text: square brackets into a `Tag`
```mermaid
sequenceDiagram
participant IP as ItemParser
participant TagP as TagParser
Note over IP: the server thread, while Brigadier parses the command line
IP->>TagP: create, over this parser's own RegistryOps on NbtOps
IP->>TagP: parseAsArgument at the opening bracket
TagP-->>IP: a Tag, read no further than its own closing brace
IP->>IP: DataComponentType.codecOrThrow parses that Tag into a DataComponentPatch.Builder
```
`ItemArgument` hands `GiveCommand` an `ItemInput`, and `ItemParser` is what
builds it. The parser holds a `RegistryOps` over `NbtOps.INSTANCE` and a
`TagParser` created *for that ops*, so `[damage=5]` becomes a `Tag` and
then goes through the very codec the chunk file used, reached by
`DataComponentType.codecOrThrow`. A leading `ItemParser.SYNTAX_REMOVED_COMPONENT`
is the command-line spelling of the same removal the disk codec writes.
Data packs are the JSON twin of this path:
`SimpleJsonResourceReloadListener.scanDirectory` takes whatever
`DynamicOps` it is handed, registry-aware or bare, and an `ItemStack` in a
loot table goes through `ItemStack.CODEC` exactly as it does on disk.
## One abstraction, and the ops that are not formats
A `Codec` is a description of a type and nothing else; it does not know
what it is writing into. The format is the `DynamicOps` handed to it at the
call, so the same object describes NBT on disk, JSON in a data pack, and
SNBT typed at a command line. Most of the game's codecs are assembled from
the combinators in `ExtraCodecs`, a thousand lines of vocabulary in
`net/minecraft/util`; the [class index](../../reference/class-index.md) is
where to look one of them up.
Two of the game's ops are not formats at all. `HashOps`, in the cast above,
answers every question a codec asks and returns a checksum instead of a
document. `NullOps`, which the cast does not list, returns `Unit`: it
encodes to nothing, and exists so
that a codec can be *run for its errors alone*, which is exactly what
`ItemStack.validatedStreamCodec` does to a creative-mode stack.
`StreamCodec` is the genuine exception, and it is not a `Codec` at all.
A `StreamCodec` pairs an encoder and a decoder over a `ByteBuf` directly,
with `StreamCodec.composite` building one out of field codecs; the
catalogue of primitives is `ByteBufCodecs`. A packet is written once, read
once and must be small, so it gets hand-laid bytes rather than a document
in some format. The two worlds meet at `ByteBufCodecs.fromCodec` and
`ByteBufCodecs.fromCodecWithRegistries`, which run an ordinary `Codec` into
NBT and put the tag on the wire, and at `IdDispatchCodec`, the packet-id
table itself.
## Where the registry context comes from
A codec that names an entry of a **dynamic** registry cannot resolve it on
its own. `RegistryFileCodec`, `RegistryFixedCodec` and `HolderSetCodec` all
demand a `RegistryOps`, a `DelegatingOps` carrying a
`RegistryOps.RegistryInfoLookup` beside whatever real ops it wraps. There
are two routes worth knowing: `HolderLookup.Provider.createSerializationContext`,
which is what nearly every caller uses, and `RegistryDataLoader.createContext`,
used during registry loading itself, when the registries are still being
built ([identifiers and registries](identifiers-and-registries.md#when-a-world-opens)). Both
end at `RegistryOps.create`, which a handful of callers reach directly.
A codec over a **built-in** registry is a different case:
`Registry.holderByNameCodec` — and so `Item.CODEC` — resolves against the
registry instance captured inside the codec, and works on bare
`NbtOps.INSTANCE`. The distinction is not academic. It decides which paths
must build a context before they can decode anything, and in practice
almost every real path must: `TagValueOutput.createWithoutContext` exists
and is called by nothing at all, and there is no context-free `ValueInput`
even in principle.
On the network the same context arrives as a decorator.
`RegistryFriendlyByteBuf` adds `RegistryFriendlyByteBuf.registryAccess` to a
`FriendlyByteBuf`, and `RegistryFriendlyByteBuf.decorator` is bound when the
protocol switches from configuration to play. Configuration packets have no
registry context — which is why registry data and tags are sent in that
phase as NBT and ids, and why the `ByteBufCodecs.holder` family is play-only
([protocol phases](../networking/protocol-phases.md)).
## What NBT actually is
`Tag` is a **sealed** interface, and the scalar leaves are records.
| branch | members | notes |
|---|---|---|
| `CompoundTag` | — | a final class; the only keyed shape |
| `CollectionTag` | `ListTag`, `ByteArrayTag`, `IntArrayTag`, `LongArrayTag` | the arrays are final classes, `ListTag` is a list |
| `PrimitiveTag` | `StringTag` and the six `NumericTag` records | records of one value each |
| `EndTag` | — | the terminator |
Because the leaves are records, the old *getAsInt* family is gone:
`Tag.asInt` and `Tag.asString` return `Optional`, `CompoundTag.getInt` is
`Optional` and `CompoundTag.getIntOr` is its defaulted form,
`CompoundTag.get` is still nullable, and the two-argument *contains* taking
a type id no longer exists.
Three things about the binary form surprise people.
**A mixed-type list is boxed on the way out.** Binary NBT stores one
element type per list, so `ListTag.write` promotes a heterogeneous list to
compounds and wraps each element in a one-entry compound under the empty
key, with `ListTag.addAndUnwrap` the exact inverse on read. This is not a
legacy artefact left lying around — it is written today, and it is the whole
reason mixed lists are legal at all.
**A numeric array stays an array, but nothing turns a list into one.**
`NbtOps.createCollector` hands back a `NbtOps.GenericListCollector` for an empty
list and only reaches for `NbtOps.ByteListCollector`, `NbtOps.IntListCollector` or
`NbtOps.LongListCollector` when it is handed an existing `ByteArrayTag`,
`IntArrayTag` or `LongArrayTag` — and those degrade back to the generic
collector the moment an element does not fit. A codec building a fresh list
gets a `ListTag`, whatever is in it; the arrays on disk are written by the
codecs that asked for arrays.
**A whole file need not be read to answer a question about it.**
`StreamTagVisitor` and the visitors beside it let `NbtIo.parse` pull a named
handful of fields out of a region chunk without materialising the chunk: a
`CollectFields` is built from the `FieldSelector`s the caller wants, two for
the `IOWorker` reading a chunk's data version and its blending data, three
for `StructureCheck`, which is how it and the world-list screen answer
without loading a world.
Every read that came from outside carries a budget. `NbtAccounter` is
charged as the per-type read strategy in `TagType` walks the stream, with
`NbtAccounter.DEFAULT_NBT_QUOTA` at 2 MiB,
`NbtAccounter.UNCOMPRESSED_NBT_QUOTA` at 100 MiB and a depth cap of
`NbtAccounter.MAX_STACK_DEPTH`; overrunning any of them raises an
`NbtException`. Region compression is a separate global:
`RegionFileVersion.configure` sets one process-wide selection from
*region-file-compression*, a `RegionFile` captures it once for **writing**,
but every chunk carries its own version byte and reads honour that — so one
world can hold chunks in several compressions at once.
`RegionFileVersion.VERSION_CUSTOM` (id 127) is the marker for a chunk
written with a compression the game does not name; the marker for a chunk
that grew too big and lives in its own external file is a different one —
`RegionFile`'s external-stream flag, 128, set in the version byte. And
`NbtIo.writeCompressed` (GZIP)
is for the standalone files, *level.dat* and player data.
The text form is `TagParser`, and it is generic over its *output* ops
rather than producing tags: SNBT can decode straight into any format's
target without a `CompoundTag` in between. `TagParser.parseCompoundFully`
is the plain-string entry point; `TagParser.FLATTENED_CODEC` accepts an
SNBT string only, and `TagParser.LENIENT_CODEC`, built as an alternative of
the two, is the one that takes a string *or* an object interchangeably.
Under it, `SnbtGrammar` is a packrat grammar and `SnbtOperations` supplies
the built-in *bool* and *uuid* functions.
## Save code never sees a `CompoundTag`
`ValueOutput` and `ValueInput` are the façade every `BlockEntity` and
`Entity` writes through — `ValueOutput.store` with a codec,
`ValueOutput.child`, `ValueOutput.list`, and typed getters with defaults on
the way back in (`ValueInput.getIntOr`, `ValueInput.getStringOr`). The only
implementations are `TagValueOutput` and `TagValueInput`, which wrap a
`CompoundTag` and a `DynamicOps`, with `ValueInputContextHelper` holding the
shared provider and the empty instances. The `CompoundTag`-returning methods
on `BlockEntity` are final shells that build one of these, and they differ
only in how much metadata they add: `BlockEntity.saveCustomOnly` none,
`BlockEntity.saveWithoutMetadata` the block entity's own *components* as a
full `DataComponentMap`, and `BlockEntity.saveWithFullMetadata` the id and
x, y and z. `BlockEntity.saveWithId` adds the id too, but only in the
`ValueOutput` form — it has no `CompoundTag` shell.
The point of the façade is that **an encode failure is reported, not
thrown**. Everything goes through a `ProblemReporter`, and
`ProblemReporter.ScopedCollector` logs the whole collected tree of problems
when it closes, rooted at `BlockEntity.problemPath` or `Entity.problemPath`;
`TagValueOutput.EncodeToFieldFailedProblem` and its siblings are what a bad
codec produces. A component that will not serialise costs you the component,
not the tick. The deliberate exceptions to the façade are `CustomData` and
`TypedEntityData`, the two components that carry a `CompoundTag` verbatim so
that data packs have an escape hatch
([data components](data-components.md#the-key-datacomponenttype)).
Migration is the one thing that runs before any of this. `DataFixTypes` is
the enum of every kind of file the game owns — `DataFixTypes.CHUNK`,
`DataFixTypes.PLAYER`, `DataFixTypes.LEVEL`, `DataFixTypes.OPTIONS` and the
rest — and each calls `DataFixTypes.updateToCurrentVersion` on the
DataFixerUpper `DataFixer` from `DataFixers.getDataFixer` before the codec is
shown the tag, using the data version `NbtUtils.addCurrentDataVersion`
stamped when the file was written. The fixes themselves are out of scope
here.
## Trusted, untrusted and validated
"Trusted" on this wire means *the server wrote it*, and it is a statement
about the read budget, not about direction. `ByteBufCodecs.TRUSTED_COMPOUND_TAG`
reads with an unlimited accounter and has exactly one call site,
`ClientboundBlockEntityDataPacket`; plain `ByteBufCodecs.COMPOUND_TAG` reads under
the 2 MiB default and carries `CustomData` and predicates.
`ByteBufCodecs.TRUSTED_TAG` also exists and has no call sites at all, so
build no mental model on it.
Serverbound defence is layered rather than singular, and the three layers
sit on three different packets. Full re-validation through
`ItemStack.validatedStreamCodec` is unique to the creative-mode slot.
`ServerboundCustomClickActionPacket` builds its own, much tighter
`NbtAccounter` and length-prefixes its payload. And the ordinary container
click sends no component data at all, only the hashes above — the strongest
defence of the three, because there is nothing to validate.
JSON is the format with the smallest footprint. It is the data packs, and
on the wire it survives in exactly two places, both outside the play phase:
`ClientboundStatusResponsePacket` and `ClientboundLoginDisconnectPacket`,
sent through `ByteBufCodecs.lenientJson`. That is why the game ships two
JSON parsers — `LenientJsonParser` for the wire and `StrictJsonParser` for
data packs. Chat text itself is NBT by the time it reaches the play phase:
`ComponentSerialization` holds that whole matrix in one class
([text components](text-components.md#serialisation-one-codec-three-shapes)).
## Questions players ask
**Why does an item look identical and not stack?** Because equality is
prototype plus patch, and the patch is what these codecs round-trip. Two
stacks that took different routes to the same components compare equal;
one that kept a component the other dropped does not, however the tooltip
reads.
**Why can a data pack put arbitrary NBT on an item at all?** Because
`CustomData` and `TypedEntityData` are components whose value *is* a
`CompoundTag`, passed through verbatim. Everything else on a stack has a
real codec and is checked by it.
**Why does a corrupt item vanish instead of crashing the world?** Because
the save façade reports rather than throws. The bad field becomes a problem
on a `ProblemReporter` and is logged when the scope closes, and the chunk
saves without it.
**Why is a creative-mode item the one thing the server double-checks?**
Because it is the only packet on which a client authors a whole stack.
Everywhere else the client either receives stacks or asserts hashes about
stacks the server already sent it.
**Why does a `/give` accept the same square brackets a chunk file uses?**
Because it is the same codec. `ItemParser` parses the text into a `Tag`
with `TagParser` and hands that tag to `DataComponentType.codecOrThrow` —
the codec `ItemStack.MAP_CODEC` reaches for when it writes *components* to
disk.
**Why can a world hold chunks in two different compressions?** Because the
compression setting is captured per `RegionFile` for writing only, and every
chunk stores the version byte it was written with. Reads honour the byte.
## Where to look
`Codec` · `DynamicOps` · `ExtraCodecs` · `RegistryOps` ·
`HolderLookup.Provider.createSerializationContext` · `Tag` · `CompoundTag` ·
`ListTag` · `NbtOps` · `NbtIo` · `NbtAccounter` · `TagParser` ·
`StreamTagVisitor` · `HashOps` · `NullOps` · `ValueOutput` · `ValueInput` ·
`TagValueOutput` · `ProblemReporter` · `BlockEntity` (the save shells) ·
`ContainerHelper` · `ItemStackWithSlot` · `ItemStack` (the codec fields) ·
`StreamCodec` · `ByteBufCodecs` · `RegistryFriendlyByteBuf` ·
`PacketEncoder` · `HashedPatchMap` · `ItemParser` · `DataFixTypes` ·
`RegionFileVersion`
---
*Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes `tools/verify_names.py`.*
---
# Identifiers and registries
> Verified against **Minecraft 26.2** · Part II · A player types `/give @s minecraft:diamond_sword`, and the sword reaches their inventory as the number of the line it was registered on.
A player types `/give @s minecraft:diamond_sword`. The string on the right
is parsed into an `Identifier`, the identifier is paired with
`Registries.ITEM` to make a `ResourceKey`, and the key is looked up in a
table the server froze at startup, before any world existed —
`BuiltInRegistries.ITEM`. The stack that then lands in the player's
inventory does not carry the name. `Item.STREAM_CODEC` is
`ByteBufCodecs.holderRegistry` over `Registries.ITEM`, and it writes an
integer. That integer is a line number. `MappedRegistry.byId` is an
insertion-ordered list, `MappedRegistry.register` appends to it, and an
entry's numeric id is its position — so the wire id of a diamond sword is
where its line falls in `Items`, reordering two lines in `Blocks` changes a
block's wire id, and a resource pack cannot. A dynamic registry, a biome
from a data pack, gets its numbers the other way: its elements are decoded
in parallel but registered in sorted order of their ids, so the server's
numbering does not depend on which file finished first. The client does not
re-derive those numbers at all — it registers what the server sent, in the
order the packet lists them.
## The cast
| class | what it decides | thread |
|---|---|---|
| `Identifier` | the name — namespace and path — and which strings are legal ones | any |
| `ResourceKey` | the name paired with the registry it belongs to, interned so equal keys are one object | any |
| `Registry` · `MappedRegistry` | the table: key to object to integer, the frozen flag, the two tag tables | written on the launching thread or a load task's worker; read from anywhere |
| `Holder` | a reference to an entry that may be handed out before the entry exists (`Holder.Reference`), or an inline value that belongs to no registry (`Holder.Direct`) | any |
| `HolderLookup.Provider` · `RegistryAccess` | the read-only view a codec resolves against: all the registries it may name | any |
| `BuiltInRegistries` | the static registries — created empty at class init, filled and frozen by `Bootstrap.bootStrap` | the launching thread, before any server or client object exists |
| `RegistryDataLoader` | the dynamic registries — one load task per registry, JSON from the packs on the server, NBT from the wire on the client | `Util.backgroundExecutor` |
| `LayeredRegistryAccess` | which layer may see which: four `RegistryLayer`s on the server, two `ClientRegistryLayer`s on the client | the server thread owns `MinecraftServer.registries`; the client thread owns its own stack |
All of this is server *and* client: `MappedRegistry`, `BuiltInRegistries`
and `RegistryDataLoader` ship in the dedicated server jar. Client-only, of
the classes this page names, are `ClientRegistryLayer`,
`RegistryDataCollector` and `KnownPacksManager` — and, less surprisingly,
`ClientPacketListener`, `ClientConfigurationPacketListenerImpl` and
`IntegratedServer`.
## The name
`Identifier`, in `net/minecraft/resources`, is a namespace and a path, and
it is the class a 1.21-era reader knows as *ResourceLocation*. It is not a
record: a final class with a private constructor whose validation is an
assertion on the trusted path and a real check on the parsing paths
(`Identifier.isValidNamespace`, `Identifier.isValidPath`). The two
character sets differ, and the difference bites: a **path** may contain
`/`, a **namespace** may not, and `..` is rejected outright as a namespace.
`Identifier.DEFAULT_NAMESPACE` is *minecraft*, and
`Identifier.withDefaultNamespace` supplies it — as does parsing a string
with no separator at all, or one that starts with the separator.
`Identifier.parse` throws `IdentifierException` (which lives in
`net/minecraft`, not beside `Identifier`) where `Identifier.tryParse`
returns null. `Identifier.read` exists twice, once returning a `DataResult`
for `Identifier.CODEC` and once over a Brigadier `StringReader`, with
`Identifier.readNonEmpty` beside it, and `Identifier.resolveAgainst`
carries a path-traversal guard. The one that surprises people:
**`Identifier.compareTo` orders by path first and namespace second**, so a
sorted list of ids is not grouped by mod.
A `ResourceKey` is an `Identifier` paired with the `Identifier` of the
registry it belongs to, and keys are **interned** through a weak map keyed
by `ResourceKey.InternKey`, so two keys for the same registry and id are
literally the same object. `Registries` holds the 148 `ResourceKey`s *of
registries* (`Registries.ITEM`, `Registries.BIOME` …) — 147 distinct
objects, for a reason the questions at the end explain — and `ItemIds` and
`BlockItemIds` (`net/minecraft/references`) hold the per-element keys the
static initialisers use.
## The table
`Registry` is the read interface — `Registry.getValue`, `Registry.getKey`,
`Registry.getId`, `Registry.getTags`, and the codecs `Registry.byNameCodec`
and `Registry.holderByNameCodec` — and it extends `IdMap`, so every registry
is also an int-to-object table. `WritableRegistry` adds
`WritableRegistry.register` and `WritableRegistry.bindTags`;
`DefaultedRegistry` answers a default entry — *air* for items and blocks —
instead of null. `MappedRegistry` is the one real implementation, with
`DefaultedMappedRegistry` its only subclass. It holds the same entries in
four indexes at once and every lookup direction is one of them:
`MappedRegistry.byKey` and `MappedRegistry.byLocation` from a name, the
insertion-ordered `MappedRegistry.byId` from a number, and
`MappedRegistry.byValue` from the object itself — an identity map that is
what answers `Registry.getKey`, with `MappedRegistry.toId` the parallel
identity map to the number. It carries the `MappedRegistry.frozen`
flag that `MappedRegistry.validateWrite` checks on every mutation.
A `Holder` is the seam between a registry and the code that names its
entries. It is a sealed interface with two kinds (`Holder.Kind`).
`Holder.Reference` is an entry *in* a registry — and is itself
`non-sealed`: it knows its `HolderOwner`, its key, its tags and its
components, and any of those may be unbound until the registry binds them
(`Holder.Reference.bindValue`, `Holder.Reference.bindTags`,
`Holder.Reference.bindComponents`). A reference is a promise:
`Holder.Reference.value` throws until `Holder.Reference.bindValue` has run,
codecs hand these out freely during a load, and the freeze is what makes
every promise kept — or fails loudly. `Holder.Direct` is a record wrapping
an inline value and a `DataComponentMap` that belongs to no registry: it has
no key, is in no tag, and serialises inline. A `HolderSet` is a set of
holders — `HolderSet.Named` is a tag, `HolderSet.Direct` a literal list.
What a codec sees is a read-only view. `HolderGetter`, `HolderLookup` and
`HolderOwner` are those views; `HolderLookup.Provider` is "all the
registries I may resolve against" and `HolderLookup.RegistryLookup` is one
of them. `RegistryAccess` is a `HolderLookup.Provider` over a set of
registries, and `RegistryAccess.Frozen` is a bare marker for the finished
kind. Every static initialiser writes through `Registry.register`; every
codec that names a *dynamic* entry — `RegistryFileCodec`,
`RegistryFixedCodec`, `RegistryCodecs.homogeneousList`, `HolderSetCodec` —
resolves through a `RegistryOps` ([codecs, NBT and JSON](codecs-nbt-json.md#where-the-registry-context-comes-from));
and at runtime `MinecraftServer.registryAccess` and
`ClientPacketListener.registryAccess` are where anything that must resolve a
key goes.
## Before the game exists
```mermaid
sequenceDiagram
participant Main
participant Boot as Bootstrap
participant BIR as BuiltInRegistries
participant Items as Items
participant Item as Item
participant DMR as DefaultedMappedRegistry
Note over Main,DMR: the launching thread, before any server or client object exists
Main->>Boot: bootStrap, early, after argument parsing: isBootstrapped is set before any registry is touched
Boot->>BIR: class init: one empty registry per built-in key, 95 of the 148 in Registries, each registered into WRITABLE_REGISTRY, each with a loader in LOADERS
Boot->>BIR: bootStrap, then createContents: run every loader
BIR->>Items: class init (the ITEM loader touches Items.AIR)
Items->>Items: registerItem(ItemIds.DIAMOND_SWORD, properties): Item.Properties.setId stores the key
Items->>Item: new Item(properties)
Item->>DMR: createIntrusiveHolder: a Holder.Reference with a value but no key yet
Items->>DMR: Registry.register, then WritableRegistry.register(key, item, BUILT_IN): bindKey, numeric id = byId.size()
BIR->>BIR: freeze: the root first, then every registry: bindBootstrappedTagsToEmpty, MappedRegistry.freeze
DMR->>DMR: freeze: bindValue on every holder, refuse if any holder or declared tag is unbound, build componentLookup
BIR->>BIR: validate: an empty registry logs, a DefaultedRegistry without its default throws
Note over Main,DMR: components are still unbound here, they are bound at the first reload, and tags at world load
```
Both `Main` classes do this early — after argument parsing and crash-report
preload, and after a handful of non-registry bootstraps
([anatomy](../anatomy/anatomy.md#from-main-to-a-world)) — on the launching thread, before any
server or client object exists. `Bootstrap.bootStrap` calls
`BuiltInRegistries.bootStrap`, and after it returns every built-in registry
is frozen and any `WritableRegistry.register` throws.
**Registries exist before their contents.** `BuiltInRegistries` class init
creates every registry empty and records the loader that fills it in
`BuiltInRegistries.LOADERS`, an insertion-ordered map. `Bootstrap.bootStrap`
then runs `BuiltInRegistries.createContents` — the loaders in that order.
By then `Items`, `Blocks` and `EntityTypes` are already initialised:
`Bootstrap.bootStrap` reaches `FireBlock.bootStrap`, `EntityTypes.PLAYER`
and `CauldronInteractions.bootStrap` before it calls
`BuiltInRegistries.bootStrap`, and each of those touches its catalogue.
`Bootstrap.checkBootstrapCalled` is the guard that makes "touched `Blocks`
from a static initialiser" a crash rather than a silent empty registry; it
works because the bootstrap flag is set *before* the registries are touched,
not after.
**The key travels in the properties.** `Items.registerItem` takes a
`ResourceKey` from `ItemIds` and calls `Item.Properties.setId` before
constructing, so an `Item` knows its own key at construction time. Blocks
do the same with `BlockItemIds` and `BlockBehaviour.Properties.setId`.
**Five registries hand the object its own holder.** `BuiltInRegistries.BLOCK`,
`BuiltInRegistries.ITEM`, `BuiltInRegistries.FLUID`,
`BuiltInRegistries.ENTITY_TYPE` and `BuiltInRegistries.BLOCK_ENTITY_TYPE`
are created with intrusive holders: the constructor asks the registry for a
`Holder.Reference` (`MappedRegistry.createIntrusiveHolder`) that wraps the
object before it has a key, and stores it in `Item.builtInRegistryHolder`.
Registration then binds the key to *that* holder rather than creating a
new one, so `Item.builtInRegistryHolder` and the registry's own holder are
the same object, and a tag check on a block or item is a set lookup on the
holder's own bound tag set with no registry hop
([tags](tags.md#from-json-to-a-parrots-decision)). `Holder.Reference.createIntrusive` is marked
deprecated — the mechanism is load-bearing but not encouraged.
**The numeric id is the line number.** `MappedRegistry.register` appends
to `MappedRegistry.byId`, and every static registration carries
`RegistrationInfo.BUILT_IN`. The wire id of an item is the position of its
line in `Items`, and `Item.STREAM_CODEC` encodes that integer.
**Freeze is a proof**, stated in full below. `BuiltInRegistries.freeze`
freezes the root registry first, then every registry it holds, and
`BuiltInRegistries.validate` closes the bootstrap: an empty registry is
logged, a `DefaultedRegistry` whose default key is missing throws.
## When a world opens
The server keeps its registries as a `LayeredRegistryAccess` in
`MinecraftServer.registries`, one layer per `RegistryLayer` —
`RegistryLayer.STATIC`, `RegistryLayer.WORLDGEN`,
`RegistryLayer.DIMENSIONS`, `RegistryLayer.RELOADABLE`, in that order —
with `MinecraftServer.registryAccess` the flattened view.
`LayeredRegistryAccess.getAccessForLoading` is everything *before* a layer,
which is what that layer's JSON may reference;
`LayeredRegistryAccess.compositeAccess` is everything. The client mirrors
this with two layers, `ClientRegistryLayer.STATIC` and
`ClientRegistryLayer.REMOTE`. On both sides the STATIC layer is
`RegistryAccess.fromRegistryOfRegistries` over `BuiltInRegistries.REGISTRY`
— a live view of the frozen root registry, not a copy of it.
```mermaid
sequenceDiagram
participant WL as WorldLoader
participant RDL as RegistryDataLoader
participant RMRLT as ResourceManagerRegistryLoadTask
participant LRA as LayeredRegistryAccess
participant SCPL as ServerConfigurationPacketListenerImpl
participant CCPL as ClientConfigurationPacketListenerImpl
participant RDC as RegistryDataCollector
Note over WL,LRA: world load, on the worker pool
WL->>WL: RegistryLayer.createRegistryAccess: STATIC filled from BuiltInRegistries.REGISTRY, three empty layers
WL->>RDL: load(resources, the lookups built from getAccessForLoading(WORLDGEN), WORLDGEN_REGISTRIES, backgroundExecutor)
RDL->>RMRLT: one RegistryLoadTask per RegistryData, every task's ConcurrentHolderGetter visible to every other
RMRLT->>RMRLT: FileToIdConverter.registry lists data/*/worldgen/biome/*.json, decode in parallel, register sorted by id, load and bind this registry's tags
RMRLT->>RMRLT: freezeRegistry, then the RegistryData's RegistryValidator
WL->>LRA: one replaceFrom(WORLDGEN, worldgen layer, dimensions layer): the dimensions layer is the WorldDataSupplier's finalDimensions
Note over WL,RDC: later, a client logs in and reaches the configuration phase: the server thread on the left, the client thread on the right
SCPL->>CCPL: ClientboundSelectKnownPacks: which packs do you already have?
CCPL->>SCPL: ServerboundSelectKnownPacks: accepted all-or-nothing
SCPL->>CCPL: ClientboundRegistryDataPacket, one per synced registry: RegistrySynchronization.packRegistries, entries from a known pack carry no data
SCPL->>CCPL: ClientboundUpdateTagsPacket: every static registry's tags plus the synced dynamic ones, as registry ints
SCPL->>CCPL: ClientboundFinishConfigurationPacket
CCPL->>RDC: collectGameRegistries: rebuild REMOTE with NetworkRegistryLoadTasks, missing data re-read from the local pack, static tags applied in place
RDC->>CCPL: a RegistryAccess.Frozen, into CommonListenerCookie.receivedRegistries
CCPL->>SCPL: ServerboundFinishConfigurationPacket: play may begin
```
`WorldLoader.load` runs `RegistryDataLoader.load` on
`Util.backgroundExecutor`, returning to the main thread for
resource-manager creation and the final assembly; this is where the
`RegistryLayer.WORLDGEN`, `RegistryLayer.DIMENSIONS` and
`RegistryLayer.RELOADABLE` layers are filled. The configuration phase is
the third moment: `SynchronizeRegistriesTask` sends the dynamic registries
on the server thread, and the client rebuilds its
`ClientRegistryLayer.REMOTE` layer in
`RegistryDataCollector.collectGameRegistries` — decoding on the worker
pool, joined on the client thread — before it will accept play packets.
**Layers load against the layers before them.** `RegistryDataLoader.load`
is given lookups built from `LayeredRegistryAccess.getAccessForLoading` —
built by `TagLoader.buildUpdatedLookups`, so that the static registries'
freshly read tags are visible to the worldgen codecs before they are
applied ([tags](tags.md#the-four-moments-tags-are-loaded)) — so a biome JSON may reference a placed feature
(same layer) or a sound event (`RegistryLayer.STATIC`) but never a level
stem (`RegistryLayer.DIMENSIONS`, which loads after). The lists
`RegistryDataLoader.WORLDGEN_REGISTRIES`,
`RegistryDataLoader.DIMENSION_REGISTRIES` and
`RegistryDataLoader.SYNCHRONIZED_REGISTRIES` say which keys belong to which
step and which subset the client is told about. Both worldgen and
dimensions are installed in a *single* `LayeredRegistryAccess.replaceFrom`
call, and the dimensions layer that wins is the world data's, not
necessarily the one just decoded — a saved world's dimension set survives.
**Loading is a task graph.** Each registry is one `RegistryLoadTask`
owning a fresh `MappedRegistry` and a lock-guarded
`ConcurrentHolderGetter`. `RegistryDataLoader.createContext` hands every
task's getter to every other, so `Biome.DIRECT_CODEC` decoding on one
worker can ask for a configured carver that another worker is still
registering — the getter returns an unbound `Holder.Reference`, and the
reference is bound when that registry freezes. Forward references cost
nothing; cycles are impossible because layers order the registries.
Fourteen of the forty-seven dynamic registries also carry a
`RegistryValidator` in their `RegistryDataLoader.RegistryData`, run after
the freeze — thirteen of them entity-variant registries running the same
check, `RegistryValidator.nonEmpty`, and the fourteenth `Registries.TIMELINE`,
whose `Timeline.validateRegistry` is its own.
**Provenance is recorded per entry, and it is coarser than it looks.**
`ResourceManagerRegistryLoadTask` gives each element a `RegistrationInfo`
naming the `KnownPack` it came from and a `Lifecycle`. The rule is
*presence*, not vanilla-ness: an element from **any** pack that reports a
`KnownPack` is stable, and only an element from a pack with no known-pack
info is experimental. `KnownPack.isVanilla` is computed on that path and
then discarded. Anything received over the network is experimental, and
the whole `RegistryLayer.RELOADABLE` layer is constructed experimental; the
registry's own lifecycle is the merge of its entries', and that merge is
what the "experimental features" warning on world open reads.
**The client is told what it does not already have.**
`SynchronizeRegistriesTask` first asks the client which `KnownPack`s it has
(`ClientboundSelectKnownPacks`). The comparison is **all or nothing**: the
client's answer must equal the request exactly, or every element of every
synced registry is sent in full. On a match,
`RegistrySynchronization.packRegistries` sends every element's *id* but
leaves the NBT payload empty for entries from those packs, and the client's
`RegistryLoadTask.PendingRegistration.findAndLoadFromResource` re-decodes
the JSON from its own jar. A modified biome from a custom data pack is sent
in full through `Biome.NETWORK_CODEC` — the network codec, which omits the
generation and mob-spawn settings the client never needs.
**The client rebuilds one layer and patches the other.**
`RegistryDataCollector` accumulates the packets, and
`RegistryDataCollector.collectGameRegistries` runs when configuration
finishes. The `ClientRegistryLayer.REMOTE` layer is rebuilt wholesale and
frozen — but the **static** registries cannot be rebuilt, so their tags are
applied in place, through the mechanism [tags](tags.md#the-four-moments-tags-are-loaded) owns, and when no
registry data arrived at all the collector takes a tags-only path that
patches and returns the original registries untouched. The result is a
`RegistryAccess.Frozen` in `CommonListenerCookie` that every
`RegistryFriendlyByteBuf` in the play phase decodes against.
**Singleplayer throws most of that away.** When an `IntegratedServer`
exists, `ClientConfigurationPacketListenerImpl.handleConfigurationFinished`
substitutes the server's own registries for the ones the client just built,
and the memory connection suppresses re-applying static tags and components
client-side. Both halves then share the same registry objects — which is
worth remembering whenever a page says "the client's copy".
## The freeze rule, stated
A frozen registry's **contents** never change. Its **tags** and its
elements' **components** do. Everything in this part that looks like an
exception to the first sentence is one of the two things in the second.
`MappedRegistry.freeze` is a proof, not a switch. It binds every holder's
value and throws if any holder is still unbound, if any intrusive holder
was created but never registered, or if any tag declared by a `TagKey` was
never bound. The tag half of that proof works because a registry keeps its
*declared* tags apart from its *bound* ones, which is the split
[tags](tags.md#a-tag-is-a-key-and-a-file) is built on: the freeze checks the
declared table and installs the bound one. For the static registries the
real tags do not exist until a data pack is read, so `BuiltInRegistries.freeze`
first binds the tags the bootstrap actually asked for to empty
(`MappedRegistry.bindAllTagsToEmpty`) and the proof passes on empty sets.
The freeze also builds `MappedRegistry.componentLookup`. After it, the
`MappedRegistry.frozen` flag makes `MappedRegistry.validateWrite` throw on
every ordinary write. Two things still change: `MappedRegistry.prepareTagReload`
*requires* the frozen flag, and the component prototypes are rebound beside
the tags ([data components](data-components.md)).
What changes afterwards changes through two doors. **Tags:** a world load
swaps the tag tables of the static registries, and `/reload` does more
than refill the `RegistryLayer.RELOADABLE` layer — it re-reads and
re-applies tags for **every** registry in the server's composite access.
How a frozen registry's tags are swapped is the pay-off of
[tags](tags.md#from-json-to-a-parrots-decision), and the mechanics of the reload
itself belong to [the resource system](resource-system.md#reload-the-same-pipeline-on-the-server). **Components:** every registry
element's `DataComponentMap` is bound after the freeze by
`Holder.Reference.bindComponents`, and `/reload` rebinds every one of them;
[data components](data-components.md#the-prototype-and-why-it-is-built-at-reload) owns how.
## Feature flags: the same registry, narrowed
A frozen registry's contents never change — but what a *lookup* will show you
can be narrower than what the registry holds, and that is the whole of the
feature-flag mechanism. `FeatureFlagSet` is a bitmask: a 64-bit *long* and the
`FeatureFlagUniverse` it belongs to, with `FeatureFlagSet.MAX_CONTAINER_SIZE`
at 64 flags. There is one universe, *main*, and `FeatureFlags` declares four
flags in it — `FeatureFlags.VANILLA` and the three experiments,
`FeatureFlags.TRADE_REBALANCE`, `FeatureFlags.REDSTONE_EXPERIMENTS` and
`FeatureFlags.MINECART_IMPROVEMENTS`. A set that is not a subset of
`FeatureFlags.VANILLA_SET` is what `FeatureFlags.isExperimental` reports, and
what puts the warning on a world.
What carries a flag is a registry element. `FeatureElement` is an interface
with one method, `FeatureElement.requiredFeatures`, implemented by exactly
seven types — `Item`, `BlockBehaviour`, `EntityType`, `GameRule`, `MenuType`,
`Potion` and `MobEffect` — and `FeatureElement.FILTERED_REGISTRIES` names the
seven registries those live in. `HolderLookup.RegistryLookup.filterFeatures`
is the filter: handed the world's enabled set, it returns the same lookup
untouched for any registry not in that set of seven, and a delegating lookup
that hides the disabled elements for one that is. The registry underneath is
not touched, and neither is its numbering — a disabled item keeps its wire id.
That is why an experiment is enabled per world and not per install. The set
is `WorldDataConfiguration.enabledFeatures`, read from *level.dat*, and the
filtered lookup is what `CommandBuildContext` hands every argument type, so
`/give` cannot name a disabled item and `/setblock` cannot name a disabled
block; `GameRules` builds its map from the filtered `BuiltInRegistries.GAME_RULE`,
so a disabled rule is not merely refused but absent; and `LevelReader.holderLookup`
filters for anything that asks a level for a registry. Where the flags come
*from* is the pack stack — a feature pack is a built-in pack carrying a
`FeatureFlagsMetadataSection`, and turning on an experiment is enabling a
data pack ([the resource system](resource-system.md#discover-the-repository-and-its-packs)).
## What crosses the wire, and where the files are
Built-in registry *elements* never cross the network — both sides ran the
same static initialisers — but their *tags* do, and dynamic elements do:
`ClientboundSelectKnownPacks` and `ServerboundSelectKnownPacks`, then
`ClientboundRegistryDataPacket` (one per synchronised registry, entries as
`RegistrySynchronization.PackedRegistryEntry`) in the configuration phase,
then `ClientboundUpdateTagsPacket`, which is a **common** packet and arrives
again mid-play after a server `/reload`; and every registry element,
built-in or dynamic, crosses inside other packets as a bare varint id
resolved against the buffer's registry access. Only one variant shifts that
numbering: `ByteBufCodecs.holder` reserves 0 for an inline `Holder.Direct`
and writes every registry id one higher, where `ByteBufCodecs.holderRegistry`
— which `Item.STREAM_CODEC` uses — writes the raw id. On disk, every key in
`RegistryDataLoader.WORLDGEN_REGISTRIES` and
`RegistryDataLoader.DIMENSION_REGISTRIES` reads
`data///*.json` (`Registries.elementsDirPath`)
through `FileToIdConverter.registry` over a `ResourceManager`
([the resource system](resource-system.md#snapshot-the-manager)), its tags live under
`Registries.tagsDirPath` — there is a third path builder,
`Registries.componentsDirPath`, but it names a *reports* directory the data
generator writes and nothing in the running game reads — and the reloadable set (loot
tables, predicates, item modifiers) comes through
`ReloadableServerRegistries`. Which registry is which kind is
[reference/registries](../../reference/registries.md).
## Questions players ask
**Are `Registries.DIMENSION` and `Registries.LEVEL_STEM` two registries?**
They are the same object. Both are created from the string "dimension", and
because `ResourceKey` interns, the two fields hold one interned key under
two names and two (unchecked) element types. `Registries.LEVEL_STEM` is the
data-pack registry the `RegistryLayer.DIMENSIONS` layer loads;
`Registries.DIMENSION` keys the `ServerLevel`s; the conversion helpers
between them are identity functions at runtime. That is why `Registries`
declares 148 keys and holds 147 objects.
**Does interning matter?** Where identity is used, and only there.
`MappedRegistry.byKey` and `MappedRegistry.byLocation` are ordinary hash
maps. What genuinely depends on interned keys is
`MappedRegistry.registrationInfos`, an identity map, and
`Holder.Reference.is` for a `ResourceKey`, which is a reference comparison.
**Where does the number come from?** Two different places. For
`BuiltInRegistries` it is an accident of source order —
`MappedRegistry.byId` insertion order, so reordering two lines in `Blocks`
changes a block's wire id and a resource pack cannot. For a **dynamic**
registry it is the element ids in sorted order:
`ResourceManagerRegistryLoadTask` decodes in parallel but registers sorted,
which is exactly why the client can rebuild the same ids from the same
element list. `MappedRegistry.toId` is keyed by *value* identity and
returns −1 for anything it has never seen, including an equal-but-distinct
object.
**Are components part of the freeze?** No. `Holder.Reference.bindComponents`
attaches a per-entry `DataComponentMap` built by
`BuiltInRegistries.DATA_COMPONENT_INITIALIZERS` — on the server during a
reload, on the client at the end of configuration. Do not confuse that with
`MappedRegistry.componentLookup`, which is a `DataComponentLookup` built
*at* freeze: a lazily-populated **reverse** index answering "which elements
have this component value?", used by things like finding the spawn egg for
an entity type ([data components](data-components.md#the-reverse-index-datacomponentlookup)).
**What does a `RegistrationInfo` say?** Per entry, a `Lifecycle` and the
`KnownPack` it came from; `RegistrationInfo.BUILT_IN` is what every static
registration gets.
**Why does a holder from one world refuse to be written by another?**
`HolderOwner` exists for one question — `HolderOwner.canSerializeIn` — and
that is it: a holder answers whether the context asking to serialise it is
its own owner.
**Is the vanilla data built at runtime?** No. `RegistrySetBuilder`,
`BootstrapContext` and `VanillaRegistries` are the data generator that
*writes* the JSON in the jar; the running game only ever reads JSON. A
1.21 reader who remembers biomes being registered in code is remembering
datagen.
**Is `Block.BLOCK_STATE_REGISTRY` a registry?** No. `IdMapper` is the
standalone `IdMap` used for `BlockState` ids and similar palettes; the two
share an interface and nothing else.
## Where to look
`Identifier` · `ResourceKey` · `Registries` · `Registry` · `MappedRegistry.register` ·
`MappedRegistry.freeze` · `DefaultedMappedRegistry` · `Holder` · `HolderSet` ·
`HolderLookup` · `Bootstrap.bootStrap` · `BuiltInRegistries.bootStrap` ·
`RegistryLayer` · `LayeredRegistryAccess` · `WorldLoader.load` ·
`RegistryDataLoader.load` · `RegistryLoadTask` ·
`ResourceManagerRegistryLoadTask` · `RegistryValidator` ·
`RegistrySynchronization.packRegistries` · `SynchronizeRegistriesTask` ·
`RegistryDataCollector.collectGameRegistries` · `NetworkRegistryLoadTask` ·
`ClientConfigurationPacketListenerImpl.handleConfigurationFinished` · `RegistryOps`
---
*Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes `tools/verify_names.py`.*
---
# The resource system
> Verified against **Minecraft 26.2** · Part II · A player presses F3+T, the screen goes to the logo and a bar, and every texture, model, sound and font is rebuilt from a stack of packs without the game stopping.
A player presses F3+T. The screen goes red, the Mojang Studios logo comes
up, and a white bar creeps across under it while the old world keeps
rendering behind. What is happening is one pipeline that everything the
game reads from a file goes through — textures, models, sounds, language
strings, recipes, advancements, loot tables, tags, worldgen JSON: a **stack
of packs** is discovered, merged into a **resource manager** that is a
snapshot of the stack, and a list of **reload listeners** each rebuild
their world from it, every one of them reading on the shared worker pool at
once ([anatomy](../anatomy/anatomy.md#four-threads-worth-memorising)) and
swapping their live state on the owning thread in the order they were
registered. The client's stack is resource packs (`PackType.CLIENT_RESOURCES`,
the *assets* tree); the server's is data packs (`PackType.SERVER_DATA`, the
*data* tree) — same classes, two instances, two directories, and `/reload`
is the same pipeline run by the server. The surprising part is the end. A
reload that fails does not find the offending pack.
`Minecraft.rollbackResourcePacks` deselects *every* resource pack, clears
the options lists, saves, and reloads again — and if vanilla was the only
selected pack it rethrows and crashes instead.
## The cast
| class | what it decides | thread |
|---|---|---|
| `PackRepository` · `Pack` | which packs exist, which are selected, and in what order | whoever asks: Render on the client, Server on the server |
| `MultiPackResourceManager` · `FallbackResourceManager` | which pack's copy of a file wins, one stack per namespace | built on the asking thread, read from workers |
| `ReloadableResourceManager` | the client's long-lived façade: the current snapshot and the listener list | Render |
| `PreparableReloadListener` · `SimplePreparableReloadListener` · `SimpleJsonResourceReloadListener` | what to read off-thread and what to swap on-thread | prepare on the worker pool, apply on the owner |
| `SimpleReloadInstance` | the schedule: every prepare at once, every apply in order behind a barrier | built on the caller's thread, barriers resolved on the owner |
| `LoadingOverlay` | when the client's reload is done, and what to do if it failed | Render |
| `ReloadableServerResources` | the server's three listeners, and the registries that replaced the rest | Server |
## The pipeline
```mermaid
flowchart LR
D["discover: PackRepository.reload re-runs every RepositorySource and rebuilds the selection"] --> S["snapshot: a new MultiPackResourceManager over the opened packs, a snapshot of the list, not of the bytes"]
S --> P["prepare: every listener reads on the worker pool at once"]
P --> A["apply: each listener swaps its live state on the owning thread, in registration order, behind a PreparationBarrier"]
A --> F["finish: checkExceptions, then the level re-extracted or the server's managers installed"]
A --> R["roll back: every pack deselected, the reload run again"]
```
Five stages, and the rest of the page is one section per stage: what comes
in, what is decided, what goes out. F3+T is the grounding trace; `/reload`
is the coda, as a table of where the server's run of the same pipeline
differs.
## Discover: the repository and its packs
What comes in is a set of `RepositorySource`s (`server/packs/repository`),
each a place packs are found. `ClientPackSource` and `ServerPacksSource`
are the built-ins, both extending `BuiltInPackSource`, which also lists the
packs bundled *inside* the vanilla pack — the art packs, the accessibility
packs and every feature pack (`BuiltInPackSource.TESTS_ID` is declared and
referenced nowhere, so the *tests* pack is a development leftover); `FolderRepositorySource` is a
directory of user packs; `DownloadedPackSource` is server-sent packs, client
only. `PackRepository.reload` re-runs every source into the *available*
map and then rebuilds the *selected* list: prior choices are kept, and a
pack whose `Pack.isRequired` is true is force-inserted at its
`Pack.getDefaultPosition`. Reading the order out of *options.txt* is a
startup step — `Options.loadSelectedResourcePacks` runs once in the
`Minecraft` constructor, not on every F3+T.
A `Pack` is a discoverable pack: a `PackLocationInfo` (id, title,
`PackSource`, optional `KnownPack`), a `Pack.ResourcesSupplier` that can
open it, its `Pack.Metadata` (description, `PackCompatibility`, requested
feature flags, overlays) and a `PackSelectionConfig` — required, default
`Pack.Position`, fixed. `Pack.Position` owns the insertion algorithm that
makes a fixed pack stick: `Pack.Position.BOTTOM` inserts at the front of
the list, past any pack already fixed there, and `Pack.Position.TOP` at the
back. The last pack in the list wins (next section), which is why vanilla
is BOTTOM and why "higher in the UI" means "later in the list". The
client's vanilla pack cannot be deselected and the server's can:
`ClientPackSource` marks vanilla required and bottom; `ServerPacksSource`
marks it bottom but optional.
What a pack *is* on disk is `PackResources` (`server/packs`): the raw file
source with `PackResources.getResource`, `PackResources.listResources` and
`PackResources.getNamespaces`. `VanillaPackResources` is the jar's own
assets and data; `FilePackResources` a zip; `PathPackResources` a
directory; `CompositePackResources` a pack plus its *overlays*
subdirectories, which the `Pack.ResourcesSupplier` assembles for zip and
folder packs (the vanilla pack never produces one). Discovery is guarded:
`DirectoryValidator`, `ForbiddenSymlinkInfo` and `PackDetector` decide
what a folder is allowed to be, `allowed_symlinks.txt` is parsed into a
`DirectoryValidator` by `LevelStorageSource.parseValidator`. Two corners of
`server/packs` are worth a sentence each. `packs/linkfs` is a synthetic
read-only file system — `LinkFileSystem`, `LinkFSProvider` and a `LinkFSPath`
that is a name in a tree rather than a name on disk — which lets a
development checkout's scattered directories present as one pack root, so
the game can open a pack that was never assembled. And `DownloadQueue` is
the client's cache for server-sent packs: one directory per pack UUID under
a cache root, downloads run one at a time on a `ConsecutiveExecutor` over
`Util.nonCriticalIoPool`, every attempt appended to a `JsonEventLog` beside
them, and the constructor calls `DownloadCacheCleaner.vacuumCacheDir` to
trim the root to `DownloadQueue.MAX_KEPT_PACKS` — twenty files, newest kept,
one per directory before any directory's second. A server you visited
twenty packs ago has been evicted.
### What *pack.mcmeta* says
The file is read as `ResourceMetadata` sections: `PackMetadataSection`
(description and a `PackFormat` range), `FeatureFlagsMetadataSection`,
`OverlayMetadataSection`, `ResourceFilterSection`. Compatibility is a
range, not a number. `PackMetadataSection` carries an inclusive range of
`PackFormat` major/minor pairs and `PackCompatibility` reports too old, too
new, unknown or compatible against the game's own — resource **88.0** and
data **107.1** in 26.2. Above `PackFormat.lastPreMinorVersion` (64 for
assets, 81 for data) the *min_format* / *max_format* fields are mandatory
and the old integer *pack_format* is not enough. A *pack.mcmeta* the strict
codec rejects gets one more chance through a description-only fallback so
the pack can at least be listed as incompatible. Overlays are versioned
sub-packs: `OverlayMetadataSection` maps a `PackFormat` range to an
overlays subdirectory that `CompositePackResources` layers on top of the
pack itself, so one zip can carry variants for several game versions.
Feature flags are packs, but not auto-selected ones. A feature pack is a
built-in pack carrying a `FeatureFlagsMetadataSection`, and its
`PackSource` deliberately reports that it must *not* be added
automatically. `MinecraftServer.enableForcedFeaturePacks` force-selects the
ones matching the world's forced features, and the world's flag set is the
selected packs' requested flags joined with those forced ones. Turning on
an experiment is enabling a data pack — through a different door than
ordinary packs use. What the resulting `FeatureFlagSet` then *gates* is a
registry lookup, and that is
[identifiers and registries](identifiers-and-registries.md#feature-flags-the-same-registry-narrowed).
What goes out of the stage is `PackRepository.openAllSelected`: the
selected `Pack`s opened into a list of `PackResources`, in order.
## Snapshot: the manager
`MultiPackResourceManager` (`server/packs/resources`) is built from that
list. It is a **snapshot of the pack list, not of the bytes**: it asks each
pack for its namespaces and builds one `FallbackResourceManager` per
namespace, each a stack searched from the **last** selected pack down. The
old world stays up until the last apply, but the old files do not: on the
client, `ReloadableResourceManager.createReload` closes the previous
`MultiPackResourceManager` — with every file handle it held — before
building the new one. What is frozen is *which packs are in the stack*; a
`Resource` still opens its file when it is read, so a **folder** pack
edited on disk mid-reload is observable. A zip is not: `FilePackResources`
holds its zip open for the life of the pack.
A `Resource` is what a lookup returns: its source pack, an `IoSupplier` for
the bytes — opened lazily, at read time — and a lazily-read
`ResourceMetadata` found beside it. A `.mcmeta` is looked for in the
winning pack or those above it, never in one below, so a pack overriding a
texture without its `.mcmeta` loses the animation. `ResourceFilterSection`
lets a pack *hide* lower packs' files by pattern without providing
replacements: `MultiPackResourceManager` reads each pack's filter section
and pushes it onto the namespace stacks as a filter, and a lookup that
reaches a filtered entry stops there. Some loaders want every copy, not
the winner: `ResourceManager.getResourceStack` and
`ResourceManager.listResourceStacks` return all packs' copies
**bottom-first**, which is how languages, tags and atlas sources merge
instead of overriding.
Two more managers frame this one. `ReloadableResourceManager` is the
long-lived client façade that holds the current snapshot and the
`PreparableReloadListener` list; the server has no façade — each reload is
a fresh `MultiPackResourceManager` inside
`MinecraftServer.ReloadableResources`. `ResourceManager.Empty` is the
do-nothing manager handed to code that must run without packs.
What goes out is one `ResourceManager`, wrapped in a
`PreparableReloadListener.SharedState`, and a `ReloadInstance` that has
already started.
## Prepare: every listener at once
A `PreparableReloadListener.reload` takes the shared state (which carries
the `ResourceManager`), a background executor, a
`PreparableReloadListener.PreparationBarrier` and a main-thread executor.
`SimplePreparableReloadListener` splits that into
`SimplePreparableReloadListener.prepare` (background) and
`SimplePreparableReloadListener.apply` (main thread);
`SimpleJsonResourceReloadListener` is the "every JSON file in a directory
through one codec" specialisation, using `FileToIdConverter` to map
*data/ns/recipe/foo.json* to *ns:foo*; `ResourceManagerReloadListener` is
the apply-only shape.
`SimpleReloadInstance` is the schedule, and this is what it does, read
from `SimpleReloadInstance.prepareTasks`. It wraps both executors in
counters. It calls `PreparableReloadListener.prepareSharedState` on every
listener first, synchronously. Then it walks the listener list once,
calling each listener's `PreparableReloadListener.reload` and handing it a
barrier chained to the *previous* listener's returned future (the first
listener's barrier is chained to the initial task). The barrier's
`PreparableReloadListener.PreparationBarrier.wait` does two things: it
posts a task to the main-thread executor that removes the listener from
the set still preparing and completes the all-preparations future when
that set empties, and it returns that future combined with the previous
listener's. So listener N's apply runs only after *every* listener has
reached its barrier *and* listener N−1 has finished entirely — apply
included — and a listener that never reaches its barrier holds every apply
behind it. The futures are sequenced fail-fast: the first listener to throw
fails the reload as a whole. It does not stop the others — `Util.sequenceFailFast`
completes the outer future exceptionally and leaves every prepare running;
`Util.sequenceFailFastAndCancel`, which would cancel them, is not the one
used here. What never happens is the applies.
```mermaid
flowchart LR
subgraph PREP["prepare, on the worker pool, all at once"]
TMp["TextureManager: read every ReloadableTexture"]
AMp["AtlasManager: stitch every atlas, completing the futures published under PENDING_STITCH"]
MMp["ModelManager: load models and block states, then join the block and item stitches from PENDING_STITCH"]
end
ALL["all preparations: every listener has reached its barrier"]
TMp --> ALL
AMp --> ALL
MMp --> ALL
AMp -. "shared state" .-> MMp
subgraph APP["apply, on the owning thread, in registration order"]
TMa["TextureManager apply: swap texture contents"]
AMa["AtlasManager apply: upload the atlases"]
MMa["ModelManager apply: install the baked models"]
end
ALL --> TMa
ALL --> AMa
ALL --> MMa
TMa --> AMa
AMa --> MMa
```
Three of the client's twenty listeners, the ones registered between them
elided. Every apply waits on the all-preparations node; each apply also
waits on the apply before it; and the one dotted edge is the only way one
listener's prepare depends on another's.
### The shared-state channel
`PreparableReloadListener.prepareSharedState` is a separate first pass for
a reason: it is the one place a listener can publish something for
*another* listener's prepare to consume, keyed by a
`PreparableReloadListener.StateKey`. The game declares exactly one —
`AtlasManager.PENDING_STITCH`. `AtlasManager` publishes a future per atlas
there before any prepare starts; `ModelManager` and `ParticleResources`
pull the pending sprite futures out of it and join them **inside their own
prepare**, so model baking overlaps atlas stitching rather than queueing
behind it. This is why the model/atlas dependency is *not* an apply-order
dependency, and why reasoning about it from the registration list gets
the wrong answer. Which thread runs that first pass depends on who started
the reload: the Render thread on the client, but a worker on the server,
because the server's reload instance is created from inside an
already-async chain.
Prepare never touches live state. That is the whole contract, and it is
one-directional: a listener that reads from the manager in
`SimplePreparableReloadListener.apply` is reading the *new* snapshot and
that is fine (`TextureManager` does exactly this, from its own
`PreparableReloadListener.reload`), while one that mutates
live state in `SimplePreparableReloadListener.prepare` is racing the
Render thread. Nothing a listener owns is torn down when a reload starts;
the client keeps rendering with the old atlases while the new ones bake.
## Apply: registration order
Registration order is apply order. The client registers, in order,
`LanguageManager`, `TextureManager`, `ShaderManager`, `SoundManager`,
`AtlasManager`, `FontManager`, the three colour listeners
(`GrassColorReloadListener`, `FoliageColorReloadListener`,
`DryFoliageColorReloadListener`), `ModelManager`, `EquipmentAssetManager`,
`EntityRenderDispatcher`, `BlockEntityRenderDispatcher`,
`ParticleResources`, `LevelExtractor`, the cloud renderer,
`GpuWarnlistManager`, a `PeriodicNotificationManager`, then `SplashManager`
from `Gui` and `WaypointStyleManager` from `Hud` — twenty in all. On the
client `ReloadableResourceManager.createReload` is called with
`Util.backgroundExecutor` (named *resourceLoad*) and `Minecraft` itself as
the main-thread executor, so apply runs on the Render thread, interleaved
with frames. On the server `ReloadableServerResources.loadResources` is
called with `Util.backgroundExecutor` and `MinecraftServer`, so apply runs
on the Server thread.
The counters `SimpleReloadInstance` wrapped the executors in are where the
progress bar's numbers come from: `ReloadInstance.getActualProgress`
weighs prepare and apply tasks double and listeners-completed single, and
the overlay smooths it.
## Finish, or roll back
On the client `LoadingOverlay` is a poll. It draws the logo from the
vanilla pack *outside* the reload (via `VanillaPackResources.asProvider`)
and a smoothed bar from `ReloadInstance.getActualProgress`; a manual reload
fades it in over half a second and it will not fade out until a full second
has passed. Each tick, once `ReloadInstance.isDone`, it calls
`ReloadInstance.checkExceptions` and hands the result to its finish
callback. Success runs `LevelExtractor.allChanged`, which is why every
chunk section rebuilds after F3+T, then `ResourceLoadStateTracker.finishReload`,
`DownloadedPackSource.onReloadSuccess` and `Minecraft.onResourceLoadFinished`.
Failure runs `Minecraft.rollbackResourcePacks`, which does **not** find the
offending pack — it deselects *every* resource pack, clears the options
lists, saves, and reloads again, and if vanilla was the only selected pack
it rethrows and crashes instead. That recovery reload bypasses the
one-at-a-time guard, skips the fade, and if *it* fails the client abandons
recovery: `Minecraft.abortResourcePackRecovery` drops the overlay,
disconnects any level and returns to the title screen with a failure
toast. `ShaderManager` is constructed with
`Minecraft.triggerResourcePackRecovery` for exactly this, so a shader that
fails at runtime rather than at load takes the same road. Throughout,
`ResourceLoadStateTracker` records what kind of reload this was and with
which packs, so a crash report can say.
On the server there is no overlay; the finish is a continuation on the
server thread, and the coda below lists it.
## F3+T, end to end
```mermaid
sequenceDiagram
participant KH as KeyboardHandler
participant MC as Minecraft
participant PR as PackRepository
participant RRM as ReloadableResourceManager
participant SRI as SimpleReloadInstance
participant Worker as Worker
participant LO as LoadingOverlay
KH->>MC: handleDebugKeys matches keyDebugReloadResourcePacks, reloadResourcePacks
MC->>PR: reload, then openAllSelected: rediscover, keep the selection, open it
MC->>RRM: createReload: close the old MultiPackResourceManager, build the new snapshot
MC->>LO: setOverlay, in the same statement: logo and a smoothed bar from getActualProgress
RRM->>SRI: create: prepareSharedState on every listener, then reload on each, in order
SRI->>Worker: every listener's prepare, all at once
Worker-->>MC: each barrier resolved on the Render thread once every prepare is in and the previous listener has applied
MC->>MC: apply, one listener per registration slot, between frames
Note over LO: a later tick
LO->>MC: isDone, checkExceptions, then allChanged on success or rollbackResourcePacks on failure
```
The key does nothing but ask. `KeyboardHandler.handleDebugKeys` matches
`Options.keyDebugReloadResourcePacks` and calls
`Minecraft.reloadResourcePacks`. If a reload is already showing an overlay,
the request is parked in `Minecraft.pendingReload` and drained from
`Minecraft.runTick`, and a second request while one is parked simply
returns the same future. The one path that bypasses the guard is a
*recovery* reload after a failure. The rest is the five stages above:
`PackRepository.reload` and `PackRepository.openAllSelected` on the Render
thread, `ReloadableResourceManager.createReload` closing the old snapshot
and building the new one, `SimpleReloadInstance` fanning prepares out to
the *resourceLoad* pool and marshalling applies back through `Minecraft`,
and `LoadingOverlay.tick` polling for the end.
The first reload of the game's life runs the same way from the `Minecraft`
constructor, tagged `ResourceLoadStateTracker.ReloadReason.INITIAL` rather
than manual; a world being opened builds its own first snapshot through
`WorldLoader.load`, whose `WorldLoader.PackConfig.createResourceManager`
runs `MinecraftServer.configurePackRepository` and opens the packs; and a
server-sent pack is just one more `RepositorySource`, so
`DownloadedPackSource` triggers an ordinary `Minecraft.reloadResourcePacks`.
## `/reload`, the same pipeline on the server
| | F3+T (client) | `/reload` (server) |
|---|---|---|
| who starts it | `KeyboardHandler.handleDebugKeys` → `Minecraft.reloadResourcePacks`, parked in `Minecraft.pendingReload` if an overlay is already up | `ReloadCommand`, at `Commands.LEVEL_GAMEMASTERS` → `MinecraftServer.reloadResources` |
| discovery | `PackRepository.reload` keeps the current selection; required packs are force-inserted | `ReloadCommand.discoverNewPacks` runs `PackRepository.reload` and then selects every available pack not in the world's disabled list — which is how a datapack dropped into the folder is picked up |
| where the packs are opened | on the Render thread, before the overlay goes up | on the *server thread* first, one `Pack.open` per selected id, before any background work starts |
| the manager | a façade swap: `ReloadableResourceManager.createReload` closes the old `MultiPackResourceManager` and holds the new one | a fresh `MultiPackResourceManager` inside a new `MinecraftServer.ReloadableResources`; the old one is closed only when the new one is installed, and the new one is closed if the reload fails |
| which thread applies, and whether it blocks | the Render thread, between frames; nothing blocks | the Server thread; if `/reload` is issued *from* the server thread the method blocks it with `BlockableEventLoop.managedBlock` until done — `/reload` stalls the tick |
| how many listeners | twenty, in registration order | three — `RecipeManager`, `ServerFunctionLibrary`, `ServerAdvancementManager` (`ReloadableServerResources.listeners`) |
| what is a registry instead | nothing; the client's registries arrive over the wire | tags are read *before* the reload instance by `TagLoader.loadTagsForExistingRegistries` and applied after it ([tags](tags.md#the-four-moments-tags-are-loaded)); loot tables, predicates and item modifiers load as the `RegistryLayer.RELOADABLE` layer in `ReloadableServerRegistries.reload` ([identifiers and registries](identifiers-and-registries.md#when-a-world-opens)); item component prototypes rebind through `BuiltInRegistries.DATA_COMPONENT_INITIALIZERS` ([data components](data-components.md#the-prototype-and-why-it-is-built-at-reload)) |
| when success is reported | when the overlay's poll finds the instance done with no exception | **before** the reload runs — the success message is sent first, and a failure arrives later, asynchronously |
| what happens on completion | `LevelExtractor.allChanged` · `ResourceLoadStateTracker.finishReload` · `DownloadedPackSource.onReloadSuccess` · `Minecraft.onResourceLoadFinished` | close the old `MinecraftServer.ReloadableResources` · install the new · `PackRepository.setSelected` · write the new `WorldDataConfiguration` into level data · `ReloadableServerResources.updateComponentsAndStaticRegistryTags` · `RecipeManager.finalizeRecipeLoading` · `PlayerList.saveAll` · `PlayerList.reloadResources` — which re-reads every player's advancements and broadcasts `ClientboundUpdateTagsPacket` and `ClientboundUpdateRecipesPacket` · `ServerFunctionManager.replaceLibrary` · `StructureTemplateManager.onResourceManagerReload` · a rebuilt fuel table |
| what happens on failure | `Minecraft.rollbackResourcePacks` | the new manager is closed, the old resources stay installed, and the command source is told |
| timing | `ProfiledReloadInstance` only when the logger is at debug | the same |
Two of those rows are worth a second look. The command tree is rebuilt by
every `/reload` — a new `Commands` inside the new
`ReloadableServerResources` — but nothing re-sends it, so connected clients
complete against the tree they were given until they reconnect. And the
reload is debug-timed only: the "Resource reload finished after N ms" line,
the per-listener timings and the total-blocking-time figure all come from
`ProfiledReloadInstance`, selected only when the logger is at debug.
## Across the wire
A server pushes a pack with `ClientboundResourcePackPushPacket` (id, URL,
hash, required, prompt) and withdraws one with
`ClientboundResourcePackPopPacket`, sent by
`ServerResourcePackConfigurationTask` in the configuration phase and by
`ServerPackCommand` (*/serverpack push|pop*) at any time in play; the
client answers with a `ServerboundResourcePackPacket` and its
`ServerboundResourcePackPacket.Action`. Packs are keyed by UUID and stack,
and a server-sent pack pins itself to the top of the selection.
`ServerCommonPacketListenerImpl.handleResourcePackResponse` disconnects a
client that declines — but the "required" it consults is
`MinecraftServer.isResourcePackRequired`, a **server-wide**
*server.properties* setting, not the flag on the individual pack, so a
declined */serverpack push* can disconnect you on a server whose
properties pack is required.
The system is data-driven by *pack.mcmeta* (`PackMetadataSection`, with
*min_format* / *max_format* replacing the integer *pack_format*),
*options.txt* (`Options.resourcePacks`), *level.dat*'s
`WorldDataConfiguration` (`DataPackConfig` enabled/disabled plus the
`FeatureFlagSet`), *server.properties* for the server-sent pack, and
*allowed_symlinks.txt* via `DirectoryValidator`.
## Questions players ask
**Why does my pack's animated texture stop animating when another pack
overrides the image?** Because the `.mcmeta` is looked for in the winning
pack or those above it, never below. The override won the image and
brought no metadata.
**Why does a datapack I dropped into the folder appear after `/reload` but
a resource pack I dropped in does not after F3+T?** `ReloadCommand.discoverNewPacks`
selects every newly available pack; `PackRepository.reload` on the client
only re-discovers and keeps the selection you had.
**Why did F3+T turn all my packs off?** A listener threw. The rollback
does not know which pack did it, so it clears them all and reloads with
vanilla alone.
**Why does `/reload` freeze the server?** When it is issued from the
server thread, `MinecraftServer.reloadResources` blocks that thread with
`BlockableEventLoop.managedBlock` until the reload is done.
**Why can I disable the vanilla data pack but not the vanilla resource
pack?** `ClientPackSource` marks it required; `ServerPacksSource` does not.
## Where to look
`PackType` · `PackResources` · `Pack` · `PackRepository` · `PackCompatibility` ·
`PackFormat` · `ServerPacksSource` · `ClientPackSource` · `BuiltInPackSource` ·
`FolderRepositorySource` · `MultiPackResourceManager` ·
`FallbackResourceManager` · `ReloadableResourceManager` ·
`PreparableReloadListener` · `SimplePreparableReloadListener` ·
`SimpleJsonResourceReloadListener` · `SimpleReloadInstance` ·
`ResourceLoadStateTracker` · `Minecraft.reloadResourcePacks` ·
`LoadingOverlay` · `ReloadCommand` · `MinecraftServer.reloadResources` ·
`ReloadableServerResources` · `WorldLoader` · `DownloadedPackSource` ·
`ServerPackManager`
---
*Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes `tools/verify_names.py`.*
---
# Tags
> Verified against **Minecraft 26.2** · Part II · A parrot flies down through the canopy looking for a log to perch on, a moment after a data pack put a new block in the logs tag.
A parrot is looking for somewhere to sit. `Parrot.ParrotWanderGoal` scans
the blocks around it and asks the block state beneath each one a single
question, *is this state in `BlockTags.LOGS`* — a leaves block it recognises by
class, a log only by tag. Nothing in that goal names oak, or spruce, or the
block a data pack added an hour ago; the tutorial toast that tells a new
player to punch a tree asks the same question. A tag is a named set of
registry entries, defined in data-pack JSON and tested against by key, and
it is how data reaches into hard-coded behaviour without the behaviour
naming any specific block. [The registries page](identifiers-and-registries.md#the-freeze-rule-stated)
ended on a promise: a frozen registry's *contents* never change. Its tags
do. Type `/reload` with a data pack that adds a block to *logs* and the
parrot perches on it a moment later — with `BuiltInRegistries.BLOCK` frozen
since before the title screen. The tag table is one of the two things a
frozen registry still lets you swap — the other is the component prototype
on each `Holder.Reference`, applied on the very next line of
`ReloadableServerResources.updateComponentsAndStaticRegistryTags` — and it
goes through `Registry.PendingTags` in three ordered steps with no lock:
`MappedRegistry.prepareTagReload`
builds the new table off to the side, and `Registry.PendingTags.apply`
binds each `HolderSet.Named`, swaps the `MappedRegistry.TagSet`, then
rebinds every holder's tag set. It is safe because one thread runs it start
to finish with nothing else looking, not because it is atomic.
## The cast
| class | what it decides | thread |
|---|---|---|
| `TagKey` | the name: a registry key and an `Identifier`, interned so equal keys are one object | any |
| `TagFile` · `TagEntry` | the on-disk shape: a list of entries, each an element or a reference to another tag, and the *replace* flag | — |
| `TagLoader` | reads every pack's copy of every file and resolves them in dependency order; what an id resolves to is its `TagLoader.ElementLookup` | Worker at world load, Server on `/reload` |
| `MappedRegistry` | the tag half: `MappedRegistry.frozenTags`, one `HolderSet.Named` per freeze-time key, and `MappedRegistry.allTags`, the bound `MappedRegistry.TagSet` | Server; Render on the client |
| `Registry.PendingTags` | a loaded table not yet installed, with a `Registry.PendingTags.lookup` that answers as if it were | built on a worker at world load, on the server thread for `/reload`, on the client's game thread from the packet; applied on the owning thread |
| `HolderSet.Named` | one tag's contents, rebound in place on every reload | — |
| `Holder.Reference` | one element's own `Set` of `TagKey`s — the thing a membership test reads | — |
| `TagNetworkSerialization` | the wire form: tag id to a list of registry ints | Server encodes, client decodes |
## A tag is a key and a file
In code a tag is a `TagKey`: a record of the registry's `ResourceKey` and
an `Identifier`, interned through a weak interner so that `TagKey.create`
always returns the canonical instance and a membership test is a
set-contains on identity. The keys are declared once, in catalogues that
hold no contents — `BlockTags`, `ItemTags`, `EntityTypeTags`, `BiomeTags`,
`FluidTags` and their siblings, twenty files in `net/minecraft/tags`
including `DamageTypeTags`, `EnchantmentTags`, `StructureTags`,
`PoiTypeTags` and the two 26.2 arrivals `FeatureTags` and `TimelineTags`.
A key that a block and its item share is declared once as a
`BlockItemTagId` in `BlockItemTags` and projected into both catalogues:
`BlockTags.LOGS` is the block half of `BlockItemTags.LOGS`.
On disk a tag is a `TagFile`: a list of `TagEntry` and a *replace* flag.
Each entry is an element id or a *#*-prefixed reference to another tag,
with a *required* flag that defaults to true. The file lives at
*data/\/tags/\/\.json* —
`Registries.tagsDirPath` builds that string in exactly one place, and there
is no plural-name fallback — so *tags/block*, *tags/item*,
*tags/entity_type*, *tags/worldgen/biome*. Vanilla's own files are written
by the data generator (`TagsProvider`, `TagBuilder`), which the running game
never calls, and players reach tags through the *#tag* syntax of
`ResourceOrTagArgument` and `ResourceOrTagKeyArgument` (Part XIII).
`ResourceSelectorArgument`, beside them, is a glob over ids and takes no
tag at all.
Between the two sits `TagLoader`, generic over what an id resolves to: a
`Holder` for a registry, a `CommandFunction` for function tags. It has two
instance steps. `TagLoader.load` reads every pack's copy of every file into
lists of `TagLoader.EntryWithSource`; `TagLoader.build` resolves them in
dependency order, `TagLoader.tryBuildTag` doing one tag at a time through
the `TagLoader.ElementLookup` the loader was built with. Its output, a
`TagLoader.LoadResult`, is resolved but bound to nothing. Everything in
`net/minecraft/tags` ships in both jars. There is no *TagManager* in 26.2
and no reload listener for registry tags: loading is static functions on
`TagLoader`, called from `WorldLoader`, `MinecraftServer.reloadResources`,
`ReloadableServerRegistries` and the registry load tasks. Function tags
are the exception — `ServerFunctionLibrary` genuinely is a reload listener
and runs its own `TagLoader` inside it.
Inside a `MappedRegistry` a tag is two things. `MappedRegistry.frozenTags`
holds one canonical `HolderSet.Named` per key for the tags that existed
when the registry froze, and `MappedRegistry.allTags` is a
`MappedRegistry.TagSet`, the bound view, which starts out as
`MappedRegistry.TagSet.unbound`, where every read throws. Beside the
registry, each `Holder.Reference` carries its own `Set` of `TagKey`s, bound
by `Holder.Reference.bindTags`; that set, not the registry, is what
`Holder.Reference.is` reads.
## The four moments tags are loaded
World load comes first, on the worker pool — the shared one
([anatomy](../anatomy/anatomy.md#four-threads-worth-memorising)).
`TagLoader.loadTagsForExistingRegistries` runs *before* the reload
listeners, over the `RegistryLayer.STATIC` layer, and produces a
`Registry.PendingTags` for every static registry that has at least one tag
file. The pending tables are made visible to worldgen and loot loading
through `TagLoader.buildUpdatedLookups`, and applied, one registry at a
time, in `ReloadableServerResources.updateComponentsAndStaticRegistryTags`
once every listener has finished. For the whole of a load the old tags are
what `Registry.getTags` answers and the new tags are what the loading
codecs see.
`/reload` is the same call on the server thread, handed the composite
access of `MinecraftServer.registries` — so it re-reads and re-applies tags
for the **dynamic** worldgen registries too, not only the static ones. Of
the loading paths only loot re-runs; a worldgen registry keeps its elements
and gets new tags.
A data-pack registry loads its tags inside its own load task.
`ResourceManagerRegistryLoadTask` reads them after its elements, with
`TagLoader.ElementLookup.fromGetters`, and `RegistryLoadTask.registerTags`
binds them under the registry's write lock before it freezes. The
reloadable layer reads tags too — `ReloadableServerRegistries` calls
`TagLoader.loadTagsForRegistry` for every `LootDataType` — but that is the
*void* overload, which throws the result away: nothing binds them, so a
loot registry answers empty for every tag key.
The fourth moment is the client's. One `ClientboundUpdateTagsPacket`
covering every synced registry is sent by `SynchronizeRegistriesTask` after
the registry data. In configuration the client merely *buffers* it —
`ClientConfigurationPacketListenerImpl.handleUpdateTags` hands it to
`RegistryDataCollector.appendTags`, and nothing resolves until
configuration finishes. After a server `/reload`, `PlayerList.reloadResources`
broadcasts the same packet into the play phase, and
`ClientPacketListener.handleUpdateTags` applies that one at once.
## From JSON to a parrot's decision
```mermaid
sequenceDiagram
participant WL as WorldLoader
participant TL as TagLoader
participant MR as MappedRegistry
participant RSR as ReloadableServerResources
participant CCPL as ClientConfigurationPacketListenerImpl
participant CPL as ClientPacketListener
participant Parrot as Parrot
Note over WL,MR: world load, on the worker pool
WL->>TL: loadTagsForExistingRegistries over the STATIC layer
TL->>TL: load, every pack's tags/block/logs.json through FileToIdConverter.listMatchingResourceStacks, a replace flag clears what lower packs contributed
TL->>TL: build, DependencySorter orders oak_logs before logs_that_burn before logs, tryBuildTag resolves ids through ElementLookup.fromFrozenRegistry
TL->>MR: prepareTagReload with the LoadResult, a Registry.PendingTags, nothing visible yet
Note over WL,RSR: worldgen and loot codecs resolve the logs tag through PendingTags.lookup, via buildUpdatedLookups
Note over MR,RSR: the thread driving the load — launching thread on a dedicated server, Render thread on the client — after the last reload listener has applied
RSR->>MR: PendingTags.apply, bind every HolderSet.Named, swap allTags, refreshTagsInHolders rebinds the tag set of every Block holder
Note over RSR,CCPL: configuration, a client joins, SynchronizeRegistriesTask sends ClientboundUpdateTagsPacket after the registry data, registry ints not names
CCPL->>CCPL: handleUpdateTags, RegistryDataCollector.appendTags, buffered until handleConfigurationFinished
Note over CPL: play, after a server /reload, PlayerList.reloadResources broadcasts the packet again
CPL->>MR: handleUpdateTags, prepareTagReload always, apply unless the connection is in memory
Note over MR,Parrot: a server tick, the parrot's wander goal
Parrot->>MR: state.is(BlockTags.LOGS), TypedInstance.is, Block.builtInRegistryHolder, Holder.Reference.is, a Set.contains
```
**Every pack's file, lowest first.** `TagLoader.load` asks
[the resource system](resource-system.md#snapshot-the-manager) for resource *stacks* — a
`FileToIdConverter.json` over the tag directory, listed with
`FileToIdConverter.listMatchingResourceStacks` — so every copy of
*tags/block/logs.json* across the enabled packs is visited in priority
order and merged, and a *replace* in a higher pack discards what the lower
packs contributed to that id. A pack whose file fails to parse is
logged and skipped, never fatal.
**Tags of tags resolve in dependency order, and a tag with a hole is
dropped whole.** The vanilla *logs* file names no block at all: it is three
tag references, *logs_that_burn*, *crimson_stems* and *warped_stems*, and
*logs_that_burn* is in turn nine references, *oak_logs* among them, before
*oak_logs* finally lists four blocks. `TagLoader.build` feeds every tag
reference into a `DependencySorter` and resolves the leaves first. A tag
with **any** failing entry — a missing required element as much as a
missing required tag reference — is dropped whole, not loaded minus the
entry, and is then absent from `Registry.getTags`, so neither the network
payload nor a lookup will find it. An optional entry (*required: false*)
resolves to nothing and the tag still builds: `TagEntry.build` answers *not
required* on a miss, whichever lookup it was handed. What the two lookups
differ on is **where a required id is looked up** —
`TagLoader.ElementLookup.fromGetters`, used by data-pack registries, sends a
required id through the registration lookup and an optional one through the
immutable lookup, while `TagLoader.ElementLookup.fromFrozenRegistry`, used
for a static registry, asks the frozen registry either way.
**Prepared, then applied.** This is where the hook pays off.
`MappedRegistry.prepareTagReload` refuses a registry that is not frozen and
builds the new table, reusing existing `HolderSet.Named` objects where it
can; nothing is visible yet, and the `Registry.PendingTags.lookup` it hands
back answers as if the new table were installed, which is what the worldgen
and loot codecs are given while they load. `Registry.PendingTags.apply` is
then **three ordered steps**: bind each `HolderSet.Named`, swap the
`MappedRegistry.TagSet`, then rebind every holder's tag set. There is no
lock and no single-reference swap; it is safe because one thread runs it
start to finish with nothing else looking, not because it is atomic. Which
thread depends on the occasion: at world load the Server thread does not
exist yet, so the apply runs on whichever thread is driving the load — the
launching thread on a dedicated server, through `Util.blockUntilDone`, and
the Render thread on the client. Only `/reload` applies on the Server
thread.
**The client gets integers.**
`TagNetworkSerialization.serializeTagsToNetwork` walks
`RegistrySynchronization.networkSafeRegistries` and writes each tag as a
list of registry ids, dropping any registry whose payload came out empty.
That set is *every* `RegistryLayer.STATIC` registry, unconditionally,
concatenated with the synced dynamic ones —
`RegistrySynchronization.isNetworkable` filters only the second group. Ids
for dynamic registries are meaningful only once both sides have built the
same registry in the same order, which is why `SynchronizeRegistriesTask`
sends the registry data first. In the configuration phase that ordering is
a *send*-order constraint rather than a handling one: the client buffers
both packets and resolves everything at the end. The play-phase packet
resolves immediately against the live registry access, and the client then
rebuilds its fuel table and the creative-inventory search tree from the new
tags.
**Singleplayer skips only what it already has.** On the play path
`ClientPacketListener.handleUpdateTags` always prepares, and skips only the
*apply* on a memory connection, because the integrated server's apply
already rebound the `BuiltInRegistries` both halves share. In configuration
the suppression is narrower still: only the non-networkable (static)
registries' tags are skipped, and the client still binds tags on its own
copies of the remote dynamic registries.
**The check is a field read.** `BlockBehaviour.BlockStateBase` is a
`TypedInstance`; `TypedInstance.is` asks the type holder — for a block,
`Block.builtInRegistryHolder`, the intrusive holder from
[identifiers-and-registries](identifiers-and-registries.md#before-the-game-exists) — and
`Holder.Reference.is` is set-contains on an interned `TagKey`. No registry
is consulted. `Parrot` (the perch search), `TrunkPlacer` (worldgen) and the
client's `PunchTreeTutorialStepInstance` all ask this way; `FluidState`,
`Entity` and `ItemStack` go through the same interface.
## The other way tags cross the wire
`ClientboundUpdateTagsPacket` is not the only one. `ByteBufCodecs.holderSet`
encodes a `HolderSet.Named` as a marker plus the tag's `Identifier`, and
decodes it by looking the tag up in the receiving side's registry — so any
packet or data component carrying a tag-shaped `HolderSet` **hard-fails on
a client that does not have that tag**. That, more than the id numbering,
is why the tags packet must reach the client before play traffic does.
The same idea appears in data: `TagKey.hashedCodec`, `HolderSetCodec` and
`RegistryCodecs` are what turn *"#minecraft:logs"* in an ordinary JSON field
— a recipe ingredient, a loot condition, a placement predicate — into a
`HolderSet` without any of those files being tag files.
## Questions players ask
**Is a tag empty or broken before a world is open?** Empty, not fatal.
`BuiltInRegistries` binds to empty only the tags the bootstrap actually
asked for through its registration lookup; every other tag is simply absent
from the table, so `Registry.get` for it answers empty and
the same `BlockBehaviour.BlockStateBase.is` answers **false**. The window in which a tag
read genuinely *throws* is narrower than it looks: it is during
`BuiltInRegistries.bootStrap` itself, before `MappedRegistry.freeze`
installs a bound `MappedRegistry.TagSet`. The throws even come from
different places — an unbound `Holder.Reference` complains that tags are
not bound, while `MappedRegistry.TagSet.unbound` guards the registry-level
lookups.
**Can a tag name something from another registry, or something that does
not exist yet?** Never the first: a `TagEntry` carries only an
`Identifier`, and the registry is fixed by the directory the file is in.
The second, on one path: a data-pack tag *can* name an element that has
not loaded yet, because the required path creates a placeholder
`Holder.Reference` through `MappedRegistry.createRegistrationLookup`, and
the registry's freeze fails with unbound values if the element never
arrives. That escape hatch exists only on the data-pack path, never for a
static registry.
**Is the `HolderSet` I captured still the right object after `/reload`?**
Only if the tag existed when the registry froze.
`MappedRegistry.prepareTagReload` reuses a `HolderSet.Named` from
`MappedRegistry.frozenTags` when it finds one, but a tag that first appears
*after* the registry froze is created fresh into the pending map and never
written back — so it gets a brand-new `HolderSet.Named` on every subsequent
reload. A recipe ingredient that captured a vanilla tag at load time is
correct after `/reload` without re-lookup; one that captured a data-pack
tag may be holding a stale object.
**What happens to a tag a reload deleted?** It keeps its old contents. It
is absent from the pending map, so apply neither rebinds nor clears it.
Anything still holding that `HolderSet.Named` will *iterate* the old list
while `HolderSet.Named.contains` — which delegates to the holder's
refreshed tag set — answers false, and `Registry.get` for the key answers
empty.
**What if two tags reference each other?** Both are dropped, and both are
logged. `DependencySorter.addDependencyIfNotCyclic` drops the edge that
would close the cycle, so an order exists and the sort does not hang — but
whichever tag is built first asks for a tag that is not in the new table
yet, gets nothing, and fails as a missing required reference; the second
then asks for the first, which failed, and fails the same way. Two
*Couldn't load tag* lines, and neither tag exists.
**Which tags does the client never hear about?** Those of
`RegistryLayer.RELOADABLE`-layer registries (loot tables, predicates) and
of non-synced worldgen registries (configured features, structures). The
reloadable ones are not even kept — see below. On the receiving side, ids the
client's registry does not know are dropped from the payload silently.
**Is picking a random element from a tag deterministic?** Yes, per pack
stack. Duplicate entries collapse and file order is preserved —
`TagLoader.tryBuildTag` collects into an insertion-ordered set — so
iterating a tag, or picking from it with `HolderSet.getRandomElement`,
gives the same sequence for the same packs.
**Why do function tags look different from every other kind?** Because
they are. `ServerFunctionLibrary` runs its own `TagLoader` over
`CommandFunction`s, inside a real reload listener, with no registry
involved; the well-known keys `ServerFunctionManager.TICK_FUNCTION_TAG` and
`ServerFunctionManager.LOAD_FUNCTION_TAG` live on the manager, not the
library (see Part XIII).
## Where to look
`TagKey` · `BlockItemTags` · `BlockTags` · `TagFile` · `TagEntry` ·
`TagLoader` · `MappedRegistry` (the tag half) · `Registry.PendingTags` ·
`HolderSet` · `DependencySorter` · `WorldLoader` · `ReloadableServerResources` ·
`TagNetworkSerialization` · `ClientboundUpdateTagsPacket` ·
`ClientConfigurationPacketListenerImpl` · `ClientPacketListener` ·
`RegistryDataCollector` · `ByteBufCodecs` · `TypedInstance` ·
`Parrot.ParrotWanderGoal`
---
*Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes `tools/verify_names.py`.*
---
# Data components
> Verified against **Minecraft 26.2** · Part II · A player types `/give @s diamond_sword[enchantments={sharpness:3}]`, and later rolls Sharpness onto a plain sword at the enchanting table: what the square brackets are, and how the client finds out.
A player types `/give @s diamond_sword[enchantments={sharpness:3}]`. The
part in square brackets is a **patch**: one keyed, typed value laid over
the sword. A data component is exactly that — one typed, keyed piece of
data attached to an item stack: its damage, its enchantments, its lore, the
food it is, the armour slot it goes in. The item *type* supplies a
**prototype** map of components; a stack carries only a patch against that
prototype. Everything that used to be "the NBT on an item" is a component
with a codec, and most of what used to be a subclass of `Item` carrying
behaviour (a sword, a piece of armour) is now a kit of components on a
plain `Item`. The surprise is where the prototype comes from. It is not
built in the item's constructor: `Item.Properties` only *records* an
initializer, and the map is built again on every reload with the world's
registries in hand — `DataComponentInitializers.build` on the reload
worker, `Holder.Reference.bindComponents` on the owning thread. That is
why a data pack can change what a jukebox plays without touching the item,
and why a stack cannot even be decoded before the first reload:
`Item.CODEC_WITH_BOUND_COMPONENTS` guards on `Holder.areComponentsBound`.
## The cast
| class | what it decides | thread |
|---|---|---|
| `DataComponentType` | the key: which codec writes the value to disk, which to the wire, and whether it is saved at all | static, registered at bootstrap |
| `DataComponentMap` | an immutable, identity-keyed map — the prototype's shape | any; never mutated |
| `PatchedDataComponentMap` | what an `ItemStack` actually owns: a shared prototype, a patch, and whether the patch is still someone else's | whichever thread owns the stack |
| `DataComponentPatch` | the serialisable form of the patch: additions and removals | Netty for packets; server and workers for saves |
| `DataComponentInitializers` | builds every registry element's prototype, from the recorded initializers, once the registries exist | reload worker |
| `Holder.Reference` | holds the bound prototype for one registry element, and throws until it has one | bound on the server thread after a reload; on the client at the end of configuration |
| `ItemStack` | the writes (`ItemStack.set`, `ItemStack.update`, `ItemStack.remove`, `ItemStack.copyFrom`) and the reads it inherits from `DataComponentHolder` | whichever thread owns the stack |
| `DataComponentLookup` | the reverse index: which elements of a registry carry this component value | built at freeze, populated lazily |
Everything here ships in both jars; in the trace below only
`ClientPacketListener` is client-only.
## The shape of a stack
```mermaid
flowchart LR
DCI["DataComponentInitializers.build, on the reload worker, with the registries in hand"] --> HR
subgraph ITEM["Item, one per registry element"]
HR["Holder.Reference, bindComponents at reload"] --> PROTO["the prototype: a DataComponentMap, immutable and identity-keyed"]
end
subgraph STACK["ItemStack"]
PDM["PatchedDataComponentMap"] --> PATCH["patch: type to Optional value, empty meaning removed from the prototype"]
PDM --> COW["copyOnWrite: the map is still shared with the stack it was copied from"]
end
PDM -. "prototype, shared by every stack of the item" .-> PROTO
PATCH -- "asPatch and fromPatch" --> DCP["DataComponentPatch: added values and removed keys, the form on disk and on the wire"]
KEY["DataComponentType: the key, with a persistent codec, a network codec, or only the latter"] --> PATCH
KEY --> PROTO
```
Read it left to right. The item's prototype is built by
`DataComponentInitializers` and bound onto the item's `Holder.Reference`.
A stack points at that shared prototype and owns only a patch over it,
whose values are `Optional` — an empty value is a removal. The patch's
serialisable twin is a `DataComponentPatch`, and the key of every entry in
all of them is a `DataComponentType`. The rest of the page is a tour of
those objects, each grounded in one small trace: Sharpness III arriving on
a sword at the enchanting table.
## The key: `DataComponentType`
A type is built by `DataComponentType.Builder`. `DataComponentType.Builder.persistent`
gives the disk and JSON codec; `DataComponentType.Builder.networkSynchronized`
a hand-written wire codec. A type with no persistent codec is **transient**
(`DataComponentType.isTransient`): never saved, only sent.
`DataComponentType.Builder.networkSynchronized` is not a gate — without it,
`DataComponentType.Builder.build` derives a wire codec from the persistent
codec (NBT over the wire), and with neither it throws. The only real switch
is transient-versus-persistent, and it gates *saving*. Three types exist
only on the wire: `DataComponents.CREATIVE_SLOT_LOCK`,
`DataComponents.ADDITIONAL_TRADE_COST`, `DataComponents.MAP_POST_PROCESSING`.
**111** — vanilla types, registered in `DataComponents` into
`BuiltInRegistries.DATA_COMPONENT_TYPE`; 29 of them have slash-shaped ids
(*villager/variant* and its siblings). The catalogue is
[reference/components](../../reference/components.md). Component *types*
are code, in a registry data packs cannot extend; what data packs reach is
the prototype (below) and `CustomData`, which carries arbitrary NBT for
them to use.
Two more flags live on the builder. `DataComponentType.Builder.cacheEncoding`
routes a type's encodes through `EncoderCache` (`DataComponents.ENCODER_CACHE`).
`DataComponentType.Builder.ignoreSwapAnimation` is set on exactly one type,
`DataComponents.DAMAGE` — so durability ticking down does not replay the
held-item swap on the client. Underneath, `DataComponentType.PERSISTENT_CODEC`
and `DataComponentType.VALUE_MAP_CODEC` are the shared dispatch machinery
that `DataComponentMap.CODEC` and the predicates are built on.
## The maps
`DataComponentMap` is immutable and identity-keyed; `DataComponentMap.Builder`
builds one and can carry a `DataComponentMap.Builder.addValidator`, which is
where the prototype-time structural rule lives (below). `DataComponentMap.EMPTY`
is the map every registry element gets when nothing declared one.
`DataComponentMap.composite` is dead API: it exists, and nothing in 26.2
calls it; the layering that actually happens is `PatchedDataComponentMap`'s
prototype-plus-patch.
`PatchedDataComponentMap` is the map an `ItemStack` actually owns: a
`PatchedDataComponentMap.prototype` shared with every stack of that item, a
`PatchedDataComponentMap.patch` whose values are `Optional` (empty means
*removed from the prototype*), and a `PatchedDataComponentMap.copyOnWrite`
flag. It sanitises on every write: `PatchedDataComponentMap.set` stores
nothing when the value equals the prototype's, and
`PatchedDataComponentMap.remove` stores a removal marker only if the
prototype had the key. In the trace, the sword's prototype carries an
*empty* `ItemEnchantments` (every item's does, through
`DataComponents.COMMON_ITEM_COMPONENTS`), so Sharpness III differs from the
prototype and the patch gains one entry. Set the enchantments back to empty
and the entry vanishes rather than becoming an explicit default.
**The first write pays for the copy.** `PatchedDataComponentMap.ensureMapOwnership`
clones the backing map only when `PatchedDataComponentMap.copyOnWrite` is
set; `ItemStack.copy`, `PatchedDataComponentMap.asPatch` and
`ItemStack.transmuteCopy` all alias the same map and set the flag, so
copying a stack is O(1) until someone writes. `ItemStack.applyComponentsAndValidate`
relies on that: it snapshots with `PatchedDataComponentMap.asPatch`,
applies, runs `ItemStack.validateStrict`, and on failure
`PatchedDataComponentMap.restorePatch`.
**Equality is prototype plus patch.** `ItemStack.isSameItemSameComponents`
bottoms out in `PatchedDataComponentMap.equals`. Because setting a value
equal to the prototype default *removes* it from the patch, two stacks that
reached the default by different routes compare equal.
## The patch, on the wire and on disk
`DataComponentPatch` is the serialisable form: additions and removals.
`DataComponentPatch.CODEC` writes removals as `!minecraft:foo`;
`DataComponentPatch.STREAM_CODEC` writes two counts then the entries;
`DataComponentPatch.DELIMITED_STREAM_CODEC` is the length-prefixed variant
for untrusted input; and `DataComponentPatch.split` is the added/removed
decomposition that the hashing and block-entity paths are both built on.
`TypedDataComponent` — a type with its value — has its own "type id then
value" stream codec, distinct from the patch encoding, for the places one
value travels alone.
**The wire patch never contains defaults — for a real stack.** Because
`PatchedDataComponentMap` sanitises on every write, a value equal to the
prototype's is dropped rather than sent. `ItemStackTemplate` is the
exception: it holds a raw `DataComponentPatch` straight from the builder,
which does no such comparison, and sends it verbatim.
On the network the patch travels inside every `ItemStack` in
`ClientboundContainerSetSlotPacket`, `ClientboundContainerSetContentPacket`,
`ClientboundSetCursorItemPacket`, `ClientboundSetPlayerInventoryPacket` and
`ClientboundSetEquipmentPacket`; the client answers ordinary clicks with
hashes, not stacks, and the creative slot alone sends a full, re-validated
stack — [codecs, NBT and JSON](codecs-nbt-json.md#the-four-paths-side-by-side) owns the four
serialisations of a stack, and [containers and menus](../items/containers-and-menus.md)
owns what the server does with a hash. On disk an item is saved as a
*patch* (`ItemStack.MAP_CODEC`'s "components", with transient types
silently dropped), but a block entity as a full `DataComponentMap` — the
two are not symmetric.
## The prototype, and why it is built at reload
An `Item` constructor registers its initializer in
`BuiltInRegistries.DATA_COMPONENT_INITIALIZERS`. `Item.Properties.component`
and its convenience methods (`Item.Properties.durability`,
`Item.Properties.food`, `Item.Properties.equippable`, `Item.Properties.sword`,
…) only *record* a `DataComponentInitializers.Initializer`; nothing is a
map yet. `Item.Properties.delayedComponent` and
`Item.Properties.delayedHolderComponent` are the reason it must be
deferred: they name registry entries that do not exist until a world's
registries do — an item's `DataComponents.JUKEBOX_PLAYABLE` or
`DataComponents.DAMAGE_RESISTANT` names an entry a data pack can change.
The maps are *built* with full registry context — tags, damage types,
jukebox songs resolvable — by `DataComponentInitializers.build` on the
reload worker on the server, and *installed* with
`Holder.Reference.bindComponents` on the owning thread: on the server in
`ReloadableServerResources.updateComponentsAndStaticRegistryTags` (server
thread, after every reload including `/reload`), on the client in
`RegistryDataCollector` at the end of configuration. A `/reload` therefore
rebinds every item's prototype. Every registry element gets a component
map (`DataComponentMap.EMPTY` if it had no initializer), so `EntityType`
holders have one too — with one asymmetry, and it runs the way round you
would not guess. `ClientConfigurationPacketListenerImpl` passes
`Connection.isMemoryConnection` into `RegistryDataCollector.collectGameRegistries`,
which negates it, so a **singleplayer** client binds only the registries
`RegistrySynchronization.isNetworkable` accepts and a **multiplayer** client
binds every one. Singleplayer can skip the rest because it shares
`BuiltInRegistries` with the integrated server, whose own apply has already
bound them.
Before binding, reading a registry element's components throws, and the
failure is a null-check, not a friendly error: `Item.components` merely
delegates, the throw comes from `Holder.Reference.components`, and the
non-throwing question is `Holder.areComponentsBound`.
`Item.CODEC_WITH_BOUND_COMPONENTS` guards on it and refuses to decode a
stack until then.
**Ten** — entries in `DataComponents.COMMON_ITEM_COMPONENTS`, the map every
item's prototype starts from. Notably it puts an *empty* `ItemEnchantments`
on every item, which is what `ItemStack.isEnchantable` depends on: it gates
first on `DataComponents.ENCHANTABLE` being present, and *then* on
`DataComponents.ENCHANTMENTS` being present and empty.
**There are two structural rules, at two different times.** At
prototype-build time a validator installed by `Item.Properties` rejects an
item that is both damageable and stackable. At stack time
`ItemStack.validateStrict` rejects a `DataComponents.MAX_DAMAGE` alongside a
`DataComponents.MAX_STACK_SIZE` above one, a count above the stack's own
maximum, an over-weight bundle, and contained items whose counts exceed
their own limits. That last check reaches exactly **one** level into
containers, bundles and charged projectiles and does not re-run the full
validation there — nesting is not followed.
## The reverse index: `DataComponentLookup`
Every frozen `MappedRegistry` builds one (`Registry.componentLookup`): a
lazily-populated reverse index answering "which elements carry this
component value?", which is how the game finds the spawn egg for an entity
type or the item for a dye colour. It reads the same bound prototypes the
holders carry, so it too is meaningless before the first reload.
## The readers and the predicates
`DataComponentGetter` reads one component; `DataComponentHolder` reads one
and has a map, and is implemented only by `ItemStack`, which is where
`DataComponentHolder.get`, `DataComponentHolder.getOrDefault` and
`DataComponentHolder.has` come from. `ItemInstance` is the read-only face
over `ItemStack` and `ItemStackTemplate` (item, count, patch — a record)
that predicates and recipes take. Beyond item behaviour, the callers are
loot functions (`CopyComponentsFunction`) and the `/give` and `/item`
commands.
The predicates come in two strengths. `DataComponentExactPredicate` requires
every listed component to equal. The partial `DataComponentPredicate`
family under `core/component/predicates` — `DataComponentPredicates`, **15**
kinds, in their own `BuiltInRegistries.DATA_COMPONENT_PREDICATE_TYPE`
registry — matches a shape rather than a value, and `DataComponentMatchers`
joins the two for `ItemPredicate`.
## The values, by package
| package | value types |
|---|---|
| `world/item/component` | `Consumable`, `Tool`, `Weapon`, `BlocksAttacks`, `ItemLore`, `CustomData`, `TooltipDisplay`, `ItemContainerContents`, `BundleContents`, `TypedEntityData` … |
| `world/item/equipment` | `Equippable` |
| `world/item/enchantment` | `ItemEnchantments`, `Enchantable`, `Repairable` |
`Item` subclasses no longer carry combat. `Weapon`, `BlocksAttacks`,
`KineticWeapon`, `PiercingWeapon`, `AttackRange`, `SwingAnimation` and
`Tool` are components; `Item.Properties.sword`, `Item.Properties.spear` and
`Item.Properties.humanoidArmor` build whole kits, and *SwordItem* is gone.
But the tools that act *on a block* are not: `AxeItem`, `ShovelItem` and
`HoeItem` still exist as classes, purely for stripping, path-making and
tilling — their combat and mining live in components like everything else.
## The trace: Sharpness at the enchanting table
```mermaid
sequenceDiagram
participant EM as EnchantmentMenu
participant IStack as ItemStack
participant PDM as PatchedDataComponentMap
participant ACM as AbstractContainerMenu
participant CPL as ClientPacketListener
Note over EM: server thread, ServerboundContainerButtonClickPacket has arrived
EM->>IStack: transmuteCopy(Items.ENCHANTED_BOOK) if the input is a book, same patch over a new prototype
EM->>IStack: enchant(holder, level) for each chosen EnchantmentInstance
IStack->>IStack: EnchantmentHelper.updateEnchantments, then set of STORED_ENCHANTMENTS for a book, ENCHANTMENTS otherwise
IStack->>PDM: set: ensureMapOwnership clones the shared map, the value differs from the prototype's empty set, so patch.put
Note over PDM: patch is now {minecraft:enchantments to {sharpness: 3}}
Note over ACM: still inside the packet handler, which calls broadcastChanges itself once the click is accepted
ACM->>ACM: broadcastChanges, RemoteSlot.Synchronized.matches fails for this slot
ACM->>CPL: ClientboundContainerSetSlotPacket: count, item id, DataComponentPatch.STREAM_CODEC
CPL->>CPL: decode: new ItemStack(holder, count, patch), fromPatch against the client's own bound prototype
CPL->>CPL: handleContainerSetSlot, AbstractContainerMenu.setItem, tooltip via ItemEnchantments.addToTooltip
```
**The menu owns the mutation.** `EnchantmentMenu.clickMenuButton` runs
under `ContainerLevelAccess.execute` on the server thread. For a book it
first calls `ItemStack.transmuteCopy` — a new stack with the *same patch*
applied to the enchanted book's prototype — then `ItemStack.enchant` per
chosen `EnchantmentInstance`. `ItemStack.enchant` hands the edit to
`EnchantmentHelper.updateEnchantments`, which reads the current
`ItemEnchantments`, edits a mutable copy and writes the immutable result
back with `ItemStack.set`; the enchanting rules, the lapis, the seed and
`/enchant` are [Part VII's](../items/enchanting.md). What matters here is
that enchantments are a value, not a list on the stack: one component, one
write.
**One write, one patch entry.** `ItemStack.set` is `PatchedDataComponentMap.set`.
The map the sword owned was shared with whatever it was copied from, so
`PatchedDataComponentMap.ensureMapOwnership` clones it now; the new
`ItemEnchantments` differs from the prototype's empty one, so the patch
gains its single entry. A book that was transmuted a moment earlier carries
the same patch over a different prototype, which is the whole meaning of
`ItemStack.transmuteCopy`.
**The menu compares, and here it does not wait for the tick.**
`AbstractContainerMenu.broadcastChanges` compares every slot against what
the client was last told (`RemoteSlot.Synchronized`). It runs from
`ServerPlayer.tick` in the ordinary case — but a menu-button click is not
the ordinary case: `ServerGamePacketListenerImpl.handleContainerButtonClick`
calls it directly, in the same handler, as soon as
`AbstractContainerMenu.clickMenuButton` accepts. The sword's slot no longer
matches, so `ClientboundContainerSetSlotPacket` goes out, and only the patch
crosses: `ItemStack.OPTIONAL_STREAM_CODEC` writes the count,
`Item.STREAM_CODEC` (a registry id) and the patch. The client answers later
clicks with a `HashedStack` of CRC32C checksums rather than stacks
([codecs, NBT and JSON](codecs-nbt-json.md#checksum-a-hash-instead-of-a-stack) for the hashing,
[containers and menus](../items/containers-and-menus.md) for the click
protocol).
**The client rebuilds against its own prototype.** The decoder constructs
`ItemStack` from holder, count and patch, and that constructor is
`PatchedDataComponentMap.fromPatch` against the client's *own* bound
prototype — the map `RegistryDataCollector` bound at the end of
configuration. That is the reason components must be bound on the client
before the play phase: an unbound prototype would throw in a packet decoder
on a Netty thread. `ClientPacketListener.handleContainerSetSlot` then hands
the stack to `AbstractContainerMenu.setItem`, and the tooltip's purple line
is `ItemEnchantments.addToTooltip` reading the same component.
## Components on things that are not items
**Block entities, both directions.** Placing runs
`BlockEntity.applyComponentsFromItemStack`, which hands subclasses a
*recording* `DataComponentGetter`: whatever `BlockEntity.applyImplicitComponents`
reads is forgotten from the patch (`DataComponentPatch.forget`) and only the
leftovers persist as opaque `BlockEntity.components`. Two types are
pre-seeded into that forget set regardless of whether anything reads them
— `DataComponents.BLOCK_ENTITY_DATA` and `DataComponents.BLOCK_STATE` — and
only the *added* half of the resulting patch is kept, so removals are
discarded. Breaking or picking runs the reverse,
`BlockEntity.collectComponents` over `BlockEntity.collectImplicitComponents`,
with `BlockEntity.removeComponentsFromTag` de-duplicating what was
promoted; `BlockItem.setBlockEntityData` is the write path for the opaque
blob.
**Entities, read-only.** `Entity` implements `DataComponentGetter` with no
patch of its own: `Entity.get` answers `DataComponents.CUSTOM_NAME` and
`DataComponents.CUSTOM_DATA` by hand, lets subclass overrides (`Sheep`,
`Wolf`, `Villager` …) answer the variant-shaped types, and otherwise falls
through to its `EntityType` holder's bound prototype — the same map every
registry element gets. `Entity.applyComponentsFromItemStack` is the write
path, and it is not only spawn eggs: any item-to-entity spawn, an arrow
picking up its stack, a lingering potion's cloud and `BlockItem` all take
it.
## Where to look
`DataComponentType` · `DataComponents` · `DataComponentMap` ·
`PatchedDataComponentMap` · `DataComponentPatch` · `DataComponentInitializers` ·
`Holder.Reference` · `DataComponentLookup` · `Item.Properties` · `ItemStack` ·
`ItemInstance` · `ItemStackTemplate` · `EnchantmentMenu` ·
`AbstractContainerMenu` · `BlockEntity` · `Entity` (the getter half)
---
*Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes `tools/verify_names.py`.*
---
# Text components
> Verified against **Minecraft 26.2** · Part II · A player dies: the message that names the killer is built on the server, crosses the wire as a translation key, and is worded on the client.
An arrow lands and a player drops. A line appears in everyone's chat, and
on the victim's screen the death screen says who did it. The server built
that message on its own thread, inside `CombatTracker.getDeathMessage`,
and sent it twice: once in `ClientboundPlayerCombatKillPacket` to the
victim, once as system chat to everybody. But the server never wrote the
sentence. What it built was a `Component` whose contents are a translation
key — *death.attack.arrow* — and two arguments, the victim's name and the
killer's, each of them another `Component` carrying a hover card and a
click action. The packet crosses the wire as NBT. On the client the packet
is decoded, handed to a `DeathScreen`, and still nobody has read the key.
**The client receives the death message before anyone knows what it says.**
The words are chosen on the first frame that draws it, when
`TranslatableContents` asks the client's own `Language` for the template
behind the key, so two players watching the same death read two
different sentences from one packet — and the server, which logs the
message too, only ever reads it in English.
## The cast
| class | what it decides | thread |
|---|---|---|
| `Component` | the interface: contents, style, siblings, and a walk over them in logical order | built wherever text is made — a server tick, a command, the Render thread |
| `MutableComponent` | the only implementation; `MutableComponent.append` and `MutableComponent.withStyle` are how a tree is built | as above |
| `ComponentContents` · `TranslatableContents` | what a node *says*: seven kinds, of which the translatable kind is the one that waits for a `Language` | worded on whichever thread first visits it |
| `Style` | the eleven inheritable fields, immutable, `Style.EMPTY` the shared blank | any |
| `ComponentSerialization` | the one recursive codec, and the NBT stream codecs built over it | Netty, in `PacketEncoder` and `PacketDecoder`; Server or Render for data on disk |
| `ComponentUtils` | `ComponentUtils.resolve`: selectors, scores and NBT paths into text, against a `ResolutionContext` | Server, during command execution |
| `Language` | a key to a template; the client swaps the instance on every resource reload | Render thread reads it, `Language.inject` swaps it |
| `ClickEvent` | what a click on styled text may do, and the one action a server may not send | Render thread, when the click lands |
## A component is three things
```mermaid
flowchart TD
Comp["Component: an interface with four abstract members"] --> MComp["MutableComponent: the only implementation"]
MComp --> Contents["one ComponentContents"]
MComp --> Style["one Style: eleven nullable fields, null means inherit"]
MComp --> Siblings["an ordered list of siblings, each a Component"]
Siblings -. "recursion" .-> MComp
subgraph kinds ["the seven kinds"]
Text["PlainTextContents"]
Trans["TranslatableContents: key, fallback, arguments"]
Key["KeybindContents"]
Score["ScoreContents"]
Sel["SelectorContents"]
Nbt["NbtContents"]
Obj["ObjectContents: a sprite in the text"]
end
Contents --> kinds
MComp --> CS["ComponentSerialization.CODEC, and STREAM_CODEC over NBT"]
```
`Component` is an interface with four abstract members —
`Component.getStyle`, `Component.getContents`, `Component.getSiblings` and
`Component.getVisualOrderText`, the last of which is why `MutableComponent`
caches a laid-out form — and exactly **one** implementation,
`MutableComponent`. A component is a triple: one `ComponentContents`, one
`Style`, and an ordered list of sibling components. Style inheritance
happens during traversal, not in storage: `Component.visit` applies a
node's own style over its parent's with `Style.applyTo` and hands the
result down to the contents and then to each sibling in turn, so a red
parent with a plain child draws the child red, and nothing in the child
records it. The mutability is in the name. `Component.literal`,
`Component.translatable` and the other factories return a
`MutableComponent`, and a tree is built by `MutableComponent.append` and
`MutableComponent.withStyle`, which replace the style field with a fresh
`Style` each time. `Component.copy` is a shallow copy — the sibling list is
new, the siblings are shared.
The walk is the only way to read one. `Component.getString` visits every
node and concatenates what it says; `Component.getString` with a limit stops
at that many characters, which is how a death message too long to send is
cut to 256 for its replacement. Everything from the walk onwards — the
codepoint stream, bidirectional reordering, glyphs — belongs to
[text and fonts](../client/text-and-fonts.md).
## The seven kinds of contents
Each kind has a static factory on `Component`:
| kind | class | made by |
|---|---|---|
| text | `PlainTextContents`, with `PlainTextContents.LiteralContents` | `Component.literal` |
| translatable | `TranslatableContents` | `Component.translatable` |
| keybind | `KeybindContents` | `Component.keybind` |
| score | `ScoreContents` | `Component.score` |
| selector | `SelectorContents` | `Component.selector` |
| nbt | `NbtContents` | `Component.nbt` |
| object | `ObjectContents` | `Component.object` |
### What each kind says, and when
**Text** says its string and nothing more; the empty string is the shared
`PlainTextContents.EMPTY`, which is what `Component.empty` and
`CommonComponents.EMPTY` hold.
**Translatable** is a key, an optional fallback and an array of
arguments. An argument is a number, a boolean, a string or another
`Component` — `TranslatableContents.isAllowedPrimitiveArgument` is the
test, and `Component.translatableEscape` turns anything else into its
string form before it can reach the codec. The template the key names is
read by `TranslatableContents.decompose`, which accepts only `%s`, `%n$s`
and `%%`: any other specifier is a `TranslatableFormatException`, and the
component then shows the raw template string instead. The decomposition is
cached against the identity of the `Language` that produced it
(`TranslatableContents.decomposedWith`), so a language switch re-words every
component the next time it is visited, without anything being re-sent.
**Keybind** names a key binding and asks `KeybindResolver.keyResolver` for
its current name; the default resolver answers with the name itself, and
the client installs `KeyMapping.createNameSupplier` in the `Minecraft`
constructor, so *key.jump* reads as whatever key is bound on a client and
as *key.jump* anywhere else.
**Score**, **selector** and **nbt** are the three kinds that say nothing
until a server resolves them. A score names a holder — an entity selector,
a literal name, or `ScoreHolder.WILDCARD` for the context's own entity —
and an objective. A selector holds a compiled `EntitySelector` and an
optional separator; visited unresolved, it shows the selector's source
text. An nbt component holds an `NbtPathArgument.NbtPath`, a separator, and
one of three `DataSource`s — `EntityDataSource`, `BlockDataSource`,
`StorageDataSource` — and either prints what it finds through
`TextComponentTagVisitor` or, with *interpret*, parses each match as a
component; *interpret* and *plain* are refused together. Visited without
resolution, score and nbt contents say nothing at all.
**Object** puts a picture inside text. Its `ObjectInfo` is an
`AtlasSprite` (an atlas and a sprite, the block atlas by default) or a
`PlayerSprite` (a `ResolvableProfile` and whether to draw the hat). When
visited with a style it emits a single placeholder character, U+FFFC, with
the style's font set to the info's `FontDescription`; the font system draws
the sprite where a glyph would go. Visited without style —
`Component.getString`, a log line — it says its fallback, or
`ObjectInfo.defaultFallback`: *[sprite]*
for an atlas sprite, *[name head]* for a player.
## Style, and the click that never crosses
`Style` holds eleven nullable fields: `Style.color` (a `TextColor`),
`Style.shadowColor`, the five booleans — `Style.bold`, `Style.italic`,
`Style.underlined`, `Style.strikethrough`, `Style.obfuscated` —
`Style.clickEvent`, `Style.hoverEvent`, `Style.insertion` and `Style.font`
(a `FontDescription`). Null means *inherit*, which is what makes
`Style.applyTo` a merge: a field the child sets wins, a field it leaves
null falls through to the parent. A setter returns a new `Style` only when
the value changes — set bold to what it already is and the same object comes back —
and the codec and the setters collapse a style with nothing left set to the
shared `Style.EMPTY`, so `Style.isEmpty` is an identity check. `ChatFormatting`
is the legacy vocabulary — `Style.applyFormat` sets one boolean or, for a
colour code, `TextColor.fromLegacyFormat`, and `ChatFormatting.RESET`
returns `Style.EMPTY` outright.
A `TextColor` is 24 bits, masked on construction, and serialises as one of
its sixteen names or as *#RRGGBB*; `TextColor.parseColor` accepts either.
The shadow is different: `Style.shadowColor` is a 32-bit ARGB integer,
`Style.NO_SHADOW` is zero, and `Style.withoutShadow` is how a component asks
to be drawn flat. `Style.font` is a `FontDescription`, which may be a
`FontDescription.Resource` naming a font file, or one of the two sprite
shapes that `ObjectContents` uses — it need not name a font at all.
### Eight clicks, three hovers, one refusal
`ClickEvent` and `HoverEvent` are interfaces implemented by nested records
— closed by convention and by their `Action` dispatch codec, not by the
language. There are eight click actions and three hover actions:
| `ClickEvent.Action` | record | carries | a server may send it |
|---|---|---|---|
| `ClickEvent.Action.OPEN_URL` | `ClickEvent.OpenUrl` | a URI, through `ExtraCodecs.UNTRUSTED_URI` | ✓ |
| `ClickEvent.Action.OPEN_FILE` | `ClickEvent.OpenFile` | a path on the viewer's disk | **no** |
| `ClickEvent.Action.RUN_COMMAND` | `ClickEvent.RunCommand` | a command string | ✓ |
| `ClickEvent.Action.SUGGEST_COMMAND` | `ClickEvent.SuggestCommand` | a string for the chat box | ✓ |
| `ClickEvent.Action.SHOW_DIALOG` | `ClickEvent.ShowDialog` | a `Holder` of a `Dialog` | ✓ |
| `ClickEvent.Action.CHANGE_PAGE` | `ClickEvent.ChangePage` | a positive page number | ✓ |
| `ClickEvent.Action.COPY_TO_CLIPBOARD` | `ClickEvent.CopyToClipboard` | a string | ✓ |
| `ClickEvent.Action.CUSTOM` | `ClickEvent.Custom` | an `Identifier` and an optional NBT payload, for servers to define | ✓ |
What keeps `ClickEvent.Action.OPEN_FILE` out of a server's hands is
`ClickEvent.Action.filterForSerialization`, applied as a validation on
`ClickEvent.Action.CODEC` and therefore biting in **both** directions and
in **every** format: a data pack cannot write one either, and the client
cannot encode one it built itself. The private flag behind it is
`ClickEvent.Action.allowFromServer`. The unfiltered
`ClickEvent.Action.UNSAFE_CODEC` exists and nothing outside the enum reads
it; an open-file click is something only client code constructs in memory —
`Screenshot`'s notice, a debug dump's path in `KeyboardHandler`, a
profiler result in `Minecraft` — for its own chat. `HoverEvent.Action` has the identical machinery and
nothing to filter — all three of its values are allowed:
`HoverEvent.ShowText` wraps a component, `HoverEvent.ShowItem` an
`ItemStackTemplate`, `HoverEvent.ShowEntity` an
`HoverEvent.EntityTooltipInfo` of type, UUID and optional name.
## Serialisation: one codec, three shapes
**Serialisation** is one recursive codec, `ComponentSerialization.CODEC`,
whose shape is a three-way choice: a bare string becomes a literal, a list
becomes its first element with the rest appended, and an object is the
full record. Contents are matched by an explicit *type* field if one is
present and otherwise by trying each contents codec in turn — which is why
an untyped component still round-trips. `Component.tryCollapseToString` is
what lets a plain unstyled literal encode as a bare string.
The full record is flat. `Style.Serializer.MAP_CODEC` is inlined, so the
style's keys — *color*, *shadow_color*, *bold*, *italic*, *underlined*,
*strikethrough*, *obfuscated*, *click_event*, *hover_event*, *insertion*,
*font* — sit beside the contents' own keys, and the siblings are a
non-empty list under *extra*. The seven contents codecs are registered in
an `ExtraCodecs.LateBoundIdMapper` under the names in the table above, and
`ComponentSerialization.createLegacyComponentMatcher` builds the matcher
that the object and data-source kinds reuse for their own *object* and
*source* fields. In JSON and NBT, encoding never writes a *type*: each
kind is written with its own distinguishing key (*text*, *translate*,
*keybind*, *score*, *selector*, *nbt*, and a sprite's *sprite* or
*player*), and a decoder recognises it by that key. A translatable
argument that decodes to a plain unstyled literal is collapsed back to a
string argument, so the argument list round-trips by meaning rather than
by shape.
### On the wire: NBT, and two budgets
On the wire, **components travel as NBT, not JSON**:
`ComponentSerialization.STREAM_CODEC` is built over the NBT ops — it is
`ByteBufCodecs.fromCodecWithRegistries`, which encodes through `NbtOps`
with the buffer's `RegistryOps` and writes the resulting `Tag`, and on
decode reads a `Tag` under an `NbtAccounter` and parses it back. The
accounter is the difference between the two families:
`NbtAccounter.defaultQuota` allows two mebibytes at depth 512, and the
trusted variants — `ComponentSerialization.TRUSTED_STREAM_CODEC` and its
siblings, built with `ByteBufCodecs.fromCodecWithRegistriesTrusted` over
`NbtAccounter.unlimitedHeap` — lift the NBT budget, and **every clientbound
chat packet uses them**
([packets and stream codecs](../networking/packets-and-stream-codecs.md)).
So does everything else a server authors: the death packet, entity custom
names through `EntityDataSerializers.OPTIONAL_COMPONENT`, score displays,
painting titles, command-suggestion tooltips. The budgeted codec is what
the two component-typed data components use — `DataComponents.CUSTOM_NAME`
and `DataComponents.ITEM_NAME` are synchronised with it — and those are
the components a creative player's stack carries serverbound. A third shape,
`ComponentSerialization.TRUSTED_CONTEXT_FREE_STREAM_CODEC`, needs no
registries and carries the texts that must decode before a registry
exists: `ClientboundDisconnectPacket`, the MOTD in
`ClientboundServerDataPacket`, a resource-pack prompt, server links. And
`ComponentSerialization.flatRestrictedCodec` caps a component by the length
of its JSON form; `WrittenBookContent.CONTENT_CODEC` uses it at 32,767 for
a page.
## Resolution: what a server does to a component before it sends it
Resolution — turning selectors, scores and NBT paths into text — is
`ComponentUtils.resolve` against a `ResolutionContext`, which carries the
command source, a depth limit and a `ResolutionContext.LimitBehavior`. The
walk copies the tree: `ComponentContents.resolve` returns a copy by default
and is overridden by the score, selector, nbt and object kinds, and by the
translatable kind, which resolves each component argument; siblings are
resolved after the contents, and a `HoverEvent.ShowText` inside a style is
resolved too. Past the depth limit — 100 by default —
`ResolutionContext.LimitBehavior.STOP_PROCESSING_AND_COPY_REMAINING` copies
the rest untouched and `ResolutionContext.LimitBehavior.DISCARD_REMAINING`
puts `CommonComponents.ELLIPSIS` in its place. A context with no command
source resolves score, selector and nbt contents to empty. The context also
carries an `ObjectInfo` validator: `ResolutionContext.validate` swaps a
rejected sprite for its fallback, which is how `ServerStatusPinger`
sanitises a server-list MOTD on the *client* — depth 16, discard past it,
and no player heads.
**Ordinary chat never resolves anything**: the content of a
`ServerboundChatPacket` is a plain string all the way to
`Component.literal`. Commands are the exception —
`MessageArgument.Message.toComponent` expands entity selectors inside a
message argument, behind a permission, which is why `/say @a` names people
and a chat line saying the same thing does not. The resolved text becomes
the message's *unsigned* content. The mechanics: `MessageArgument.Message.parseText`
refuses more than 256 characters and, if the source may use selectors at
all (`EntitySelectorParser.allowSelectors`), tries every `@` as a selector
and keeps the ones that parse as a `MessageArgument.Part` — an `@` that
fails on a missing or unknown selector type is left as ordinary text; at execution the permission is
`Permissions.COMMANDS_ENTITY_SELECTORS`, and each part becomes
`EntitySelector.joinNames` of what it finds. A raw component argument —
`/tellraw`'s — is a `ComponentArgument`, parsed with the full codec from
SNBT and resolved by `ComponentArgument.getResolvedComponent` with the
target player as the scoreboard entity.
## The death message
```mermaid
sequenceDiagram
participant SP as ServerPlayer
participant CT as CombatTracker
participant CS as ComponentSerialization
participant CPL as ClientPacketListener
participant DScr as DeathScreen
participant TrC as TranslatableContents
participant Language as Language
Note over SP,CT: the server tick in which the player dies
SP->>CT: die: getDeathMessage, if show_death_messages
CT->>CT: the last CombatEntry's DamageType.deathMessageType, then DamageSource.getLocalizedDeathMessage
CT-->>SP: translatable death.attack.arrow, arguments: the victim's display name, the killer's
SP->>CS: ClientboundPlayerCombatKillPacket to the victim, TRUSTED_STREAM_CODEC
SP->>CS: the same component in ClientboundSystemChatPacket to everyone, and getString for the log
Note over CS: the Netty thread, PacketEncoder
CS->>CS: encode through NbtOps: translate, with, and each argument a compound with hover_event and insertion
Note over CS,CPL: the client's Netty thread decodes, then ensureRunningOnSameThread
CS->>CPL: handlePlayerCombatKill, a MutableComponent nobody has read
CPL->>DScr: new DeathScreen with the packet's message, Render thread
Note over DScr: the next frame
DScr->>TrC: visitText, then Component.visit reaches the contents
TrC->>Language: decompose: Language.getInstance, getOrDefault(death.attack.arrow)
Language-->>TrC: the template from the client's language stack, en_us then the selected code
TrC-->>DScr: the victim's name, " was shot by ", the killer's name, each argument visited in turn
TrC->>Language: the killer's name, if it is a mob, is entity.minecraft.zombie, worded the same way
```
### Built on the server, in no language
`ServerPlayer.die` runs on the server thread. If
`GameRules.SHOW_DEATH_MESSAGES` is on it asks the `CombatTracker` for the
message; `CombatTracker.getDeathMessage` takes the last `CombatEntry`,
reads its `DamageType.deathMessageType`, and for the default kind hands
to `DamageSource.getLocalizedDeathMessage`, which picks a key from the
damage type's `DamageType.msgId` — *death.attack.* plus the id, with
*.player* when the source names no entity but `LivingEntity.getKillCredit`
still finds one (the last player, or failing that the last mob, to hurt
the victim), with *.item* when the killer's held item has a
`DataComponents.CUSTOM_NAME` — and builds `Component.translatable` with
the victim's and the killer's display names as arguments.
`DeathMessageType.FALL_VARIANTS` and `DeathMessageType.INTENTIONAL_GAME_DESIGN`
take two more branches, the second of them attaching a
`ClickEvent.OpenUrl` to a bracketed link — and the fall branch only when
`CombatTracker.getMostSignificantFall` actually found one, so a fall-typed
source with no recorded fall drops back to the ordinary message. A fourth
branch comes first: an empty combat log is *death.attack.generic*. The key
is a line in *en_us.json*; the tracker never sees the line.
The killer's name is itself a component, and often a translatable one.
`Entity.getDisplayName` is `PlayerTeam.formatNameForTeam` over
`Entity.getName` — the custom name if there is one, otherwise
`Entity.getTypeName`, which is `EntityType.getDescription`, a
`Component.translatable` of *entity.minecraft.zombie* — with a
`HoverEvent.ShowEntity` and the UUID as insertion. `Player.getDisplayName`
adds a `ClickEvent.SuggestCommand` of */tell name* and the name as
insertion. A team wraps the name in its prefix, suffix and colour. So the
argument list carries components inside a component, styles inside
styles, and a mob killer's name is a second key the client will look up
after the first.
The packet is sent twice over. `ClientboundPlayerCombatKillPacket` goes to
the victim, with a `PacketSendListener.exceptionallySend` fallback: if the
send fails, a replacement carries *death.attack.even_more_magic* with the
first 256 characters of the real message in a hover, so the death screen
never goes blank. The same component goes to everyone as system chat —
`PlayerList.broadcastSystemMessage`, or the team-scoped
`PlayerList.broadcastSystemToTeam` / `PlayerList.broadcastSystemToAllExceptTeam`
when the team's `Team.Visibility` says so — each as a
`ClientboundSystemChatPacket`. With the game rule off, the kill packet
still goes, carrying `CommonComponents.EMPTY`, and nothing is broadcast.
Both packets use `ComponentSerialization.TRUSTED_STREAM_CODEC`, so what
crosses is a compound with *translate* and *with*, the arguments compounds
of their own.
The server reads the message once, for its log:
`PlayerList.broadcastSystemMessage` starts with
`MinecraftServer.sendSystemMessage`, which logs `Component.getString`, and
the walk visits the translatable contents and asks `Language.getInstance`.
That is the ordinary path only. A victim on a team whose death-message
visibility is not `Team.Visibility.ALWAYS` goes through
`PlayerList.broadcastSystemToTeam` or
`PlayerList.broadcastSystemToAllExceptTeam` instead, neither of which
logs — and `Team.Visibility.NEVER` matches neither branch, so the message
reaches nobody and is never read at all.
On a server that is
`Language.DEFAULT_INSTANCE`, loaded once from the *en_us.json* on the
classpath, and nothing on the server ever calls `Language.inject`. A
named mob's death is logged the same way from `LivingEntity.die`. The
console is in English whatever the players speak.
### Worded on the client, on the first frame
On the client, `ClientPacketListener.handlePlayerCombatKill` hops to the
Render thread and constructs a `DeathScreen` with
`ClientboundPlayerCombatKillPacket.message`, or
respawns at once if `LocalPlayer.shouldShowDeathScreen` is off. Nothing has
read the key. The next frame's `DeathScreen.visitText` hands the component
to the text collector, and the walk reaches
`TranslatableContents.decompose`, which asks the current `Language` for
the template. That `Language` is a `ClientLanguage`, built by
`LanguageManager.onResourceManagerReload` from `ClientLanguage.loadFrom`
over a stack of two codes — *en_us* first, then the selected language if
its pack declares it — reading *lang/code.json* from every namespace of
every enabled pack, merged in stack order. A key missing from the chosen
language falls to English; a key missing from both is shown as itself, by
`Language.getOrDefault`, unless the component carried a fallback from
`Component.translatableWithFallback`. `Language.loadFromJson` rewrites a
*%d* or *%f* specifier to *%s* as it loads, which is why translators' number
specifiers do not crash the decomposer. The template's arguments are
visited in their turn — the killer's name, if it is *entity.minecraft.zombie*,
goes back to the same `Language` — and the sentence exists, in this
client's language, for the first time. The chat line took the same path
through `ChatListener.handleSystemMessage`.
## What this page does not own
Signing and chat delivery — the `PlayerChatMessage`, the session, the
*Not Secure* tag — is [chat and signing](../networking/chat-and-signing.md).
Everything after `Component.visit` — `StringDecomposer`,
`FormattedCharSequence`, wrapping, reordering, glyphs — is
[text and fonts](../client/text-and-fonts.md). What a `Codec` is, and how
NBT and JSON differ under one, is
[codecs, NBT and JSON](codecs-nbt-json.md#one-abstraction-and-the-ops-that-are-not-formats).
## Questions players ask
**Why did my friend's death message say something different from mine?**
Because neither client received a sentence. Both received
*death.attack.arrow* and two name components, and each client's
`Language` supplied its own template on the first frame that drew it.
**Why does `/tellraw` with a selector name people when chat with an `@`
does not?** Chat is a literal, always: nothing in the chat path calls
`ComponentUtils.resolve`. A command's message argument parses `@` into
`MessageArgument.Part`s and expands them behind
`Permissions.COMMANDS_ENTITY_SELECTORS`; a `/tellraw` component is resolved
whole by `ComponentArgument.getResolvedComponent`.
**Why does a message sometimes show as a raw key like *death.attack.foo*?**
The key is in neither the selected language nor *en_us*, and the component
has no fallback, so `Language.getOrDefault` returned the key. A server or
data pack that invents a key without shipping a resource pack for it shows
the key on every client.
**Why can a server show me a link but not open a file?**
`ClickEvent.Action.OPEN_FILE` fails `ClickEvent.Action.filterForSerialization`,
so it cannot be encoded into any packet, data pack or book by either side.
Only client code that constructs the `ClickEvent.OpenFile` in memory — the
screenshot notice, a debug dump's path — can present one.
**What does an object component actually put in the text?** One U+FFFC
placeholder whose style names a sprite font — an atlas sprite or a player
head — so the font system draws the picture where a glyph would go.
Anything that reads the text as a string sees *[sprite]* or *[name head]*
instead.
**Why does the server console show death messages in English?** The server's
`Language` is `Language.DEFAULT_INSTANCE`, the bundled *en_us.json*;
`Language.inject` is called only by the client's `LanguageManager`.
## Where to look
`Component` · `MutableComponent` · `ComponentContents` · `PlainTextContents` ·
`TranslatableContents.decompose` · `KeybindContents` · `ScoreContents` ·
`SelectorContents` · `NbtContents` · `ObjectContents` · `Style.applyTo` ·
`TextColor` · `ClickEvent.Action.filterForSerialization` · `HoverEvent` ·
`ComponentSerialization.CODEC` · `ComponentSerialization.STREAM_CODEC` ·
`ByteBufCodecs.fromCodecWithRegistries` · `ComponentUtils.resolve` ·
`ResolutionContext` · `MessageArgument.Message.toComponent` ·
`CombatTracker.getDeathMessage` · `DamageSource.getLocalizedDeathMessage` ·
`ServerPlayer.die` · `ClientboundPlayerCombatKillPacket` ·
`ClientPacketListener.handlePlayerCombatKill` · `DeathScreen.visitText` ·
`Language.getOrDefault` · `ClientLanguage.loadFrom` ·
`LanguageManager.onResourceManagerReload`
---
*Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes `tools/verify_names.py`.*
---
# The data-driven type pattern
> Verified against **Minecraft 26.2** · Part II · A data pack's loot table says *"function": "minecraft:set_count"*, and the game turns that string into an object it never named in code.
A data-pack author writes a chest loot table, gives one entry a function
whose *function* field says *minecraft:set_count* and whose *count* is a
range, and drops the file into *data/mypack/loot_table/chests/*. Nothing in
the pack says `SetItemCountFunction`. Nothing in the jar says
*mypack*. When the server reloads, `ReloadableServerRegistries.reload`
scans the directory, hands the file to `LootTable.DIRECT_CODEC`, and
somewhere inside that codec the string *minecraft:set_count* is looked up
in `BuiltInRegistries.LOOT_FUNCTION_TYPE` — a registry that was filled by a
static initialiser and frozen before any world existed — and the
`MapCodec` it finds there reads the rest of the object. The result is a
`SetItemCountFunction`, and the first time a player opens that chest,
`SetItemCountFunction.run` calls `ItemStack.setCount` on every stack the
pool emits. The same move is made in fifty-six places. That is why *type*
is the most important key in a data pack: **every file that has one is a
lookup in a registry data packs cannot add to**, so a pack can compose the
game's behaviours endlessly and never add a new one.
## The cast
| class | what it decides | thread |
|---|---|---|
| `MapCodec` (DataFixerUpper) | how to read one kind's fields out of a JSON object that also carries a type key; the registry element in the bare spelling | — |
| `Registry` | `Registry.byNameCodec`: a string to an element, with the error *Unknown registry key* and the element's registration lifecycle attached | — |
| `BuiltInRegistries` | the registries of kinds — filled at `BuiltInRegistries.bootStrap`, frozen, identical on client and server | main thread, at `Bootstrap` |
| `ReloadableServerRegistries` | the three loot registries (`LootDataType.TABLE`, `LootDataType.MODIFIER`, `LootDataType.PREDICATE`) rebuilt on every reload | the background executor |
| `RegistryOps` | the ops that let a codec resolve a `Holder` to another data-pack element while it decodes | wherever the codec runs |
| `LootItemFunctions` | `LootItemFunctions.TYPED_CODEC`, the dispatch codec for one instance of the pattern; `LootItemFunctions.compose`, the list of functions folded into one | — |
| `SetItemCountFunction` | the kind traced below: conditions, a `NumberProvider`, an *add* flag | Server, when the loot rolls |
| `Holder` | how everything else refers to the loaded element — by key, bound later | — |
## The idea, stated once
A codec built by `Codec.dispatch` reads one field of a JSON object — *type*
unless the caller names another — decodes it with a codec for the *kind*,
and asks that kind for a `MapCodec` to read the remaining fields. The class
that does it is DataFixerUpper's `KeyDispatchCodec` — a `Codec` and a
`DynamicOps` being the two halves of every read in this part
([codecs, NBT and JSON](codecs-nbt-json.md#one-abstraction-and-the-ops-that-are-not-formats)) —
which is why the
`MapCodec` a kind supplies is a *map* codec: it reads a set of fields from
the same object the type key came from, so the file looks flat. When the
kind codec is `Registry.byNameCodec` over a registry in `BuiltInRegistries`,
the set of kinds is whatever the jar registered at `Bootstrap`, and a pack
can reach every one of them by name and none it did not ship. The element
that comes out is then either **registered** — a `RegistryDataLoader`
registry such as `Registries.STRUCTURE`, or one of the three
`ReloadableServerRegistries` registries — or **inline**, a value inside a
larger element that has no id of its own, such as the `PlacementModifier`
list in a `PlacedFeature`. A registered element is referred to everywhere
else by `Holder`: a `RegistryFileCodec` reads either an id or the inline
object, and the identifiers page explains how the reference is handed out
before the entry exists ([identifiers and registries](identifiers-and-registries.md)).
```mermaid
flowchart LR
F["a data-pack file with a type key"] --> D["Codec.dispatch over Registry.byNameCodec"]
D --> K["a built-in registry of kinds, frozen at Bootstrap"]
K --> M["that kind's MapCodec reads the remaining fields"]
M --> O["an object of a class the file never named"]
O --> R["registered by RegistryDataLoader or ReloadableServerRegistries, or inline in a larger element"]
R --> H["referred to by Holder from other files and from the wire"]
```
The pattern has two spellings, and they differ only in what the registry
holds. In the **bare** spelling the element *is* the `MapCodec`:
`BuiltInRegistries.LOOT_FUNCTION_TYPE` is a `Registry` of
`MapCodec`, `LootItemFunctions.bootstrap` registers
`SetItemCountFunction.MAP_CODEC` under *set_count*, and
`LootItemFunction.codec` is how a live object names its own kind for
encoding. In the **type-object** spelling the element is a small interface
or record that wraps the codec: `PlacementModifierType` is an interface
with one method, `PlacementModifierType.codec`, its constants such as
`PlacementModifierType.COUNT` are registered into
`BuiltInRegistries.PLACEMENT_MODIFIER_TYPE`, and `PlacementModifier.CODEC`
dispatches on `PlacementModifier.type`. The type object exists so that a
kind can carry something beside its codec — `RecipeSerializer`,
`ConsumeEffect.Type` and `RecipeDisplay.Type` are records of a `MapCodec`
and a `StreamCodec`, one for the file and one for the wire. There is a
**third** spelling with two members, in which the type object is the
behaviour itself: a `Feature` is registered into
`BuiltInRegistries.FEATURE`, `Feature.place` is what it does, and what the
file supplies is only a *config* — `Feature.configuredCodec` wraps the
feature's configuration codec under that key, and
`ConfiguredFeature.DIRECT_CODEC` dispatches to it. `WorldCarver` and
`ConfiguredWorldCarver.DIRECT_CODEC` are the same shape.
Seven of the instances accept a bare value in place of the object:
`IntProviders.CODEC`, `FloatProviders.CODEC` and `NumberProviders.CODEC`
read a plain number as a constant, `DensityFunctions.DIRECT_CODEC` reads a
plain number as `DensityFunctions.Constant`, a height provider reads a bare
anchor, and `NbtProviders.CODEC` and `ScoreboardNameProviders.CODEC` read a
bare string as the context form. Three accept a bare **list**:
`LootItemFunctions.ROOT_CODEC` tries `LootItemFunctions.TYPED_CODEC` and
falls back to `SequenceFunction.INLINE_CODEC`, so a JSON array where one
function was expected is a sequence of them; `LootItemCondition` does the
same through `AllOfCondition.INLINE_CODEC`, and `SlotSources` through
`GroupSlotSource.INLINE_CODEC`.
> **For a 1.21-era reader.** The loot package has no type-object class any
> more: there is no *LootItemFunctionType* record wrapping a `MapCodec`,
> and `BuiltInRegistries.LOOT_FUNCTION_TYPE`,
> `BuiltInRegistries.LOOT_CONDITION_TYPE` and the provider registries hold
> the `MapCodec` itself. The worldgen registries
> kept their type objects. Both spellings dispatch identically.
## Fifty-six of them
**Fifty-six** — registries in `BuiltInRegistries` that a codec dispatches
on from the **value** of a field, counted at the dispatch sites:
thirty-one bare, twenty-three type-object, two where the type is the
behaviour. The dispatch key is *type* unless the row says otherwise. The
criterion is the value *in the data this book is about*, not
`Registry.byNameCodec` itself: four more registries dispatch through it and are
not here. `BuiltInRegistries.GAME_RULE` and `BuiltInRegistries.STAT_TYPE`
spell the registry name as the *key* of a map rather than the value of a
field. `BuiltInRegistries.ENVIRONMENT_ATTRIBUTE` and
`BuiltInRegistries.DATA_COMPONENT_TYPE` do both: they are keys everywhere a
data pack meets them, and each also backs exactly one field dispatch —
`EnvironmentAttributeCheck.MAP_CODEC` on *attribute*, and one client
item-model property on *component*. All four are among the exceptions below.
### The bare spelling: the registry holds a `MapCodec`
| registry | element | key | where the elements live | taught in |
|---|---|---|---|---|
| `BuiltInRegistries.LOOT_POOL_ENTRY_TYPE` | `LootPoolEntryContainer` | | inline in loot tables | [loot tables](../items/loot-tables.md) |
| `BuiltInRegistries.LOOT_FUNCTION_TYPE` | `LootItemFunction` | *function* | `Registries.ITEM_MODIFIER` (reloadable), and inline in tables, pools and entries | [loot tables](../items/loot-tables.md) |
| `BuiltInRegistries.LOOT_CONDITION_TYPE` | `LootItemCondition` | *condition* | `Registries.PREDICATE` (reloadable), and inline | [loot tables](../items/loot-tables.md) |
| `BuiltInRegistries.LOOT_NUMBER_PROVIDER_TYPE` | `NumberProvider` | | inline; a bare number is a constant | [loot tables](../items/loot-tables.md) |
| `BuiltInRegistries.LOOT_NBT_PROVIDER_TYPE` | `NbtProvider` | | inline in loot functions | [loot tables](../items/loot-tables.md) |
| `BuiltInRegistries.LOOT_SCORE_PROVIDER_TYPE` | `ScoreboardNameProvider` | | inline in loot conditions | [loot tables](../items/loot-tables.md) |
| `BuiltInRegistries.SLOT_SOURCE_TYPE` | `SlotSource` | | inline in `SlotLoot` entries and container-modifying functions | [loot tables](../items/loot-tables.md) |
| `BuiltInRegistries.INT_PROVIDER_TYPE` | `IntProvider` | | inline in feature configs and elsewhere; a bare integer is a constant | [features and placement](../worldgen/features-and-placement.md) |
| `BuiltInRegistries.FLOAT_PROVIDER_TYPE` | `FloatProvider` | | inline; a bare float is a constant | [features and placement](../worldgen/features-and-placement.md) |
| `BuiltInRegistries.DENSITY_FUNCTION_TYPE` | `DensityFunction` | | `Registries.DENSITY_FUNCTION`, and inline; a bare number is a constant | [density functions](../worldgen/density-functions.md) |
| `BuiltInRegistries.MATERIAL_CONDITION` | `SurfaceRules.ConditionSource` | | inline in `Registries.NOISE_SETTINGS` | [terrain](../worldgen/terrain.md) |
| `BuiltInRegistries.MATERIAL_RULE` | `SurfaceRules.RuleSource` | | inline in `Registries.NOISE_SETTINGS` | [terrain](../worldgen/terrain.md) |
| `BuiltInRegistries.BIOME_SOURCE` | `BiomeSource` | | inline in `Registries.LEVEL_STEM` | [biomes](../worldgen/biomes.md) |
| `BuiltInRegistries.CHUNK_GENERATOR` | `ChunkGenerator` | | inline in `Registries.LEVEL_STEM` | [terrain](../worldgen/terrain.md) |
| `BuiltInRegistries.STRUCTURE_PROCESSOR` | `StructureProcessor` | *processor_type* | `Registries.PROCESSOR_LIST` | [jigsaw and templates](../worldgen/jigsaw-and-templates.md) |
| `BuiltInRegistries.POOL_ALIAS_BINDING_TYPE` | `PoolAliasBinding` | | inline in jigsaw structures | [jigsaw and templates](../worldgen/jigsaw-and-templates.md) |
| `BuiltInRegistries.ENCHANTMENT_LEVEL_BASED_VALUE_TYPE` | `LevelBasedValue` | | inline in `Registries.ENCHANTMENT` | [enchantments](../items/enchantments.md) |
| `BuiltInRegistries.ENCHANTMENT_ENTITY_EFFECT_TYPE` | `EnchantmentEntityEffect` | | inline in `Registries.ENCHANTMENT` | [enchantments](../items/enchantments.md) |
| `BuiltInRegistries.ENCHANTMENT_LOCATION_BASED_EFFECT_TYPE` | `EnchantmentLocationBasedEffect` | | inline in `Registries.ENCHANTMENT` | [enchantments](../items/enchantments.md) |
| `BuiltInRegistries.ENCHANTMENT_VALUE_EFFECT_TYPE` | `EnchantmentValueEffect` | | inline in `Registries.ENCHANTMENT` | [enchantments](../items/enchantments.md) |
| `BuiltInRegistries.ENCHANTMENT_PROVIDER_TYPE` | `EnchantmentProvider` | | `Registries.ENCHANTMENT_PROVIDER` | [enchantments](../items/enchantments.md) |
| `BuiltInRegistries.SPAWN_CONDITION_TYPE` | `SpawnCondition` | | inline in entity variants, through `SpawnPrioritySelectors.CODEC` | [entity lifecycle](../entities/entity-lifecycle.md) |
| `BuiltInRegistries.TEST_ENVIRONMENT_DEFINITION_TYPE` | `TestEnvironmentDefinition` | | `Registries.TEST_ENVIRONMENT` | [game tests](../commands/game-tests.md) |
| `BuiltInRegistries.TEST_INSTANCE_TYPE` | `GameTestInstance` | | `Registries.TEST_INSTANCE` | [game tests](../commands/game-tests.md) |
| `BuiltInRegistries.DIALOG_TYPE` | `Dialog` | | `Registries.DIALOG` | [dialogs](../commands/dialogs.md) |
| `BuiltInRegistries.DIALOG_ACTION_TYPE` | `Action` | | inline in dialogs | [dialogs](../commands/dialogs.md) |
| `BuiltInRegistries.DIALOG_BODY_TYPE` | `DialogBody` | | inline in dialogs | [dialogs](../commands/dialogs.md) |
| `BuiltInRegistries.INPUT_CONTROL_TYPE` | `InputControl` | | inline in dialogs, as a `MapCodec` (`Codec.dispatchMap`) | [dialogs](../commands/dialogs.md) |
| `BuiltInRegistries.PERMISSION_TYPE` | `Permission` | | inline in a permission check | [Brigadier and commands](../commands/brigadier-and-commands.md) |
| `BuiltInRegistries.PERMISSION_CHECK_TYPE` | `PermissionCheck` | | only written, by `ArgumentUtils` into the command-tree report | [Brigadier and commands](../commands/brigadier-and-commands.md) |
| `BuiltInRegistries.BLOCK_TYPE` | `Block` | | nothing loads it: every block is Java, and `BlockTypes.CODEC` is read by no one and written only by `BlockListReport` | [blocks and states](../blocks/blocks-and-states.md) |
### The type-object spelling: the registry holds a type that carries a `MapCodec`
| registry | type object | element | key | where the elements live | taught in |
|---|---|---|---|---|---|
| `BuiltInRegistries.PLACEMENT_MODIFIER_TYPE` | `PlacementModifierType` | `PlacementModifier` | | inline in `Registries.PLACED_FEATURE` | [features and placement](../worldgen/features-and-placement.md) |
| `BuiltInRegistries.HEIGHT_PROVIDER_TYPE` | `HeightProviderType` | `HeightProvider` | | inline in placements; a bare anchor is a constant | [features and placement](../worldgen/features-and-placement.md) |
| `BuiltInRegistries.BLOCK_PREDICATE_TYPE` | `BlockPredicateType` | `BlockPredicate` | | inline in features and placements | [features and placement](../worldgen/features-and-placement.md) |
| `BuiltInRegistries.BLOCKSTATE_PROVIDER_TYPE` | `BlockStateProviderType` | `BlockStateProvider` | | inline in feature configs | [features and placement](../worldgen/features-and-placement.md) |
| `BuiltInRegistries.TRUNK_PLACER_TYPE` | `TrunkPlacerType` | `TrunkPlacer` | | inline in tree configs | [trees](../worldgen/trees.md) |
| `BuiltInRegistries.FOLIAGE_PLACER_TYPE` | `FoliagePlacerType` | `FoliagePlacer` | | inline in tree configs | [trees](../worldgen/trees.md) |
| `BuiltInRegistries.ROOT_PLACER_TYPE` | `RootPlacerType` | `RootPlacer` | | inline in tree configs | [trees](../worldgen/trees.md) |
| `BuiltInRegistries.TREE_DECORATOR_TYPE` | `TreeDecoratorType` | `TreeDecorator` | | inline in tree configs | [trees](../worldgen/trees.md) |
| `BuiltInRegistries.FEATURE_SIZE_TYPE` | `FeatureSizeType` | `FeatureSize` | | inline in tree configs | [trees](../worldgen/trees.md) |
| `BuiltInRegistries.STRUCTURE_TYPE` | `StructureType` | `Structure` | | `Registries.STRUCTURE` | [structure placement](../worldgen/structure-placement.md) |
| `BuiltInRegistries.STRUCTURE_PLACEMENT` | `StructurePlacementType` | `StructurePlacement` | | inline in `Registries.STRUCTURE_SET` | [structure placement](../worldgen/structure-placement.md) |
| `BuiltInRegistries.STRUCTURE_POOL_ELEMENT` | `StructurePoolElementType` | `StructurePoolElement` | *element_type* | inline in `Registries.TEMPLATE_POOL` | [jigsaw and templates](../worldgen/jigsaw-and-templates.md) |
| `BuiltInRegistries.RULE_TEST` | `RuleTestType` | `RuleTest` | *predicate_type* | inline in processor lists | [jigsaw and templates](../worldgen/jigsaw-and-templates.md) |
| `BuiltInRegistries.POS_RULE_TEST` | `PosRuleTestType` | `PosRuleTest` | *predicate_type* | inline in processor lists | [jigsaw and templates](../worldgen/jigsaw-and-templates.md) |
| `BuiltInRegistries.RULE_BLOCK_ENTITY_MODIFIER` | `RuleBlockEntityModifierType` | `RuleBlockEntityModifier` | | inline in processor rules | [jigsaw and templates](../worldgen/jigsaw-and-templates.md) |
| `BuiltInRegistries.RECIPE_SERIALIZER` | `RecipeSerializer` | `Recipe` | | the `RecipeMap` that `RecipeManager` builds from *data/<ns>/recipe/* — a reload listener, not a registry | [recipes](../items/recipes.md) |
| `BuiltInRegistries.RECIPE_DISPLAY` | `RecipeDisplay.Type` | `RecipeDisplay` | | inline, mostly on the wire to the recipe book | [recipes](../items/recipes.md) |
| `BuiltInRegistries.SLOT_DISPLAY` | `SlotDisplay.Type` | `SlotDisplay` | | inline in recipe displays | [recipes](../items/recipes.md) |
| `BuiltInRegistries.CONSUME_EFFECT_TYPE` | `ConsumeEffect.Type` | `ConsumeEffect` | | inline in the `Consumable` and `DeathProtection` components | [data components](data-components.md) |
| `BuiltInRegistries.TRIGGER_TYPES` | `CriterionTrigger` | `Criterion` | *trigger*, with the fields under *conditions* (`ExtraCodecs.dispatchOptionalValue`) | advancements, loaded by `ServerAdvancementManager` — a reload listener, not a registry | [advancements](../commands/advancements.md) |
| `BuiltInRegistries.PARTICLE_TYPE` | `ParticleType` | `ParticleOptions` | | inline in biome ambient particles (`AmbientParticle`) and area effect clouds | [particles](../rendering/particles.md) |
| `BuiltInRegistries.NUMBER_FORMAT_TYPE` | `NumberFormatType` | `NumberFormat` | | inline in `Objective` and `Score` — save data and commands, no pack | [scoreboard and data](../commands/scoreboard-and-data.md) |
| `BuiltInRegistries.POSITION_SOURCE_TYPE` | `PositionSourceType` | `PositionSource` | | `VibrationParticleOption`, and one enchantment effect (`SpawnParticlesEffect`) — save data and the wire, no pack | [game events and vibrations](../world/game-events-and-vibrations.md) |
The first nine rows are the sub-objects of a configured or placed feature,
and the trace on [features and placement](../worldgen/features-and-placement.md)
walks a tree through all of them.
### The type is the behaviour: the file supplies only a config
| registry | type object | element | where the elements live | taught in |
|---|---|---|---|---|
| `BuiltInRegistries.FEATURE` | `Feature` | `ConfiguredFeature` | `Registries.CONFIGURED_FEATURE` | [features and placement](../worldgen/features-and-placement.md) |
| `BuiltInRegistries.CARVER` | `WorldCarver` | `ConfiguredWorldCarver` | `Registries.CONFIGURED_CARVER` | [terrain](../worldgen/terrain.md) |
Three of the registered destinations are not in `RegistryDataLoader` at
all. `Registries.LOOT_TABLE`, `Registries.ITEM_MODIFIER` and
`Registries.PREDICATE` are built by `ReloadableServerRegistries`, which
`DatapackStructureReport` calls stable dynamic registries, while
`Registries.RECIPE` and `Registries.ADVANCEMENT` it calls pseudo-registries:
keys exist for them, directories are named after them, and no `Registry`
is ever constructed. The elements that reach the client are the ones in
`RegistryDataLoader.SYNCHRONIZED_REGISTRIES`, re-encoded with the same
direct codec, which is why `BuiltInRegistries` must be identical on both
sides: the client runs the same dispatch on the same kinds
([protocol phases](../networking/protocol-phases.md)).
## One instance traced: *set_count*
```mermaid
sequenceDiagram
participant RSReg as ReloadableServerRegistries
participant LT as LootTable
participant LIF as LootItemFunctions
participant BIR as BuiltInRegistries
participant SICF as SetItemCountFunction
participant CBE as ChestBlockEntity
Note over RSReg: a reload, on the background executor
RSReg->>RSReg: reload builds a RegistryOps over JsonOps, then scanDirectory per LootDataType
RSReg->>LT: DIRECT_CODEC parses data/mypack/loot_table/chests/mine.json
LT->>LIF: a functions entry, ROOT_CODEC then TYPED_CODEC reads the function key
LIF->>BIR: LOOT_FUNCTION_TYPE.byNameCodec looks up minecraft:set_count
BIR-->>LIF: SetItemCountFunction.MAP_CODEC, out of a frozen registry
LIF->>SICF: MAP_CODEC reads conditions, count and add, the object exists
SICF-->>LT: compose folds the list into one BiFunction
LT-->>RSReg: registered in a fresh MappedRegistry, validated, the RELOADABLE layer replaced
Note over CBE: a later tick, on the server thread, a player opens the chest
CBE->>RSReg: unpackLootTable asks reloadableRegistries for the key
RSReg-->>CBE: the LootTable, or LootTable.EMPTY for an unknown key
CBE->>LT: fill, then getRandomItems with a CHEST context
LT->>SICF: decorate wraps the output, every emitted stack passes through apply
SICF->>SICF: the conditions pass, run calls ItemStack.setCount with count.getInt
SICF-->>CBE: the stack lands in a slot
```
**The reload half.** `MinecraftServer.reloadResources` — and `WorldLoader.load`
on first start — calls `ReloadableServerResources.loadResources`, whose first
act is `ReloadableServerRegistries.reload` on the background executor. It
builds a `RegistryOps` over `JsonOps.INSTANCE` from a
`HolderLookup.Provider` that already carries the updated tags, so a loot
condition can name an item tag while it decodes
([tags](tags.md#the-four-moments-tags-are-loaded)). For each of the three
`LootDataType`s it creates a new `MappedRegistry` and calls
`SimpleJsonResourceReloadListener.scanDirectory` — the every-JSON-file-in-a-directory
listener shape ([the resource system](resource-system.md#prepare-every-listener-at-once)) —
whose lister is `FileToIdConverter.registry` over `Registries.elementsDirPath` — the
directory *is* the registry's path, *loot_table* — and which parses every
file with the type's codec. A file that fails to parse is logged and
skipped, and a duplicate id is an error, so one bad table costs one table.
Inside `LootTable.DIRECT_CODEC` the *functions* list is
`LootItemFunctions.ROOT_CODEC`; the entry in question is an object, so
`LootItemFunctions.TYPED_CODEC` runs: `Registry.byNameCodec` on
`BuiltInRegistries.LOOT_FUNCTION_TYPE` reads *minecraft:set_count*,
finds `SetItemCountFunction.MAP_CODEC`, and that codec reads *conditions*
(the `LootItemConditionalFunction.commonFields` every conditional function
shares, itself a list dispatched on `BuiltInRegistries.LOOT_CONDITION_TYPE`),
*count* through `NumberProviders.CODEC` — a third dispatch, on
`BuiltInRegistries.LOOT_NUMBER_PROVIDER_TYPE` — and the optional *add*.
Three built-in registries were consulted to build one function, and the
file named none of them. A misspelt kind fails at the first of them with
an *Unknown registry key* error that names the registry of kinds, and the
whole file is dropped. The `LootTable` constructor then calls
`LootItemFunctions.compose`, which folds each list of functions into a
single function; a list of one is the function itself.
After all three registries are built,
`ReloadableServerRegistries.createUpdatedRegistries` replaces the
`RegistryLayer.RELOADABLE` layer through `LayeredRegistryAccess.replaceFrom`
and `ReloadableServerRegistries.validateLootRegistries` runs
`LootDataType.runValidation` over every element:
`SetItemCountFunction.validate` checks its count provider's references
against the finished lookup. Validation **warns** — problems are logged,
the element stays registered. Every element in these three registries is
`Lifecycle.experimental`.
**The run half.** `RandomizableContainerBlockEntity.getItem` — like the
other container methods on that class — calls
`RandomizableContainer.unpackLootTable`, which asks
`MinecraftServer.reloadableRegistries` for the table by `ResourceKey`; an
unknown key is `LootTable.EMPTY`, never an exception. `LootTable.fill` rolls
`LootTable.getRandomItems` with a `LootContextParamSets.CHEST` context and
`LootTable.shuffleAndSplitItems` spreads the result over the slots. On the
way out, each level wraps the consumer: `LootTable.getRandomItemsRaw`
decorates the output with the table's composite function, `LootPool.addRandomItems`
with the pool's, and `LootPoolSingletonContainer.EntryBase` with the
entry's, each through `LootItemFunction.decorate`. `LootItem.createItemStack`
makes a stack of one, and it passes through
`LootItemConditionalFunction.apply`, which tests the conditions and, if they
pass, calls `SetItemCountFunction.run` — `ItemStack.setCount` with
`NumberProvider.getInt`, added to the current count if *add* was set. The
object the pack described by a string is now a method call on a stack in a
chest.
## What does not follow the pattern
Not every registry in `BuiltInRegistries` whose name ends in *type* is a
registry of kinds, and most of the ones that are not fall into three
groups. A few fall outside them altogether — `BuiltInRegistries.TICKET_TYPE`,
`BuiltInRegistries.MAP_DECORATION_TYPE`,
`BuiltInRegistries.POINT_OF_INTEREST_TYPE` and
`BuiltInRegistries.VILLAGER_TYPE` are registries of ordinary things whose
names happen to end in *type*, dispatching nothing, and
`BuiltInRegistries.ATTRIBUTE_TYPE` has a `Registry.byNameCodec` —
`AttributeTypes.CODEC` — that nothing in the tree reads; the attribute name
a file actually uses as a key belongs to
`BuiltInRegistries.ENVIRONMENT_ATTRIBUTE`.
**A key, not a kind.** `BuiltInRegistries.DATA_COMPONENT_TYPE`,
`BuiltInRegistries.ENCHANTMENT_EFFECT_COMPONENT_TYPE`,
`BuiltInRegistries.GAME_RULE` and `BuiltInRegistries.ENVIRONMENT_ATTRIBUTE`
each hold objects that carry a codec for
their *value*, and a file uses them as JSON **keys**: `GameRuleMap.CODEC`
and `DataComponentPredicate.CODEC` are `Codec.dispatchedMap`, a map whose
key codec is `Registry.byNameCodec` and whose value codec depends on the
key. There is no *type* field because the name of the field is the type.
`BuiltInRegistries.ENTITY_SUB_PREDICATE_TYPE` is the same shape and holds a
plain `Codec` rather than a `MapCodec`, and `EntityPredicate` reads it as a
dispatched map too. `BuiltInRegistries.STAT_TYPE` is a key whose value codec
is derived from `StatType.getRegistry` rather than stored, which is what
`PlayerPredicate.StatMatcher` builds on. `BuiltInRegistries.MEMORY_MODULE_TYPE`
is a key in a brain's saved memories, `MemoryMap.CODEC`, the same way.
**A type object with no codec.** `BuiltInRegistries.RECIPE_TYPE` is the
one a modder reaches for and the wrong one: `RecipeType.CRAFTING` groups
recipes for lookup, and the kind a recipe file names — the field
`Recipe.CODEC` dispatches on through `Recipe.getSerializer` — is a
`RecipeSerializer`, in `BuiltInRegistries.RECIPE_SERIALIZER`.
`BuiltInRegistries.STRUCTURE_PIECE` holds `StructurePieceType`, a loader
from NBT with a `StructurePieceSerializationContext`, for the pieces of a
started structure saved in the chunk — save data, not a pack.
`BuiltInRegistries.ENTITY_TYPE`, `BuiltInRegistries.BLOCK_ENTITY_TYPE`,
`BuiltInRegistries.MENU`, `BuiltInRegistries.SENSOR_TYPE` and
`BuiltInRegistries.COMMAND_ARGUMENT_TYPE`
are registries of type objects that no codec dispatches on: they are
looked up by name and construct or describe things in Java.
**A registry of kinds with nothing to load.** `BuiltInRegistries.BLOCK_TYPE`
is a complete instance of the bare spelling — `BlockTypes.CODEC` dispatches
on it through `Block.codec` — that no data pack and no loader ever reads.
Its one caller is the data generator's `BlockListReport`, which encodes
every block with it. It is the pattern applied for the sake of the report.
## Questions players ask
**Can a data pack add a new loot function, placement modifier or dialog
kind?** No. Every kind is an entry in a `BuiltInRegistries` registry, and
those are frozen at `Bootstrap`. A pack composes kinds; only the jar adds
one.
**Why does one typo break the whole file and not the whole pack?**
`SimpleJsonResourceReloadListener.scanDirectory` parses each file on its
own and logs the ones that fail, so a loot table that names
*minecraft:set_cuont* is simply missing, and the chest that names the table
gets `LootTable.EMPTY`. `RegistryDataLoader` is stricter: it collects every
error by key and fails the whole load, which is why a broken biome stops
the world from opening and a broken loot table does not.
**Why is the same kind name accepted in a pool, an entry and a table?**
Because the field is decoded by the same `LootItemFunctions.ROOT_CODEC` in
all three places, and the three composite functions wrap the output
consumer one inside the other. A function on the table runs last.
**Why do worldgen files sometimes take a number where an object was
expected?** `Codec.either` in front of the dispatch: a bare number is a
constant for int, float and number providers and for density functions.
## Where to look
`Codec.dispatch` · `KeyDispatchCodec` · `Registry.byNameCodec` ·
`BuiltInRegistries` · `LootItemFunctions.TYPED_CODEC` ·
`LootItemFunctions.bootstrap` · `SetItemCountFunction.MAP_CODEC` ·
`PlacementModifier.CODEC` · `PlacementModifierType` ·
`ConfiguredFeature.DIRECT_CODEC` · `Feature.configuredCodec` ·
`LootDataType` · `ReloadableServerRegistries.reload` ·
`SimpleJsonResourceReloadListener.scanDirectory` · `RegistryFileCodec` ·
`RegistryDataLoader.WORLDGEN_REGISTRIES` · `LootTable.fill` ·
`LootItemFunction.decorate` · `SetItemCountFunction.run`
---
*Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes `tools/verify_names.py`.*
---
# III · The server
> Verified against **Minecraft 26.2** · Part III · The program that owns the world: one thread, one loop, twenty times a second, from the command line that starts it to the exception that ends it.
Everything a player thinks of as *the world* — the blocks, the mobs, the
weather, the hunger bar — is state owned by one object on one thread, and
this part is that thread doing a full lap. Part I named the Server thread;
this is the first place you watch it work. A player recognises this part by
its clock: nothing in the world happens continuously. A furnace advances in
steps of a twentieth of a second, a hopper moves one item per eight of
them, and when the console prints *Can't keep up! Is the server overloaded?*
some of those steps did not happen at all and never will.
## The shape of the part
Part III is a line into a loop and out again. Two pages are the loop itself
— the server tick and the one step of it that is a whole lecture — and the
other three are the beginning, the population and the end.
```mermaid
flowchart LR
Start["Starting a server: java -jar to the word Done"]
Tick["The server tick: 50 ms on the Server thread"]
Level["The level tick: one dimension advances"]
Players["Players and sessions: who is in the loop"]
Death["How a server dies: three endings"]
Start -- "the Server thread is spun, the levels are built" --> Tick
Tick -- "tickChildren calls ServerLevel.tick, overworld first" --> Level
Level -- "the packets the tick decided to send" --> Tick
Tick -- "the connection phase, after the levels" --> Players
Players -- "a join, a respawn, a disconnect" --> Tick
Tick -- "the loop's finally, which two of the three endings reach" --> Death
```
## Before you start
[Anatomy](../anatomy/anatomy.md), and specifically its *two loops* figure.
This part assumes you know that the Server thread is an event loop as well
as a game loop, that a packet is decoded on a Netty thread and handled on
this one, and that the client's frame loop is a separate clock. Part II's
[codecs](../foundations/codecs-nbt-json.md) and
[registries](../foundations/identifiers-and-registries.md) are assumed
wherever something is written to disk or sent on the wire, and [the resource
system](../foundations/resource-system.md) is assumed once, by *starting a
server*, which runs its staged load for server data.
Two pages from a later part are assumed, and they are cut two different
ways. [Tickets and loading](../world/tickets-and-loading.md) owns what
*entity-ticking* and *block-ticking* range mean, and [the level
tick](server-level-tick.md) defines both in one sentence before it uses
them, so that one keeps until Part IV. [Environment attributes and
timelines](../world/environment-attributes-and-timelines.md) does not: it
owns the per-position system whose cache `ServerLevel.tick` throws away
before it touches anything else — before the border, before the weather —
and out of which `Level.updateSkyBrightness` later reads the sky light rather
than deriving it from the time of day. That is the one page worth watching
out of order before this part; everything else in Part IV can wait.
## Watch in this order
1. [The server tick](server-tick.md) — one 50 ms lap: the deadline that
moves before the work starts, every packet since last time handled at
once, every dimension advanced, and the two writes per client the tick
leaves behind. *"Can't keep up!"* is not a warning that the server is
about to skip ticks — it is the skip.
2. [The level tick](server-level-tick.md) — one step of that lap, which is
the whole world changing: weather, scheduled ticks, mob spawns, random
ticks, every entity, every block entity. The block changes go out
*before* the entities move, so a change a piston makes reaches you a tick
later than one a player's command makes — with falling sand the exception
that sends its own packet, and a console command the one that is as late
as the piston.
3. [Players and sessions](players-and-sessions.md) — a join from the end of
the login handshake to a player standing in a world with chunks on the
way, and then the four ways that session changes: death, a dimension,
a disconnect, and a debug command that sends the player back to the
configuration phase. Dying replaces your player object; the Nether does not.
4. [Starting a server](starting-a-server.md) — *java -jar server.jar* to
the word *Done*: the EULA, the lock on `session.lock`, the packs and
registries, the thread, the levels. The step that loads the world's
chunks loads none of them on an ordinary world.
5. [How a server dies](how-a-server-dies.md) — three endings compared:
`/stop`, a crash in the tick loop, and the watchdog. A crash saves your
world. The watchdog does not.
## Reference this part uses
[Threads](../../reference/threads.md) — the Server thread, the worker pool
and the dedicated server's five side threads, with who makes each.
[Game rules](../../reference/gamerules.md) — the rules the tick consults,
which is most of them. [Packets](../../reference/packets.md) — everything
the tick sends. [Diagram lanes](../../reference/lanes.md).
---
*Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes `tools/verify_names.py`.*
---
# The server tick
> Verified against **Minecraft 26.2** · Part III · One 50 ms tick on the Server thread, from the moment the clock says *now* to the moment the thread parks again.
Twenty times a second the Server thread wakes, hands every packet that
arrived since it last looked to the code that answers it, advances every
dimension by one step, pushes what each player needs to know onto the wire,
and then spends whatever is left of its 50 ms on work other threads posted
back before parking until the next beat. Everything a player calls *the
world* — blocks, mobs, weather, the contents of a chest — is computed in
plenty of places and **committed** in this one, on this one thread. When a
lap runs long the console eventually prints *Can't keep up! Is the server
overloaded?*, and that line is not a warning that the server is about to
start missing ticks. `MinecraftServer.runServer` logs it and advances the
deadline past the whole backlog inside the same *if*, so the message **is**
the skip; because the log and the skip share one condition, a server that
complained recently keeps running behind rather than skipping at all. And the
ticks it does skip are simply gone — nothing runs them later, so game time
ends up that many ticks younger than the wall clock and stays there.
## The cast
| class | what it decides | thread |
|---|---|---|
| `MinecraftServer` | the loop, the thread and the event loop in one object: the deadline, the budget, and the order everything happens in | Server |
| `ServerTickRateManager` | how long this tick is, whether game elements advance at all, and whether the loop is sprinting | Server |
| `TickTask` | one queued runnable plus the `MinecraftServer.tickCount` it was submitted on — the age the budget reads | any thread submits, Server runs |
| `PacketProcessor` | which serverbound packets are waiting, and that all of them are handled at the top of a tick | Netty fills it, Server empties it |
| `ServerLevel` | one dimension's step — [a lecture of its own](server-level-tick.md) | Server |
| `ServerConnectionListener` | which connections are still alive, and when each one's player takes its own tick | Server |
| `ServerCommonPacketListenerImpl` | whether a packet handed to a player is written, or written *and* flushed | Server, mostly |
| `ServerChunkCache.MainThreadExecutor` | what the leftover milliseconds buy, one queue per level | Server |
## One lap, ending on the wire
```mermaid
sequenceDiagram
participant PP as PacketProcessor
participant MS as MinecraftServer
participant SL as ServerLevel
participant Conn as Connection
participant PCS as PlayerChunkSender
participant Wire as the network
Note over MS: runServer moves nextTickTimeNanos forward, past the backlog if it warns
MS->>PP: processQueuedPackets
PP->>MS: every serverbound packet Netty queued since the last drain, handled now
Note over MS,Conn: tickChildren begins, suspendFlushing on every player connection
MS->>SL: tick(haveTime), each dimension in turn, overworld first
SL->>Conn: send, written to the channel and not flushed
MS->>Conn: ServerConnectionListener.tick walks the list, Connection.tick each
Conn->>Conn: flushQueue, then ServerGamePacketListenerImpl.tick, the player's own tick
Conn->>Wire: flush one, the levels and the player tick
MS->>PCS: sendNextChunks, per player
PCS->>Conn: the chunk batch, written and not flushed
MS->>Conn: resumeFlushing
Conn->>Wire: flush two, the chunks and everything sent after the connection phase
Note over MS: waitUntilNextTick runs all tasks, then managedBlock parks until the deadline
Note over PP,Wire: the next tick begins
```
### The deadline moves before the work starts
`MinecraftServer.runServer` re-reads this tick's length every iteration from
`TickRateManager.nanosecondsPerTick` and adds it to
`MinecraftServer.nextTickTimeNanos` *before* calling anything. Being behind
means the wall clock has already passed that field. The overload branch fires
when it has passed by more than `MinecraftServer.OVERLOADED_THRESHOLD_NANOS`
(one second) plus `MinecraftServer.OVERLOADED_TICKS_THRESHOLD` ticks' worth —
two seconds at the default rate — **and** the last warning is at least
`MinecraftServer.OVERLOADED_WARNING_INTERVAL_NANOS` (ten seconds) plus
`MinecraftServer.OVERLOADED_TICKS_WARNING_INTERVAL` ticks' worth behind it,
fifteen seconds at the default rate. Both effects — the log line, and
`MinecraftServer.nextTickTimeNanos` jumping over the missed ticks — are
statements of that one branch. Lateness therefore has to *accumulate* before
either happens: a server running every lap ten percent long says nothing while
the shortfall piles up, then warns and drops the pile in one step. And because
`MinecraftServer.lastOverloadWarningNanos` is set to the new deadline, the
second gate is fifteen seconds of the server's *own* scheduled time — which on
an overloaded server is rather more than fifteen seconds of yours. In between,
the backlog is real and every tick of it is run.
Sprinting takes the other arm of the same *if*: when the server is not paused,
`ServerTickRateManager.isSprinting` is true and
`ServerTickRateManager.checkShouldSprintThisTick` consents, this tick is
declared **zero nanoseconds long** and `MinecraftServer.nextTickTimeNanos` is
set to *now*. `TickRateManager.nanosecondsPerTick` still reads 50 ms
throughout — a sprint changes the length of *this tick*, gives the overload
check nothing to measure, and sends the loop straight back for another. Sprinting also unfreezes the game —
`ServerTickRateManager.requestGameToSprint` remembers the old state in
`ServerTickRateManager.previousIsFrozen` — and restores it when
`ServerTickRateManager.finishTickSprint` reports the measured rate.
### Every packet since last time, in one drain
`MinecraftServer.processPacketsAndTick` opens the Tracy frame, then calls
`PacketProcessor.processQueuedPackets` — before `MinecraftServer.tickServer`,
so the frame a profiler shows includes the packets. Each entry in that
`ConcurrentLinkedQueue` is a `PacketProcessor.ListenerAndPacket`: a Netty
thread decoded the packet, the handler called
`PacketUtils.ensureRunningOnSameThread`, and that queued the pair here and
aborted the Netty-side call by throwing `RunningOnDifferentThreadException`
([anatomy](../anatomy/anatomy.md) has the crossing). This is where most
player input enters the world, but not all of it: the handlers that never
call `PacketUtils.ensureRunningOnSameThread` hop by the other door instead.
`ServerGamePacketListenerImpl.handleChat` and both command packets run their
work through `MinecraftServer.execute`, and filtered sign and book text comes
back on a `CompletableFuture` completed against the server — so chat and
commands arrive as *tasks*, drained by the event loop below, and not with the
packets.
Two gates sit on each queued pair. `PacketListener.shouldHandleMessage` is
asked again at handling time, so a player who disconnected between arrival and
now is dropped with a debug line rather than handled into a dead session. And
a handler that throws does not end the tick: `ServerPacketListener` overrides
`PacketListener.onPacketError` to log *"suppressing error"* and return, and
`ServerCommonPacketListenerImpl.onPacketError` additionally files the
throwable through `MinecraftServer.reportPacketHandlingException` into the
`SuppressedExceptionCollector` that the next crash report dumps. The one
escape is a `ReportedException` wrapping an *OutOfMemoryError*, which
`PacketUtils.makeReportedException` rethrows.
### An empty server stops ticking
Before anything else `MinecraftServer.tickServer` compares
`MinecraftServer.emptyTicks` against `MinecraftServer.pauseWhenEmptySeconds`
times twenty — the *pause-when-empty-seconds* property, default 60 in
`DedicatedServerProperties`, and zero on the base class, which disables the
feature. The counter advances only while nobody is online *and* the loop is
not sprinting; on the tick it first reaches the threshold the server logs,
autosaves once, and from then on runs `MinecraftServer.tickConnection` alone
and returns. `MinecraftServer.tickCount` does not advance, so a paused server
is stopped in every sense that matters and still answers pings.
The integrated server pauses on a different signal.
`IntegratedServer.tickServer` sets `IntegratedServer.paused` from
`Minecraft.isPaused` or an empty player list, saves once on the way in, runs
`IntegratedServer.tickPaused` — connections plus one statistic — instead of
the real tick, and re-syncs the world time on the way out. It also copies the
client's render and simulation distance into the `PlayerList` on every
unpaused tick, which is why singleplayer has no separate view-distance
setting.
### What `MinecraftServer.tickChildren` runs, and in what order
`MinecraftServer.tickChildren` is the tick, and the rows below are its
order. All but the first are a profiler section of their own; suspending the
flush has none, and the debug row is three:
| in order | what it does | skipped when |
|---|---|---|
| suspend flushing | `ServerCommonPacketListenerImpl.suspendFlushing` on every player's connection | never |
| command functions | `ServerFunctionManager.tick` runs `ServerFunctionManager.LOAD_FUNCTION_TAG` once after a reload, then `ServerFunctionManager.TICK_FUNCTION_TAG` | frozen |
| clocks | `ServerClockManager.tick` advances the world clocks, a `SavedData` kept in the *world_clocks* file | frozen, or `GameRules.ADVANCE_TIME` is off |
| time sync | `MinecraftServer.forceGameTimeSynchronization` broadcasts a `ClientboundSetTimePacket` | not a multiple of 20 ticks |
| levels | `MinecraftServer.updateEffectiveRespawnData`, then `ServerLevel.tick` for each dimension in `MinecraftServer.getAllLevels` order, overworld first | never |
| connection | `MinecraftServer.tickConnection` — every `Connection`, and each playing client's own tick | never |
| players | `PlayerList.tick` broadcasts a latency-only `ClientboundPlayerInfoUpdatePacket` | its own counter has not passed 600 — so every 601st call, not every 600th tick |
| debug, game tests, tickables | `ServerDebugSubscribers.tick`, `GameTestTicker.tick`, the dedicated server GUI's refresh through `MinecraftServer.addTickable` | game tests alone, when frozen |
| send chunks | `PlayerChunkSender.sendNextChunks`, then `ServerCommonPacketListenerImpl.resumeFlushing`, per player | never |
A throwable out of `ServerLevel.tick` is caught, filled with the level's
details as *"Exception ticking world"* and rethrown as a `ReportedException`
— which is how one bad dimension ends the whole server. Nothing else in the
list is wrapped. Two things a reader looks for here are elsewhere: the
`/schedule` queue is a `TimerQueue` the server owns and persists
(`MinecraftServer.getScheduledEvents`) but ticks from inside
`ServerLevel.tickTime`, with the dimension's own game time, and the last
statement of `MinecraftServer.tickChildren` is `ServerActivityMonitor.tick`,
a rate-limited nudge to the `NotificationManager` rather than anything the
world can see.
### Where a player's own tick actually happens
The connection phase runs *after* every level.
`ServerConnectionListener.tick` walks its synchronized list and, for each live
`Connection`, calls `Connection.tick`: flush the deferred send queue, tick the
`TickablePacketListener`, drop the connection if it has died, flush the
channel, and every twentieth tick recompute the packet-rate averages. For a
playing client that listener is `ServerGamePacketListenerImpl`, whose
`ServerGamePacketListenerImpl.tick` acknowledges pending block changes, runs
`ServerPlayer.doTick` through `ServerGamePacketListenerImpl.tickPlayer`, and
then, only if that returns without having kicked anyone, three more things
in this order: the fifteen-second keep-alive
(`ServerCommonPacketListenerImpl.LATENCY_CHECK_INTERVAL`), the three spam
throttles, and the idle-timeout check.
So a movement packet is applied to the player before any level ticks, and the
player *entity* takes its step after all of them. Entities see the player
where the packets put her; the player then ticks against a world that has
already moved. A throw out of `Connection.tick` disconnects that client with
*"Internal server error"* — except on an in-memory connection, where it is
rethrown as *"Ticking memory connection"* and takes the integrated server down
with it. On a dedicated server `DedicatedServer.tickConnection` adds
`DedicatedServer.handleConsoleInputs`, which is how a command typed at the
console reaches the Server thread. RCON does not come this way:
`DedicatedServer.runCommand` puts the command on the task queue with
`BlockableEventLoop.executeBlocking` and waits for the answer, so it runs
wherever the queue next drains. [Players and sessions](players-and-sessions.md) is
what happens inside that phase; [the
connection](../networking/the-connection.md) is the channel underneath it.
### The two writes each client gets
`ServerCommonPacketListenerImpl.suspendFlushing` at the top of
`MinecraftServer.tickChildren` sets a flag that turns
`ServerCommonPacketListenerImpl.send` into a channel *write* with no flush —
but only for sends made on the Server thread, because the flag is tested
together with `BlockableEventLoop.isSameThread`, so anything sent from another
thread flushes on its own as before. Two things then empty the buffer.
`Connection.tick` flushes the channel unconditionally at the end of the
connection phase, carrying everything the levels and the player's own tick
produced. `ServerCommonPacketListenerImpl.resumeFlushing`, after the chunk
batch, both clears the flag and calls `Connection.flushChannel` itself,
carrying the player-list update, the debug subscribers, the game-test ticker,
the server's own tickables and last the chunks.
**Two** — writes to the socket per client per tick: one after the levels, one
after the chunks.
The pacing of that second write is [tickets and
loading](../world/tickets-and-loading.md)'s subject. `PlayerChunkSender`
answers to the client's own acknowledgements, so a slow client throttles its
own chunks without slowing the tick.
### The bookkeeping at the bottom
`MinecraftServer.tickServer` closes with three ledgers. The cached
`ServerStatus` is rebuilt when the old one is more than
`MinecraftServer.STATUS_EXPIRE_TIME_NANOS` (five seconds) old, so a ping never
costs a walk of the player list. `MinecraftServer.ticksUntilAutosave` counts
down to `MinecraftServer.autoSave`. And the tick's own duration replaces its
slot in the hundred-entry `MinecraftServer.tickTimesNanos` ring, updates
`MinecraftServer.aggregatedTickTimesNanos`, and folds into
`MinecraftServer.smoothedTickTimeMillis` at
`MinecraftServer.AVERAGE_TICK_TIME_SMOOTHING`. That ring is what `/tick query`
reads, through `MinecraftServer.getAverageTickTimeNanos` and
`MinecraftServer.getTickTimesNanos`.
The debug screen's TPS chart is a different pipe: a `SampleLogger` with one
slot per `TpsDebugDimensions` value, written from three points of the loop.
`MinecraftServer.logTickMethodTime` records the tick method,
`MinecraftServer.finishMeasuringTaskExecutionTime` the scheduled-task and idle
slots after the wait, and `MinecraftServer.logFullTickTime` at the very bottom
both flushes the four-slot sample and measures the **whole iteration**, tick
plus wait. `IntegratedServer` logs it always; a dedicated server only while a
client is subscribed to `DebugSubscriptions.DEDICATED_SERVER_TICK_TIME`.
The loop is instrumented in three more places, none of them that ring.
Every iteration gets a fresh `ProfilerFiller` from
`MinecraftServer.createProfiler`, composing the `MetricsRecorder`'s profiler
with a `SingleTickProfiler`, and `/debug start` arms
`MinecraftServer.TimeProfiler` at the *top* of an iteration through
`MinecraftServer.debugCommandProfilerDelayStart`. `MinecraftServer.tickFrame`
is a `DiscontinuousFrame` from `TracyClient`, opened before the packet drain
and closed after the tick. `JvmProfiler` is fed
`MinecraftServer.smoothedTickTimeMillis` after the wait, on the last line of
the iteration — where `MinecraftServer.isReady` is also set, every lap rather
than once.
## The event loop, and what a tick's spare time buys
`MinecraftServer` extends `ReentrantBlockableEventLoop` of `TickTask`: it is
an `Executor` whose queue drains on the Server thread, and every other thread
that needs to touch server state submits to it and waits. This section is
where the rest of the book sends you for that machinery.
```mermaid
flowchart TD
P["BlockableEventLoop.pollTask peeks the head of the queue"] --> E{"anything queued"}
E -- "a task" --> B{"blocking depth above zero"}
B -- "inside managedBlock" --> RUN["run it, and report true"]
B -- "not blocked" --> S{"MinecraftServer.shouldRun"}
S -- "queued more than MAX_TICK_LATENCY ticks ago" --> RUN
S -- "otherwise, ask the budget" --> H{"MinecraftServer.haveTime"}
H -- "a task is already running" --> RUN
H -- "in the slack, now is before delayedTasksMaxNextTickTimeNanos" --> RUN
H -- "inside the tick, now is before nextTickTimeNanos" --> RUN
H -- "out of time" --> L["leave it queued"]
E -- "nothing" --> C{"only now does pollTaskInternal offer every level's chunk source a turn, when sprinting or blocked or in time"}
L --> C
C -- "one of them had work" --> RUN
C -- "none did" --> W["report false. Inside managedBlock, waitForTasks parks, and a schedule unparks it early"]
```
### Every runnable becomes a `TickTask`
`MinecraftServer.wrapRunnable` stamps the current `MinecraftServer.tickCount`
onto whatever is handed to the server, from whichever thread. That stamp is
the whole of a task's identity to the scheduler: `MinecraftServer.shouldRun`
lets a task run when there is time left, *or* when it is older than
`MinecraftServer.MAX_TICK_LATENCY` (three) ticks — so a saturated server still
drains its queue, late but in order and without unbounded growth. Submitting
from the Server thread does not mean running inline:
`ReentrantBlockableEventLoop.scheduleExecutables` reports true while another
task is running, so re-entrant work queues instead of nesting. Once the server
has stopped, `MinecraftServer.scheduleExecutables` reports false and a
submitted task runs *inline on the caller's thread*; it is the separate
`MinecraftServer.executeIfPossible` door that refuses with a
*RejectedExecutionException*, and `PacketProcessor.scheduleIfPossible` that
refuses a late packet the same way.
A task that throws is not the loop's problem either.
`BlockableEventLoop.doRunTask` logs the failure under the fatal marker and
returns, rethrowing only what `BlockableEventLoop.isNonRecoverable` calls
unrecoverable — an *OutOfMemoryError* or a `StackOverflowError`, unwrapped
through any `ReportedException` around it.
A worker thread that dies surfaces here too. `Util`'s uncaught-exception
handler and `GenerationChunkHolder.applyStep` both park the report through
`BlockableEventLoop.relayDelayCrash`, and the next
`BlockableEventLoop.pollTask` on a loop built to propagate crashes rethrows
it. The dedicated server is such a loop and the integrated server is not: in
singleplayer it is `Minecraft`'s loop that propagates, and
`IntegratedServer.onServerCrash` relays the server's own crash into the same
slot so the client picks it up. What happens next belongs to [how a server
dies](how-a-server-dies.md) — the crash report, the shutdown the loop's
*finally* performs, and the watchdog that reads
`MinecraftServer.getNextTickTime` from outside the loop and halts the JVM
without saving.
### The budget, and where it stops applying
`MinecraftServer.haveTime` is the `BooleanSupplier` the whole tick is handed.
It is true unconditionally while a task is running
(`ReentrantBlockableEventLoop.runningTask`), and otherwise compares the clock
against `MinecraftServer.delayedTasksMaxNextTickTimeNanos` or
`MinecraftServer.nextTickTimeNanos` depending on
`MinecraftServer.mayHaveDelayedTasks` — which `MinecraftServer.runServer` sets
true, together with the slack deadline, immediately after the tick, and which
every subsequent `MinecraftServer.pollTask` overwrites with *was there more*.
It stops applying the moment the thread blocks. Inside
`BlockableEventLoop.managedBlock` the blocking depth is non-zero, so
`BlockableEventLoop.shouldRunAllTasks` is true and
`BlockableEventLoop.pollTask` never consults `MinecraftServer.shouldRun`:
every queued task runs, budget and age irrelevant. That is what lets a level
block on a chunk mid-tick without deadlocking — the wait *is* the drain, and
the thread doing the waiting is the thread that completes the thing it waits
for. `MinecraftServer.waitUntilNextTick` is the same mechanism used
deliberately: `BlockableEventLoop.runAllTasks`, then
`BlockableEventLoop.managedBlock` on *no time left*.
`MinecraftServer.waitForTasks` parks with `LockSupport` until
`MinecraftServer.nextTickTimeNanos` — or 100 µs at a time when the loop is not
waiting on a tick — and `BlockableEventLoop.schedule` unparks it, so a
submitted task cuts the park short. `PacketProcessor.scheduleIfPossible`
pointedly does not: a packet landing in the slack waits for the next drain.
### What the budget actually gates
**Three** — the things `MinecraftServer.haveTime` decides, once it has
travelled from `MinecraftServer.tickServer` through
`MinecraftServer.tickChildren`, `ServerLevel.tick`, `ServerChunkCache.tick`
and `ChunkMap.tick`.
They are `ChunkMap.processUnloads` (the unload queue, which drains anyway
while it holds more than two thousand entries), `ChunkMap.saveChunksEagerly`
(at most twenty chunks a tick, and only under 128 outstanding writes) and
`SectionStorage.tick` by way of `PoiManager.tick` (the dirty village-point
sections being written out). Loading a chunk, generating one, propagating
tickets and ticking chunks take no supplier and are not gated at all. A late
server does not load fewer chunks, then; it postpones unloading and saving
them, and its memory grows while it is behind.
### The slack, and the sprint that inverts it
After the tick, `MinecraftServer.waitUntilNextTick` spends the remaining
milliseconds. `MinecraftServer.pollTaskInternal` polls the server's own queue
first and, only if that queue had nothing to run, offers every level's
`ServerChunkCache.MainThreadExecutor.pollTask` a turn — when the loop is
sprinting, or blocked, or still in time. The levels get the leftovers of the
leftovers.
That executor keeps a policy of its own: its `ServerChunkCache.MainThreadExecutor.shouldRun` is unconditionally
true, with no age rule, and its poll runs
`ServerChunkCache.runDistanceManagerUpdates` first and returns at once if that
did any work. The first thing leftover milliseconds buy is chunks changing
status; the light schedule and the one queued chunk task only happen on a poll
where the graphs were already quiet.
Sprinting inverts the arithmetic. `MinecraftServer.processPacketsAndTick`
hands `MinecraftServer.tickServer` a constant *false* instead of
`MinecraftServer.haveTime`, so unloading, eager saving and section flushing
stop for the length of the sprint — and yet `ServerTickRateManager.isSprinting`
is the *first* term of the condition guarding the chunk-source poll, so every
level's queue is drained on every poll regardless. A sprint therefore does
more chunk work per wall-clock second than an ordinary server, not less,
while doing almost none of the housekeeping that would let the results reach
the disk — the exception being the unload queue, which the two-thousand rider
drains whether there is time or not.
## Questions players ask
**Does freezing stop the server?** It stops the *world*. `/tick freeze` sets
`TickRateManager.isFrozen`, and `TickRateManager.tick` turns that into this
tick's `TickRateManager.runGameElements` — unless `/tick step` left
`TickRateManager.frozenTicksToRun` above zero, which it also decrements. The
loop still runs, `MinecraftServer.tickCount` still increments, connections
still tick, and `TickRateManager.isEntityFrozen` exempts players and anything
carrying one. Everything that consults `TickRateManager.runsNormally` —
functions, clocks, weather, block and fluid ticks, other entities, game
tests — stops.
**Why does lowering the tick rate not delay my autosave?**
`MinecraftServer.ticksUntilAutosave` starts at
`MinecraftServer.AUTOSAVE_INTERVAL` (6000) and is thereafter
`MinecraftServer.computeNextAutosaveInterval`: the tick rate times 300,
floored at `MinecraftServer.MIMINUM_AUTOSAVE_TICKS` (100 — the typo is
Mojang's). An autosave is five wall-clock minutes.
`MinecraftServer.onTickRateChanged` re-derives it whenever `/tick rate`
changes, but only ever *shortens* the pending countdown. While sprinting it
uses the measured rate from `MinecraftServer.getAverageTickTimeNanos`, so a
sprint saves at the speed it is really running.
**Is the tick rate settable to anything?** Between
`TickRateManager.MIN_TICKRATE` (1.0) and `TickCommand.MAX_TICKRATE` (10000).
Clients are told: `ServerTickRateManager.setTickRate` and
`ServerTickRateManager.setFrozen` broadcast a `ClientboundTickingStatePacket`,
`ServerTickRateManager.stepGameIfPaused` a `ClientboundTickingStepPacket`, and
`ServerTickRateManager.updateJoiningPlayer` sends both to anyone arriving. A
sprint is not announced as such — a client sees the unfreeze before it and the
refreeze after.
**Why is an empty Nether nearly free?** Because a dimension that nothing holds
a simulation ticket in stops ticking entities and block entities after 300
ticks of `ServerLevel.emptyTime`, while still running its chunk-source work.
That is [the level tick](server-level-tick.md)'s rule, and [tickets and
loading](../world/tickets-and-loading.md) owns which ticket resets it.
The loop itself is written once. `IntegratedServer` and
`DedicatedServer` override pieces of the tick, never the loop:
`DedicatedServer.tickServer` adds the JSON-RPC `ManagementServer.tick`,
`DedicatedServer.tickConnection` the console, `IntegratedServer.tickServer`
the pause. [Starting a server](starting-a-server.md) is how the thread that runs
`MinecraftServer.runServer` comes to exist.
## Where to look
`MinecraftServer.runServer` · `MinecraftServer.processPacketsAndTick` ·
`MinecraftServer.tickServer` · `MinecraftServer.tickChildren` ·
`MinecraftServer.waitUntilNextTick` · `MinecraftServer.haveTime` ·
`MinecraftServer.shouldRun` · `MinecraftServer.pollTask` · `TickTask` ·
`ReentrantBlockableEventLoop` · `BlockableEventLoop` · `PacketProcessor` ·
`PacketUtils` · `TickRateManager` · `ServerTickRateManager` · `TickCommand` ·
`ServerConnectionListener.tick` · `Connection.tick` ·
`ServerCommonPacketListenerImpl.send` · `ServerChunkCache.MainThreadExecutor` ·
`ChunkMap.processUnloads` · `IntegratedServer` · `DedicatedServer` ·
`ServerClockManager` · `SampleLogger` · `TpsDebugDimensions`
---
*Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes `tools/verify_names.py`.*
---
# The level tick
> Verified against **Minecraft 26.2** · Part III · A player stands still for one twentieth of a second while a whole dimension advances — weather, scheduled ticks, spawns, every entity, every block entity.
Nobody moves. A wheat crop is one random tick short of ripe, a creeper is
walking a fence line, rain has been falling for four minutes and a piston
somewhere is halfway through extending. Then `ServerLevel.tick` runs, once,
for this dimension, and all of it advances: the border interpolates, the
weather counts down, the scheduled block ticks fire, mobs are counted and
spawned, chunks near players get their random ticks, every entity in range
runs its `Entity.tick`, block entities run their tickers, and the block
changes that all of that produced are handed to the clients. It is one
method on the Server thread, called once per dimension from
`MinecraftServer.tickChildren` ([the server tick](server-tick.md)), overworld
first. The order inside it is the whole lecture, because one of the steps is
in a place nobody expects: **the block-change broadcast runs before the
entities tick**. `ServerChunkCache.broadcastChangedChunks` comes before
`ChunkMap.tick` and long before `EntityTickList.forEach`, so a block a
command changed reaches your screen this tick and a block a piston changed
reaches it on the next one.
## Three ranges, before we need them
Three phrases run through everything below and they are all one number line.
A chunk is *loaded* when it has a `ChunkHolder` at all; it is
**block-ticking** at level 32 or below (`ChunkLevel.BLOCK_TICKING_LEVEL`) and
**entity-ticking** at 31 or below (`ChunkLevel.ENTITY_TICKING_LEVEL`), and
those last two answers come from the simulation graph, through
`DistanceManager.inBlockTickingRange` and
`DistanceManager.inEntityTickingRange`. How a chunk gets its level — which
tickets put it there and which graph they feed — is Part IV's
[tickets and loading](../world/tickets-and-loading.md); for this page it is
enough that block-ticking reaches one chunk further out than entity-ticking,
and that both are decided fresh, inside this tick, before anything ticks.
## The cast
| class | what it decides | thread |
|---|---|---|
| `ServerLevel` | the order of the tick, and every gate in it — one instance per dimension | Server |
| `ServerChunkCache` | the chunk half of the tick: ticket purging, distance updates, spawning, random ticks, the broadcast | Server |
| `ChunkMap` | which chunks are candidates for spawning, which are entity-ticking, and every player's view of every entity | Server |
| `ChunkHolder` | one chunk's pending block and light changes, and which packet shape they become | Server |
| `LevelTicks` | the two scheduled-tick queues, one `LevelChunkTicks` per chunk, drained by priority | Server |
| `EntityTickList` | which entities are ticked, and a stable view of that set while it is being walked | Server |
| `PersistentEntitySectionManager` | which entities exist and which of them are ticking — the tick list's only editor | Server; its inbox is filled by IO threads |
| `TickRateManager` | whether this is a normal tick at all, through `TickRateManager.runsNormally` | Server |
## The whole tick, and its three gates
The tick is one method calling its own private methods, so its shape is not
a conversation — it is a column with guards down the side. There are three
guards, and every step is behind one of them, a combination of them, or
nothing: *running* (`TickRateManager.runsNormally`, false while
`/tick freeze` holds and no step is pending), *not a debug world*
(`Level.isDebug`), and *the dimension is not empty* (`ServerLevel.emptyTime`
below `ServerLevel.EMPTY_TIME_NO_TICK`, 300).
```mermaid
flowchart TD
START["MinecraftServer.tickChildren calls ServerLevel.tick, overworld first"]
ENV["handlingTick goes true, EnvironmentAttributeSystem.invalidateTickCache — no gate"]
WB["WorldBorder.tick, then advanceWeatherCycle and its game-event packets — running"]
SLEEP["the sleep check: move the clock, wake the players, reset the weather — no gate"]
SKY["Level.updateSkyBrightness, read out of the environment attributes — no gate"]
TIME["ServerLevel.tickTime: gameTime and the schedule queue, overworld only — running"]
SCHED["ServerLevel.blockTicks then ServerLevel.fluidTicks, 65536 apiece — running, and not a debug world"]
RAID["Raids.tick — running"]
SCC["ServerChunkCache.tick, handed the server's time budget — no gate"]
PURGE["TicketStorage.purgeStaleTickets — running"]
DIST["ServerChunkCache.runDistanceManagerUpdates: which chunks tick is settled here — no gate"]
CHUNKS["mob counts, spawning chunks, thunder, spawns, random ticks, custom spawners — running, and not a debug world"]
CAST["ServerChunkCache.broadcastChangedChunks: block, block-entity and light packets — not a debug world"]
TRACK["ChunkMap.tick: chunk tracking, and the movement of everything that moved last tick — no gate"]
UNLOAD["ChunkMap.tick with haveTime: POI saving and chunk unloads, until the budget is spent — no gate"]
EVENTS["ServerLevel.runBlockEvents, then handlingTick goes false — running"]
EMPTY["hasActiveTickets resets emptyTime, otherwise it rises — and it rises only while running"]
DRAGON["EnderDragonFight.tick, the End only — running, and the dimension is not empty"]
ENT["EntityTickList.forEach: each entity, then its riders — the dimension is not empty"]
BE["Level.tickBlockEntities — the dimension is not empty, and each ticker fires only while running"]
EM["PersistentEntitySectionManager.tick: the loading inbox, then the unload set — no gate"]
DBG["LevelDebugSynchronizers.tick — no gate"]
START --> ENV --> WB --> SLEEP --> SKY --> TIME --> SCHED --> RAID --> SCC
SCC --> PURGE --> DIST --> CHUNKS --> CAST --> TRACK --> UNLOAD
UNLOAD --> EVENTS --> EMPTY --> DRAGON --> ENT --> BE --> EM --> DBG
```
Read the gates and most of the page's surprises fall out of the figure.
Sleeping through the night works with the game frozen. A frozen world still
loads, sends and unloads chunks — and stops expiring its tickets. A debug
world keeps its entities and drops its block updates. And the last two steps
run on a dimension with nobody in it.
## The cache that is dropped before the border
The first thing the tick does to the world, before the border and before
the weather, is `EnvironmentAttributeSystem.invalidateTickCache`: last tick's
resolved environment attributes are thrown away. That system
([environment attributes](../world/environment-attributes-and-timelines.md))
is where the old per-dimension and per-biome constants went, and
`Level.updateSkyBrightness` later in this same tick reads
`EnvironmentAttributes.SKY_LIGHT_LEVEL` out of it rather than deriving sky
light from the time of day. `ServerClockManager` invalidates the same cache
on every level whenever a clock moves, so the level is not its only owner —
it is the first reader of the tick, and it starts clean.
Then `WorldBorder.tick` advances the interpolated extent, and
`ServerLevel.advanceWeatherCycle` counts the clear, rain and thunder timers
down under `GameRules.ADVANCE_WEATHER`, resampling each from
`ServerLevel.RAIN_DELAY`, `ServerLevel.RAIN_DURATION`,
`ServerLevel.THUNDER_DELAY` and `ServerLevel.THUNDER_DURATION` as it
expires, and fading `Level.rainLevel` and `Level.thunderLevel` by 0.01 a
tick, which is why a downpour arrives as a five-second ramp. The countdowns
belong to the *server*: `ServerLevel.getWeatherData` delegates to
`MinecraftServer.getWeatherData`, one `WeatherData` shared by every
dimension, and the only per-level parts are those two floats and the
`Level.canHaveWeather` test that decides whether this dimension acts on any
of it. Every move of a float is a `ClientboundGameEventPacket`
(`ClientboundGameEventPacket.RAIN_LEVEL_CHANGE`,
`ClientboundGameEventPacket.THUNDER_LEVEL_CHANGE`) to this dimension's
players, and a start or a stop goes to every player in every dimension.
## Sleeping is the one thing a freeze cannot stop
`SleepStatus.areEnoughSleeping` and `SleepStatus.areEnoughDeepSleeping`
(against `GameRules.PLAYERS_SLEEPING_PERCENTAGE`) decide the night skip, and
they are checked outside every gate. If the dimension type has a default
clock and `GameRules.ADVANCE_TIME` is on,
`ServerClockManager.moveToTimeMarker` jumps that clock to
`ClockTimeMarkers.WAKE_UP_FROM_SLEEP`; `ServerLevel.wakeUpAllPlayers` gets
everyone out of bed, and `ServerLevel.resetWeatherCycle` clears the storm.
The `ClientboundSetTimePacket` that follows is sent by the clock manager,
not by the level — day time is not the level's state in 26.2 at all.
What the level does own is *gameTime*, and only in the overworld:
`ServerLevel.tickTime` advances it when the level was built with its
`ServerLevel.tickTime` flag set, which `MinecraftServer` does for the
overworld alone, and every other dimension reads that number. The same call
advances the server-wide `TimerQueue` behind `/schedule`
(`MinecraftServer.getScheduledEvents`), so a scheduled function is timed off
overworld *gameTime* and stands still while the world is frozen.
## Scheduled ticks, twice, and a promise to the same block
`LevelTicks.tick` is called twice — `ServerLevel.blockTicks`, then
`ServerLevel.fluidTicks` — each with the current *gameTime* and a budget of
`ServerLevel.MAX_SCHEDULED_TICKS_PER_TICK`, 65536, and both skipped entirely
in a debug world. Each call collects every `LevelChunkTicks` container whose
head is due and whose chunk passes
`ServerLevel.isPositionTickingWithEntitiesLoaded`, drains them in
`ScheduledTick.INTRA_TICK_DRAIN_ORDER` — priority, then submission order,
with no time term, because a container is only collected once its head is
already due, and `ScheduledTick.DRAIN_ORDER`, which does compare times,
orders each chunk's own queue — and hands each drained tick to
`ServerLevel.tickBlock` or `ServerLevel.tickFluid`.
Both of those check that what is at the position is *still* what the tick was
scheduled for: `ServerLevel.tickBlock` the `Block`, before
`BlockBehaviour.BlockStateBase.tick`; `ServerLevel.tickFluid` the `Fluid`,
before `FluidState.tick`.
That check is the whole cancellation mechanism: replace a block and its
pending ticks evaporate, with nothing anywhere removing them. The queue
itself, and what schedules into it, is
[scheduled ticks](../world/scheduled-ticks.md).
## The chunk source does five things in one call
`ServerChunkCache.tick` receives the server's `MinecraftServer.haveTime`
supplier — the level never looks at it, it only passes it on — and does five
things in order.
It purges stale tickets, but only while running, so a frozen world holds on
to expired portal and pearl tickets indefinitely. It runs
`ServerChunkCache.runDistanceManagerUpdates`, ungated, which is where chunks
change ticking state: the reason an entity starts or stops ticking this tick
is decided here, several steps before the entity loop reads it. Then, if
this is not a debug world, it does the spawning and random-ticking work
below and broadcasts the block changes; it updates entity tracking; and
finally it spends whatever time is left on POI saving and chunk unloads.
That last step is the only part of the whole level tick that yields to the
clock, and even it does not yield completely: `ChunkMap.processUnloads`
force-drains anything over two thousand queued unloads whatever the budget
says.
### Two chunk sets, and two different mob caps
`NaturalSpawner.createState` walks `ServerLevel.getAllEntities` — every
entity in the dimension, skipping mobs that require persistence — and counts
them per `MobCategory`, using the chunk each one stands in to charge a
spawn-potential field and to feed a `LocalMobCapCalculator`. That count is
then read two different ways. Globally,
`NaturalSpawner.SpawnState.canSpawnForCategoryGlobal` allows a category
while its count is under `MobCategory.getMaxInstancesPerChunk` ×
`DistanceManager.getNaturalSpawnChunkCount` ÷ 289
(`NaturalSpawner.MAGIC_NUMBER`, 17²) — the mob cap everyone argues about.
Locally, `LocalMobCapCalculator.canSpawn` applies the raw, unscaled
`MobCategory.getMaxInstancesPerChunk` per player close enough to the chunk,
so a category can sit well under the server-wide cap and still refuse to
spawn beside one crowded player. Persistent categories — the animals — are
considered only on a tick where *gameTime* divides by 400, and the whole
spawning half is behind `GameRules.SPAWN_MOBS`.
Two chunk sets are then walked, and they are not the same set.
`ChunkMap.collectSpawningChunks` gathers the **spawning chunks**: the
radius-8 tracker's candidates that have a ticking chunk and at least one
non-spectating player within 128 blocks
(`ChunkMap.playerIsCloseEnoughForSpawning`). They are shuffled, and each
then gets `ChunkAccess.incrementInhabitedTime`, then `ServerLevel.tickThunder`
*if it is also entity-ticking* — a 1-in-100000 roll per chunk per tick while
raining and thundering, whose bolt prefers a lightning rod, then a mob that
can see the sky, then the heightmap, and which brings a trap skeleton horse
along at effective difficulty × 1 % — and then
`NaturalSpawner.spawnForChunk` if `ServerLevel.canSpawnEntitiesInChunk`.
The second set is `ChunkMap.forEachBlockTickingChunk`, which despite its
name walks `DistanceManager.forEachEntityTickingChunk` — the entity-ticking
set, level 31 and below. Each of those chunks gets `ServerLevel.tickChunk`.
### Random ticks are counted per section, and empty sections are free
`ServerLevel.tickChunk` does two things with one number,
`GameRules.RANDOM_TICK_SPEED` (default 3). It rolls that many 1-in-48
chances at `ServerLevel.tickPrecipitation` — the ice and snow layer, using
`Biome.shouldFreeze`, `Biome.shouldSnow` and
`GameRules.MAX_SNOW_ACCUMULATION_HEIGHT` — and then, for every
`LevelChunkSection` in the chunk that reports
`LevelChunkSection.isRandomlyTicking`, picks that many random positions and
rolls both `BlockBehaviour.BlockStateBase.randomTick` and the fluid's at
each. `LevelChunkSection.isRandomlyTicking` is a counter maintained on every
block change, so a section of solid stone costs nothing at all: the loop
over sections is a loop over the chunk's *interesting* height. Set the rule
to zero and both halves stop, ice and snow included.
The position itself does not come from `Level.random`.
`Level.getBlockRandomPos` advances `Level.randValue`, a plain linear
congruential generator, and unpacks x, y and z out of one integer.
Everything the block then *does* with that position — the crop-growth roll,
fire spreading, every implementation of
`BlockBehaviour.BlockStateBase.randomTick` — takes `Level.random`, which is
also what the ice and snow rolls, the lightning roll and the spawning-chunk
shuffle use.
The custom spawners come last inside this step.
`ServerLevel.tickCustomSpawners` runs the overworld's `PhantomSpawner`,
`PatrolSpawner`, `CatSpawner`, `VillageSiege` and `WanderingTraderSpawner` —
a list only the overworld is constructed with. Three of the five carry a
game rule of their own (`GameRules.SPAWN_PHANTOMS`,
`GameRules.SPAWN_PATROLS`, `GameRules.SPAWN_WANDERING_TRADERS`); cats and
sieges answer only to `GameRules.SPAWN_MOBS`, which gates the whole call.
### The broadcast, which is why entities are a tick behind
Almost nothing in the game sends a block update at the moment a block
changes — a landing `FallingBlockEntity` is the exception worth knowing, and
it is dealt with at the end of this section.
`ServerLevel.sendBlockUpdated` calls `ServerChunkCache.blockChanged`, which
finds the `ChunkHolder`, records the position in that holder's per-section
set, and — the first time a holder gains a changed section — adds it to
`ServerChunkCache.chunkHoldersToBroadcast`. Then it returns. The packets are
built once, later, in the chunk source's step.
```mermaid
sequenceDiagram
participant SL as ServerLevel
participant SCC as ServerChunkCache
participant CH as ChunkHolder
participant CM as ChunkMap
participant Wire as the network
Note over SL,CM: before the tick — a command changed a hundred blocks
SL->>SCC: sendBlockUpdated, once per block
SCC->>CH: blockChanged, one entry in that section's set
CH-->>SCC: nothing leaves, the holder is only marked
Note over SL,Wire: this tick, inside ServerChunkCache.tick
SCC->>CH: broadcastChangedChunks walks every marked holder
CH->>Wire: ClientboundLightUpdatePacket first, to the border players only
CH->>Wire: ClientboundBlockUpdatePacket for a section with one change
CH->>Wire: ClientboundSectionBlocksUpdatePacket for a section with several
CH->>Wire: BlockEntity.getUpdatePacket beside any changed position that has one
SCC->>CM: ChunkMap.tick, the movement of everything that moved last tick
Note over SL,Wire: still this tick, several steps later
SL->>SL: tickBlockEntities, PistonMovingBlockEntity finishes a push and changes a block
SL->>SCC: sendBlockUpdated, marked, and it waits for the next tick
```
`ChunkHolder.broadcastChanges` asks two different questions of two
different audiences, and it asks the light one first: if either light filter
has anything in it, `ChunkHolder.PlayerProvider.getPlayers` is called with
the border-only flag set and one `ClientboundLightUpdatePacket` goes to the
players on the edge of their tracked area, before a single block packet is
built. The blocks are then emitted **per 16³ section**, to everyone tracking
the chunk, and the shape depends on how many blocks in that section changed: one
`ClientboundBlockUpdatePacket` for a single change, one
`ClientboundSectionBlocksUpdatePacket` for several, plus
`BlockEntity.getUpdatePacket` for any changed position that carries a block
entity. A hundred blocks changed by one command are therefore a handful of
packets, one per affected section — not a hundred.
Then `ChunkMap.tick` runs — chunk tracking for each player, and
`ChunkMap.TrackedEntity` for each entity, which is where
`ServerEntity.sendChanges` turns last tick's movement into packets. Blocks
first, entities second, and the entity loop only after both. The ordering is
visible from a client: a player's `/setblock` lands in the tick the command
was typed in, because a command packet is handled before
`MinecraftServer.tickChildren` even starts, while a piston head lands in the
tick after the one that moved it.
Falling sand is the exception that proves the rule, and it is deliberate.
`FallingBlockEntity` calls `Level.setBlock` and then, on the very next line,
`ChunkMap.sendToTrackingPlayers` with a `ClientboundBlockUpdatePacket` of its
own — so the block the client sees appear is sent in the same tick the entity
that placed it vanished, rather than a tick behind it. A handful of other
places do the same thing to one player rather than to everyone tracking the
chunk. What the client does with all of this is
[what the client is told](../networking/what-the-client-is-told.md).
## Block events close the handlingTick window
`ServerLevel.runBlockEvents` drains `ServerLevel.blockEvents` — the
note-block plays, piston pushes and chest-lid counts raised anywhere in this
tick — completely, calling `BlockBehaviour.BlockStateBase.triggerEvent`
after re-checking that the block at the position is still the one the event
was raised for, the same promise a scheduled tick makes. When the block
returns true, a `ClientboundBlockEventPacket` goes to players within 64
blocks. Because the set is a linked hash set, two identical events in one
tick collapse into one; and an event whose chunk is not block-ticking is
parked on `ServerLevel.blockEventsToReschedule` and re-queued rather than
dropped.
`ServerLevel.handlingTick` went true at the very top of the tick and goes
false here, so the whole entity half runs outside that window. Its one
reader in the game is `PistonBaseBlock`, which uses it to tell a piston
update raised inside the tick from one raised outside it.
## An empty dimension skips exactly three things
`ServerChunkCache.hasActiveTickets` — really
`TicketStorage.shouldKeepDimensionActive`, which is the players' *simulation*
tickets and a handful of others — resets `ServerLevel.emptyTime`. Otherwise
the counter rises, and it rises only while running, so a frozen dimension
never falls asleep. Past `ServerLevel.EMPTY_TIME_NO_TICK`, 300 ticks, the
level skips the dragon fight, the entity loop and the block entities. That
is the entire skip: the weather, the scheduled ticks, the chunk source, the
block events, the entity manager's load and unload drain and the debug feed
all keep running on a dimension nobody has visited for fifteen seconds.
## Every entity, and then its riders
`EntityTickList.forEach` walks the tick list. An entity is skipped if it has
been removed, or if `TickRateManager.isEntityFrozen` says so — frozen, and
neither a player nor something carrying one. Otherwise it gets
`Entity.checkDespawn`, and then ticks only if it is a `ServerPlayer` or its
chunk answers `DistanceManager.inEntityTickingRange`. A passenger whose
vehicle is alive and still lists it is passed over here and ticked by the
vehicle instead; a stale link is broken with `Entity.stopRiding`.
`ServerLevel.tickNonPassenger` records the old position, bumps
`Entity.tickCount` and calls `Entity.tick`, then `ServerLevel.tickPassenger`
runs `Entity.rideTick` for each rider that is a `Player` or in the tick
list, recursively down the stack. `Level.guardEntityTick` wraps each one in
a crash report titled *Ticking entity*, so a mob that throws names itself.
The list stays stable under all of that by construction. Membership changes
during the loop — a spawner's mob, a fired arrow, a lightning bolt — arrive
through `ServerLevel.EntityCallbacks.onTickingStart` immediately.
`EntityTickList` allows exactly one `EntityTickList.forEach` at a time and,
on any add or remove while it is iterating, copies into its
`EntityTickList.passive` map and swaps that with `EntityTickList.active`:
the running loop keeps walking the view it started with, and the new entity
waits for the next tick. Which entities are in the list at all is
`PersistentEntitySectionManager`'s answer, through
`Visibility.fromFullChunkStatus` — only `FullChunkStatus.ENTITY_TICKING`
maps to a ticking visibility — with one override, `Player.isAlwaysTicking`,
which keeps a player ticking whatever its chunk is doing and makes the
`ServerPlayer` test inside the loop a second, redundant guard. Entities are
Part VI's subject: [entity lifecycle](../entities/entity-lifecycle.md).
## Block entities reach one chunk further than mobs
`Level.tickBlockEntities` walks `Level.blockEntityTickers`, dropping removed
tickers as it goes and running the rest — but only those whose position
passes `ServerLevel.shouldTickBlocksAt`, which is the **block**-ticking
range, and only while `TickRateManager.runsNormally`. That single condition
is why a furnace keeps smelting one chunk further out than a zombie keeps
walking. A block entity created mid-tick — a chest a piston just pushed —
lands on `Level.pendingBlockEntityTickers` instead, because
`Level.tickingBlockEntities` is true, and is merged in at the top of the
next tick. [Block entities](../blocks/block-entities.md) has the rest.
## The two steps that always run
`PersistentEntitySectionManager.tick` drains
`PersistentEntitySectionManager.loadingInbox`, a concurrent queue that chunk
storage fills from IO threads — entities from freshly loaded chunks join the
world here — and then processes
`PersistentEntitySectionManager.chunksToUnload`. The
`ServerLevel.EntityCallbacks` it fires
(`ServerLevel.EntityCallbacks.onTickingStart`,
`ServerLevel.EntityCallbacks.onTickingEnd`) are exactly what add and remove
entries in the tick list the previous step walked.
Last, `LevelDebugSynchronizers.tick` pushes this tick's neighbour updates,
brains, paths, POIs and raids to any client subscribed through
`DebugSubscriptions` — and, just before it, arms or clears the neighbour
listener on `CollectingNeighborUpdater` depending on whether anyone is
subscribed to `DebugSubscriptions.NEIGHBOR_UPDATES` at all. It is the one
step of the level tick whose entire output is diagnostic, and it is outside
every gate.
## What leaves the level, and when
| the packet | the step that sends it | to whom |
|---|---|---|
| `ClientboundGameEventPacket`, rain and thunder levels | `ServerLevel.advanceWeatherCycle` | this dimension's players |
| `ClientboundGameEventPacket`, start and stop raining | the same step, on a transition | every player in every dimension |
| `ClientboundSetTimePacket` | `ServerClockManager.moveToTimeMarker`, from the sleep skip | everyone, sent by the clock manager |
| `ClientboundBlockUpdatePacket` · `ClientboundSectionBlocksUpdatePacket` | `ChunkHolder.broadcastChanges`, per 16³ section | everyone tracking the chunk |
| `BlockEntity.getUpdatePacket` | the same walk, beside a changed position that has one | everyone tracking the chunk |
| `ClientboundLightUpdatePacket` | the same walk, before the blocks | the players on their tracked area's border |
| entity add, move and remove | `ChunkMap.TrackedEntity`, inside `ChunkMap.tick` | whoever tracks that entity |
| `ClientboundBlockEventPacket` | `ServerLevel.runBlockEvents` | players within 64 blocks |
None of them go out when they are written. Every one is queued behind the
suspended flush that `MinecraftServer.tickChildren` opens around all the
levels, and leaves on the wire at the end of the server tick
([the server tick](server-tick.md)).
## Questions players ask
**Does `/tick freeze` stop the server doing work?** Barely, on the chunk
side. Distance updates, the block-change broadcast, entity tracking and
chunk unloads all run every tick regardless. Two things inside the chunk
source *are* frozen and are easy to miss: the spawning and random-ticking
step, and `TicketStorage.purgeStaleTickets` — so a frozen world accumulates
expired tickets and never releases the chunks they hold.
**Why does my furnace keep going after the mobs around it stop?** Two
thresholds, one chunk apart. Block entities are gated on block-ticking range
(`ChunkLevel.BLOCK_TICKING_LEVEL`, 32) and entities on entity-ticking range
(`ChunkLevel.ENTITY_TICKING_LEVEL`, 31), and a chunk on the boundary is in
one and not the other.
**Why did replacing a block cancel its scheduled tick?** Because a scheduled
tick is a promise to *that* block: `ServerLevel.tickBlock` compares the
`Block` at the position with the one scheduled, and a mismatch runs nothing.
**Why does `/weather rain` in the Nether change the overworld?** There is one
`WeatherData` on the `MinecraftServer` and every dimension advances the same
countdowns. Only the fade of `Level.rainLevel` and `Level.thunderLevel` is
per level, and only where `Level.canHaveWeather`.
**Why does a mob that spawns this tick not move until the next one?**
`EntityTickList` swapped its maps the moment the mob was added, so the loop
that is running kept the view it started with. The mob is in the list — it
is just not in *this* walk of it.
**Why is a dimension with nobody in it still burning CPU?** Because going
empty skips three things and nothing else: past 300 ticks with no active
ticket the dragon fight, the entity loop and the block entities stop, and
the weather, scheduled ticks, chunk source, block events and entity manager
carry on.
**Where did the day–night cycle go?** Out of the level. Time is a set of
`WorldClock`s owned by `ServerClockManager` and ticked by the server;
`ServerLevel.tickTime` advances *gameTime* only, only in the overworld, and
every other dimension reads the overworld's number.
## Where to look
`ServerLevel.tick` · `ServerLevel.advanceWeatherCycle` · `ServerLevel.tickTime` ·
`ServerLevel.tickBlock` · `ServerLevel.tickChunk` · `ServerLevel.tickThunder` ·
`ServerLevel.tickCustomSpawners` · `ServerLevel.runBlockEvents` ·
`ServerLevel.tickNonPassenger` · `ServerLevel.tickPassenger` ·
`ServerChunkCache.tick` · `ServerChunkCache.tickChunks` ·
`ServerChunkCache.broadcastChangedChunks` · `ServerChunkCache.blockChanged` ·
`ChunkHolder.broadcastChanges` · `ChunkMap.collectSpawningChunks` ·
`ChunkMap.forEachBlockTickingChunk` · `ChunkMap.tick` · `ChunkMap.processUnloads` ·
`DistanceManager.inEntityTickingRange` · `ChunkLevel.fullStatus` ·
`LevelTicks.tick` · `NaturalSpawner.createState` · `LocalMobCapCalculator.canSpawn` ·
`EntityTickList.forEach` · `PersistentEntitySectionManager.tick` ·
`Level.tickBlockEntities` · `Level.getBlockRandomPos` · `WeatherData` ·
`ServerClockManager.moveToTimeMarker` · `SleepStatus.areEnoughSleeping` ·
`EnvironmentAttributeSystem.invalidateTickCache` · `LevelDebugSynchronizers.tick`
---
*Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes `tools/verify_names.py`.*
---
# Players and sessions
> Verified against **Minecraft 26.2** · Part III · One connection, four events: a player joins, dies, walks into a Nether portal, and logs out.
A player clicks a server in the list, watches a progress bar, and is
standing in a world. An hour later they fall in lava, press *Respawn*, walk
through a portal and log off for the night. On the server those are four
events on one object graph, and they disagree about what survives. Dying
**destroys** the `ServerPlayer` and builds a new one; a trip to the Nether
keeps the same object and moves it. Both keep the player's entity id, and
both keep the very same `ServerGamePacketListenerImpl` — a respawn does not
rebuild the connection, it repoints it, in one field assignment, at the new
player. Which is why anyone still holding the old `ServerPlayer` after a
respawn is holding a corpse: an object flagged removed, out of every list,
that will keep answering questions all day.
## The cast
| class | what it decides | thread |
|---|---|---|
| `PlayerList` | who is admitted, who is in the tab list, and the exact order of the join burst | Server |
| `ServerPlayer` | one player's world state, and what a new one inherits from an old one | Server |
| `ServerGamePacketListenerImpl` | the session: the player it points at, the flush suspension, the load gate, the kicks | Netty to decode, Server to handle |
| `ServerConfigurationPacketListenerImpl` | the strictly serial task queue a join is prepared in | Netty, with four handlers hopping to Server |
| `PrepareSpawnTask` | where the player will stand, and when the `ServerPlayer` is finally constructed | Server |
| `PlayerDataStorage` | the `.dat` file, its *.dat_old* twin and its corrupt-copy rescue | Server |
| `PlayerChunkSender` | how many chunks a client is trusted with, starting at almost none | Server |
| `CommonListenerCookie` | the four facts that survive a phase change: profile, latency, client options, transferred | — |
The phases this trace passes through — login, configuration, play, and the
terminal packets between them — belong to
[protocol phases](../networking/protocol-phases.md). This page starts where
that one hands over: with `PlayerList` deciding whether the login is allowed
at all.
## Admission is a `Component` or nothing
Identity here is a `NameAndId`: a record of a UUID and a name, made from the
authenticated `GameProfile`, and the key that every stored-user list, every
op lookup and every save file is addressed by. `UserNameToIdResolver`,
reached through `MinecraftServer.services`, is the cache that maps between
the two halves, and comparing its remembered name against the profile's is
how the server notices a player has been renamed since their last visit.
`PlayerList.canPlayerLogin` returns the reason to refuse, or null. It asks
four questions in order — the ban list, the whitelist, the IP ban list, then
capacity — and the two ways past it are not the ones a reader expects. The
whitelist is bypassed by being an **op**: `PlayerList.isWhiteListed` is
satisfied by presence in the op list, and `DedicatedPlayerList.isWhiteListed`
overrides it to route through `PlayerList.isOp`, which also asks whether
the identity is the singleplayer owner — a branch that never fires here,
because `DedicatedServer.isSingleplayerOwner` returns false for everyone.
The *bypassesPlayerLimit* flag in *ops.json* is a
separate thing entirely and reaches exactly one of the four questions:
`ServerOpListEntry.bypassesPlayerLimit` is read only by
`DedicatedPlayerList.canBypassPlayerLimit`, and only the capacity test calls
it. A banned op is still banned.
The gate runs **twice**, and the second run is not a repeat of the first.
`ServerLoginPacketListenerImpl.verifyLoginAndFinishConnectionSetup` runs it
during login; `ServerConfigurationPacketListenerImpl.handleConfigurationFinished`
runs it again when the client says configuration is over, because a ban or a
newly full server can land in the seconds a configuration takes. Duplicate
logins differ between the two. At login the newcomer wins:
`PlayerList.disconnectAllPlayersWithProfile` kicks every session holding
that UUID with `PlayerList.DUPLICATE_LOGIN_DISCONNECT_MESSAGE`, and the
login parks until the old connection is really gone. At the second check the
newcomer loses — an existing player with that id is a flat rejection, since
by then there is a prepared spawn to throw away rather than a session to
evict.
## Preparing a place to stand
```mermaid
sequenceDiagram
participant SLPL as ServerLoginPacketListenerImpl
participant PL as PlayerList
participant SCPL as ServerConfigurationPacketListenerImpl
participant PST as PrepareSpawnTask
participant PDS as PlayerDataStorage
participant SL as ServerLevel
SLPL->>PL: canPlayerLogin, then disconnectAllPlayersWithProfile
SLPL->>SCPL: handleLoginAcknowledgement builds the listener, startConfiguration
Note over SCPL: one task at a time, startNextTask refuses to overlap two
SCPL->>SCPL: SynchronizeRegistriesTask, then a code of conduct or a resource pack
SCPL->>PST: returnToWorld appends PrepareSpawnTask, then JoinWorldTask
PST->>PDS: load, decoding SavedPosition out of the whole datafixed file
PST->>SL: a PLAYER_SPAWN ticket at radius 3, then wait
Note over SCPL,SL: every tick until the chunks land, the client still in configuration
SL-->>PST: the load future completes, Preparing becomes Ready
SCPL->>SCPL: JoinWorldTask sends ClientboundFinishConfigurationPacket
SCPL->>PL: handleConfigurationFinished re-checks duplicates and canPlayerLogin
SCPL->>PST: spawnPlayer
```
Nothing in that queue overlaps.
`ServerConfigurationPacketListenerImpl.startNextTask` throws rather than
start a task while another is unfinished, so the registry transfer completes
before `PrepareSpawnTask` reads a byte, and the two optional tasks sit
between them. What *does* overlap is the chunk load and the client's
remaining work: once the ticket is placed the task simply reports *not
finished* from `ConfigurationTask.tick` each tick, and the client spends
that time in configuration with no idea a world is being assembled for it.
The ticket needs re-arming. `TicketType.PLAYER_SPAWN` is registered with a
timeout of twenty ticks, so `PrepareSpawnTask.keepAlive` — called from
`ServerConfigurationPacketListenerImpl.tick` — re-adds it at the same radius
every tick once the task has reached `PrepareSpawnTask.Ready`. Without that,
a client slow to acknowledge the finish packet would arrive to find its
spawn chunks expired underneath it.
A player with no save file gets a search instead of a position.
`PlayerSpawnFinder.findSpawn` walks up to
`PlayerSpawnFinder.ABSOLUTE_MAX_ATTEMPTS` candidates — a thousand and
twenty-four, or fewer if `GameRules.RESPAWN_RADIUS` or the world border says
so — in a coprime-strided order from a random offset, loading each
candidate's chunk under a one-tick `TicketType.SPAWN_SEARCH` ticket and
returning a future. The `ChunkLoadCounter` beside it feeds only the
server's `LevelLoadListener`, through
`LevelLoadListener.Stage.LOAD_PLAYER_CHUNKS` — the client's progress bar on
an integrated server, and nothing a remote player ever sees on a dedicated
one, where the listener is a `LoggingLevelLoadListener` that boot already
closed.
## The save file is read twice, and both reads are the whole file
`PrepareSpawnTask.start` reads the `.dat` and decodes
`ServerPlayer.SavedPosition` from it — three optional fields, dimension,
position and rotation, and nothing else. That is all it *decodes*. It is not
all it does: `PlayerDataStorage.load` reads the compressed tag with an
unlimited accounter and runs `DataFixTypes.PLAYER` over the entire document
before any codec sees it ([codecs, NBT and
JSON](../foundations/codecs-nbt-json.md)). So the narrow read saves decoding,
not I/O, and a join pays the migration cost twice — once here, and once in
`PrepareSpawnTask.Ready`, where the file is loaded again for the full
`Entity.load`.
Between the two reads the player is built. `ServerLevel.waitForEntities`
blocks the Server thread until the entities in the spawn chunks have
finished loading — a horse to remount has to exist before a rider can be
attached to it — and then the `ServerPlayer` constructor runs, pulling its
`ServerStatsCounter` and `PlayerAdvancements` out of `PlayerList` on the way
past and defaulting `ServerPlayer.requestedViewDistance` to 2 until the
client's `ClientInformation` says otherwise. The second read fills the
object in, the player is snapped to the prepared position,
`PlayerList.placeNewPlayer` runs, and only afterwards do
`ServerPlayer.loadAndSpawnEnderPearls` and
`ServerPlayer.loadAndSpawnParentVehicle` put back what the player was
carrying and sitting on when they left.
One rescue is wired into the read. `PlayerList.loadPlayerData` checks
whether the joining identity is the singleplayer owner and, if the world
records a *singleplayer_uuid* in its level data, loads **that** file rather
than the one named for the joining id. It works once: the save writes under
the current id and `MinecraftServer.saveAllChunks` stamps the current
owner's id into the level data, so the old file is read on one join and
orphaned by the first save afterwards.
## `PlayerList.placeNewPlayer` sends a world in one write
```mermaid
sequenceDiagram
participant PL as PlayerList
participant SGPL as ServerGamePacketListenerImpl
participant SL as ServerLevel
participant CM as ChunkMap
participant Wire as the network
Note over PL,Wire: in the scheduled packet processing at the top of a tick
PL->>SGPL: new listener, inbound protocol to play, suspendFlushing
PL->>SGPL: ClientboundLoginPacket, difficulty, abilities, held slot, recipes
PL->>SGPL: the permission level as an entity event, then the command tree
PL->>SGPL: recipe book, scoreboard, join message, teleport, server status
PL->>SGPL: everyone already here, then the joiner to everyone
PL->>SGPL: sendLevelInfo, border, clocks, spawn, rain, LEVEL_CHUNKS_LOAD_START
PL->>SL: addNewPlayer
SL->>CM: onTrackingStart, updatePlayerStatus, the first ChunkTrackingView
CM->>SGPL: the chunks in view marked pending on PlayerChunkSender
PL->>SGPL: boss events, active effects, initInventoryMenu, resumeFlushing
SGPL->>Wire: one write
Note over SL,Wire: at the end of the same tick, the first chunk batch, and only one
```
The whole method sits inside one suspension.
`ServerCommonPacketListenerImpl.suspendFlushing` sets a flag that every send
from the Server thread consults, queuing packets unflushed, and
`ServerCommonPacketListenerImpl.resumeFlushing` clears it and calls
`Connection.flushChannel` once. Everything above — a login packet, a command
tree, a scoreboard, a tab list, a world border — leaves as a single write.
It is the same bracket [the server tick](server-tick.md) puts around every
client every tick, opened by hand here because a join is handled in the
scheduled packet processing that runs *before* `MinecraftServer.tickChildren`
opens the tick's own.
`ClientboundLoginPacket` is where the client learns the entity id it is
about to be given, the hardcore flag, every dimension key the server has,
the view and simulation distances, whether the server authenticates, and a
`CommonPlayerSpawnInfo` from `ServerPlayer.createCommonSpawnInfo` carrying
the dimension type, the obfuscated seed, both game modes, the last death
location, the portal cooldown and the sea level. Three game rules are pinned
into it as plain booleans and never re-sent by this path:
`GameRules.REDUCED_DEBUG_INFO`, `GameRules.LIMITED_CRAFTING`, and
`GameRules.IMMEDIATE_RESPAWN` inverted into *show the death screen*.
The permission level arrives twice over, as a `ClientboundEntityEventPacket`
carrying one of five event ids and as the whole command tree from
`Commands.sendCommands`, both from `PlayerList.sendPlayerPermissionLevel`
resolving a `LevelBasedPermissionSet` out of
`MinecraftServer.getProfilePermissions`. The model behind that set is
[Brigadier and commands](../commands/brigadier-and-commands.md); all a join
needs to know is that both halves are re-sent whenever `PlayerList.op` or
`PlayerList.deop` changes it.
The tab list goes out in a deliberate order: the joiner is sent everyone
already present, *then* added to `PlayerList.players`, *then* everyone —
themselves included — is sent the joiner. And the join message is chosen a
few lines earlier than it is sent, because
*multiplayer.player.joined.renamed* is used when the name in the profile
differs from the one the name cache remembers, and the cache is overwritten
at the top of the method.
Entering the level is the step that starts the terrain, though it is not
the last: the boss bars, the active effects, the inventory menu and the join
notification all follow it, and `ServerCommonPacketListenerImpl.resumeFlushing`
closes the single write.
`ServerLevel.addNewPlayer` hands the player to
`PersistentEntitySectionManager.addNewEntity`, whose callback adds it to
`ServerLevel.players` and to the chunk source; `ChunkMap.updatePlayerStatus`
registers the player with `DistanceManager`, resets its chunk tracking to
`ChunkTrackingView.EMPTY` and computes the real one, and every chunk inside
that view is marked pending on the connection's `PlayerChunkSender`. So
there are two player lists, written by different systems:
`PlayerList.players` is *who is on the server* and `PlayerList` alone writes
it, while `ServerLevel.players` is *whose entity is in this level* and only
the entity manager's tracking callbacks write it. A player halfway through a
dimension change is in the first and in neither copy of the second.
A joining client is trusted with one batch. `PlayerChunkSender` starts with a
budget of a single unacknowledged batch and a guess of nine chunks a tick;
the first `ServerboundChunkBatchReceivedPacket` raises the budget to ten
(`PlayerChunkSender.MAX_UNACKNOWLEDGED_BATCHES`) and replaces the guess with
the client's own measured rate, clamped between
`PlayerChunkSender.MIN_CHUNKS_PER_TICK` and
`PlayerChunkSender.MAX_CHUNKS_PER_TICK`. Batches go out nearest chunk first,
in the *send chunks* step at the very end of
`MinecraftServer.tickChildren`. [Tickets and
loading](../world/tickets-and-loading.md) picks up from there.
## Loaded is something the client says
The player is in the world before the client can play.
`ServerGamePacketListenerImpl.hasClientLoaded` gates movement, block
breaking, item use, sprinting and sneaking, and it answers false until
either the client sends `ServerboundPlayerLoadedPacket` or a sixty-tick
timer runs out — `ServerGamePacketListenerImpl.CLIENT_LOADED_TIMEOUT_TIME`,
started in the listener's constructor and counted down from
`ServerPlayer.tick`, which is to say from the level's entity loop rather
than from the connection.
Death re-arms the same gate by a different mechanism.
`ServerGamePacketListenerImpl.markClientUnloadedAfterDeath`, called at the
end of `ServerPlayer.die`, sets a flag that no timer clears: a dead player's
client counts as unloaded indefinitely, and only
`ServerGamePacketListenerImpl.restartClientLoadTimerAfterRespawn` — reached
from the respawn, or from a brand-new listener — clears the flag and starts
the sixty ticks again. The death screen is held open by the same field the
*Joining world* screen is.
That the countdown ticks from `ServerPlayer.tick` while the food, health and
stat sync tick from `ServerPlayer.doTick` is the whole reason a player is
ticked from two places every tick; [player
anatomy](../player/player-anatomy.md) is the lecture on that. The fact worth
carrying out of here is that the level's entity loop ticks players
*regardless* of entity-ticking range, which is how a player standing in an
otherwise idle chunk still moves.
## Four ways the session changes
The join is one story; what happens afterwards is a comparison. Three of
these a player will meet tonight and the fourth exists for one debug
command. They are not quite every way out of a `ServerLevel` — the end
credits are a fifth, and `ServerPlayer.showEndCredits` removes the player
with `Entity.RemovalReason.CHANGED_DIMENSION` on its way to the respawn
below — but they are the four a session is built out of, and they disagree
about almost everything.
| | respawn | dimension change | disconnect | `ServerGamePacketListenerImpl.switchToConfig` |
|---|---|---|---|---|
| **who runs it** | `ServerGamePacketListenerImpl.handleClientCommand` | `ServerPlayer.teleport` | `Connection.handleDisconnection` | `DebugConfigCommand` |
| **same `ServerPlayer`?** | no — `PlayerList.respawn` constructs a new one | yes | yes, briefly | yes, then a new one on the way back |
| **same entity id?** | yes, copied over by `Entity.setId` | yes | — | no, the rebuilt player gets a fresh one |
| **same connection listener?** | yes, `ServerPlayer.connection` is reassigned | yes | — | no, a new one from `PlayerList.placeNewPlayer` |
| **what carries over** | whatever `ServerPlayer.restoreFrom` copies | everything, because nothing is copied | — | whatever the `.dat` holds |
| **is the `.dat` written?** | no | no | yes, inside `PlayerList.remove` | yes, the same call |
| **the client is told** | `ClientboundRespawnPacket`, keeping nothing, or attribute modifiers | `ClientboundRespawnPacket`, `ClientboundRespawnPacket.KEEP_ALL_DATA` | nothing, the channel is already gone | `ClientboundStartConfigurationPacket` |
| **removal reason** | `Entity.RemovalReason.KILLED` | `Entity.RemovalReason.CHANGED_DIMENSION` | `Entity.RemovalReason.UNLOADED_WITH_PLAYER` | the same |
### The object, and the reference that outlives it
`PlayerList.respawn` builds the new `ServerPlayer` from the old one's
profile and client information, then performs three assignments that make
the difference invisible from outside: `ServerPlayer.connection` is set to
the old player's listener, `Entity.setId` copies the entity id, and the
listener's own player field is reassigned by its caller. To every other
client on the server nothing has happened — same id, same UUID, same tab
list row, and no `ClientboundPlayerInfoUpdatePacket` is sent at all. To
anything inside the server holding the old object, everything has happened:
it was removed from its level before the new one existed, and it is not in
`PlayerList.players` any more.
Only two of the four paths take a `TeleportTransition`, and they are the two
that move you. `ServerPlayer.findRespawnPositionAndUseSpawnBlock` turns the
saved `ServerPlayer.RespawnConfig` — respawn data plus a *forced* flag —
into one: a respawn anchor is found and a charge spent unless the point is
forced, a bed is found and costs nothing,
`TeleportTransition.missingRespawnBlock` says the block is gone, and
`TeleportTransition.createDefault` falls back to the world spawn. Both of
those last two are the expensive branch, not just the fallback: each builds
its position with `TeleportTransition.findAdjustedSharedSpawnPos`, so each
runs `ServerPlayer.adjustSpawnLocation` — the same `PlayerSpawnFinder` search
a join runs asynchronously — and blocks the Server thread on the future with
`BlockableEventLoop.managedBlock`.
### What comes across when you die
`ServerPlayer.restoreFrom` has two branches, and the interesting one is not
the one people name it for. Its *restore everything* branch — permanent
attribute modifiers, health, hunger, every active effect, the inventory, the
portal state — is reached only when `PlayerList.respawn` is called with
`Entity.RemovalReason.CHANGED_DIMENSION`, which happens in exactly one place:
a player pressing *Respawn* on the end credits, with `ServerPlayer.wonGame`
set. That is the End-portal return, not *keepInventory*.
An ordinary death takes the other branch. Health is reset to maximum,
effects are gone, and `GameRules.KEEP_INVENTORY` — or having died as a
spectator — decides only whether `ServerPlayer.transferInventoryXpAndScore`
runs, moving the inventory, the experience and the score and nothing else.
Outside the branch, and so true of every death, a long tail is copied
unconditionally: the ender chest, the enchantment seed, both game modes,
base attribute values, the recipe book, the warden spawn tracker, the chat
session, the skin customisation, the last death location. The ender chest
surviving death is not a game rule; it is a field assignment.
### Why the Nether keeps your potion effects
Because nothing is copied. `ServerPlayer.teleport` across a dimension
boundary sets `ServerPlayer.isChangingDimension`, sends the client a
`ClientboundRespawnPacket` marked `ClientboundRespawnPacket.KEEP_ALL_DATA`,
removes the entity from the old level with
`Entity.RemovalReason.CHANGED_DIMENSION` — a reason that neither destroys
the entity nor writes it into a chunk — immediately calls
`Entity.unsetRemoved`, points it at the new level with
`ServerPlayer.setServerLevel`, and adds it back with
`ServerLevel.addDuringTeleport`. It is one object the whole way. The effects
survive because they were never touched, and what the server then re-sends
— level info, the inventory through `PlayerList.sendAllPlayerInfo`, the
active effects, the abilities, the permission level — is not a restoration
but a **re-sync**, because the client threw its own world away the moment
the respawn packet arrived.
A same-dimension teleport is not this path at all. It is
`ServerGamePacketListenerImpl.teleport` followed by
`ServerGamePacketListenerImpl.resetPosition`, with no client-side world
discarded and no respawn packet sent.
### Where your llama goes when you log out
The channel closes on a Netty thread and nothing happens until
`ServerConnectionListener.tick`, in the connection step of the server tick,
notices and calls `Connection.handleDisconnection`. That reaches
`ServerGamePacketListenerImpl.removePlayerFromWorld`: the leave message,
`ServerPlayer.disconnect` to eject passengers and stop sleeping, and
`PlayerList.remove`.
The order inside `PlayerList.remove` is the answer to the question. The
`.dat` is written **first**, with the stats and advancements JSON beside it,
and `ServerPlayer.saveParentVehicle` writes the entire root vehicle into it
under *RootVehicle* — but only if that vehicle has exactly one player
passenger. The same test then decides whether the vehicle chain is removed
from the world with `Entity.RemovalReason.UNLOADED_WITH_PLAYER`, the removal
reason that neither destroys an entity nor saves it into a chunk, which is
exactly what you want for something already written into a different file.
In-flight ender pearls go the same way, saved into the file and removed from
the world. So the llama leaves the world with you, rides in your save, and
is put back by `ServerPlayer.loadAndSpawnParentVehicle` after your next
`PlayerList.placeNewPlayer` — unless somebody else was riding it too, in
which case it stays where it is and you land beside it. On an integrated
server one further thing happens:
`ServerCommonPacketListenerImpl.onDisconnect` sees that the leaver is the
singleplayer owner and halts the server ([how a server
dies](how-a-server-dies.md), which owns `PlayerList.saveAll` and
`PlayerList.removeAll` at shutdown).
`ServerGamePacketListenerImpl.switchToConfig` is that same removal with the
connection left alive. It runs `ServerGamePacketListenerImpl.removePlayerFromWorld` — leave message,
saved file, tab-list removal and all — sends
`ClientboundStartConfigurationPacket` and swaps the outbound protocol back;
the acknowledgement builds a fresh `ServerConfigurationPacketListenerImpl`,
and `ServerConfigurationPacketListenerImpl.returnToWorld` exists separately
from `ServerConfigurationPacketListenerImpl.startConfiguration` precisely so
the second visit re-queues the spawn and join tasks without re-sending
registries. It is also why `CommonListenerCookie` carries the transferred
flag and the client's options across at all.
### What everyone else is told
Very little, and only on the way out. A respawn and a dimension change send
nothing to other clients about the player, because the identity a tab list
is keyed on never changed. A disconnect sends one
`ClientboundPlayerInfoRemovePacket` to everybody, and `PlayerList.tick`
broadcasts a latency-only update for the whole list on every six hundred and
first call, counted by `PlayerList` rather than off the tick number.
The rest of what other players see is entity tracking in `ChunkMap`, and the
removal reasons in the table above have already told it what to do.
### The three kicks that come from the tick
`ServerGamePacketListenerImpl.tick` ends sessions of its own accord. An idle
player is disconnected after `MinecraftServer.playerIdleTimeout` minutes
with no action, unless they are sitting on the end credits. A player whose
client reports itself airborne for longer than
`ServerGamePacketListenerImpl.MAXIMUM_FLYING_TICKS` — eighty ticks, scaled
up when gravity is reduced and disabled entirely when gravity is zero — is
kicked for flying, and a floating vehicle is counted separately from a
floating rider. And keep-alive is a strict pair:
`ServerCommonPacketListenerImpl.keepConnectionAlive` sends one every
`ServerCommonPacketListenerImpl.LATENCY_CHECK_INTERVAL` milliseconds and
disconnects with `ServerCommonPacketListenerImpl.TIMEOUT_DISCONNECTION_MESSAGE`
if the previous one is still unanswered, while an answer carrying the wrong
id disconnects immediately rather than being ignored. The round trip it
measures is smoothed three parts old to one part new, so a tab list lags a
genuine latency change by several pings. The singleplayer owner is exempt
from the keep-alive, and from that alone: it is the only one of the three
that asks `ServerCommonPacketListenerImpl.isSingleplayerOwner`, and the host
can be kicked for idling or for flying like anyone else.
> **For a 1.21-era reader.** Identity is a `NameAndId` record, not a
> `GameProfile`, everywhere below the login handshake — the ban list, the op
> list, the whitelist, the save file and the name cache all key on it. And a
> permission is no longer an integer: `ServerOpListEntry` holds a
> `LevelBasedPermissionSet`, and the number in *ops.json* is a spelling of
> one.
## Where to look
`PlayerList.canPlayerLogin` · `PlayerList.placeNewPlayer` ·
`PlayerList.respawn` · `PlayerList.remove` · `PlayerList.loadPlayerData` ·
`PlayerList.sendLevelInfo` · `PrepareSpawnTask` · `JoinWorldTask` ·
`PlayerSpawnFinder` · `PlayerDataStorage` · `ServerPlayer.restoreFrom` ·
`ServerPlayer.teleport` ·
`ServerPlayer.findRespawnPositionAndUseSpawnBlock` · `ServerPlayer.die` ·
`ServerPlayer.doTick` ·
`ServerGamePacketListenerImpl.removePlayerFromWorld` ·
`ServerGamePacketListenerImpl.switchToConfig` ·
`ServerGamePacketListenerImpl.hasClientLoaded` ·
`ServerCommonPacketListenerImpl` · `PlayerChunkSender` ·
`ChunkMap.updatePlayerStatus` · `TeleportTransition`
---
*Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes `tools/verify_names.py`.*
---
# Starting a server
> Verified against **Minecraft 26.2** · Part III · Dropping the jar in an empty folder, typing *java -jar server.jar*, and waiting for the line that says *Done*.
The first run writes two files and exits: you have not agreed to the EULA.
The second gets as far as *Preparing level "world"* and, a second or two
later, *Done (1.284s)! For help, type "help"*. By the time the second of
those lines prints, the server has opened a data pack stack, built every
registry the world is made of, taken an operating-system lock on the save
directory, rewritten `level.dat`, constructed a `ServerLevel` for every
dimension the packs declare and started listening on 25565 — and only the
levels were built between the two lines. Everything else was over before the
first one printed, which is why the elapsed time it reports is so short. And
the step in the middle whose name promises the most —
`MinecraftServer.prepareLevels`, the one whose progress line still reads
*Preparing spawn area* — does the least: **on an ordinary world it loads no
chunks at all.** All it does is re-arm the tickets the last shutdown wrote
down, and exactly two of the nine ticket types are ever written down.
## The cast
| class | what it decides | thread |
|---|---|---|
| `server/Main` | everything decided before a second thread exists: the flags, the properties, the EULA, which directory is the world, and when to hand off | JVM main |
| `DedicatedServerSettings` | the live copy of `server.properties` — and the rewrite that happens every time a setting changes | main, then Server |
| `LevelStorageSource.LevelStorageAccess` | one open world: its directory layout, its `session.lock`, and every read and write of `level.dat` | whoever holds it |
| `WorldLoader` | the packs, the registries and the datapack-driven resources, assembled into a `WorldStem` | main and `Util.backgroundExecutor` |
| `DedicatedServer` | what a dedicated server has and singleplayer does not: the console, the port, the legacy conversions, RCON, query, the watchdog | Server, but constructed on main |
| `MinecraftServer` | the levels, the saves and the loop — and `MinecraftServer.spin`, the line where the second thread begins | Server |
| `ServerLevel` | one dimension: its chunk source, its saved data, its ticket store | Server |
| `LevelLoadListener` | what boot progress looks like: log lines on a dedicated server, a progress bar on a client | the caller's |
## From the command line to the Server thread
```mermaid
sequenceDiagram
participant Main as Main
participant LSA as LevelStorageSource.LevelStorageAccess
participant WL as WorldLoader
participant Worker as Worker
participant DS as DedicatedServer
participant MS as MinecraftServer
participant SL as ServerLevel
Main->>Main: tryDetectVersion, the flags, CrashReport.preload, Bootstrap.bootStrap and validate, the timer hack thread
Main->>Main: DedicatedServerSettings reads server.properties and writes it straight back, then Eula. Without agreement main returns here
Main->>Main: JsonRpc.create binds the ManagementServer on its own daemon Netty group, with no world open yet
Main->>LSA: validateAndCreateAccess. session.lock is taken in the constructor
LSA-->>Main: level.dat parsed, or level.dat_old restored into its place
Main->>WL: load, wrapped in Util.blockUntilDone, so this thread is now an executor
WL->>Main: createResourceManager opens the packs, handed to the main-thread executor
WL->>Worker: static tags, worldgen then dimension registries, ReloadableServerResources
Worker-->>WL: the stages that must be single-threaded come back to main
WL-->>Main: a WorldStem
Main->>LSA: saveDataTag rewrites level.dat now, upgrade or no upgrade
Main->>MS: spin builds the Thread object and sets priority 8 above four processors
MS->>DS: the factory it was handed runs the constructor here, on this thread, and can throw
MS->>MS: only once there is a server to run does spin start the thread
Note over Main,MS: main registers the shutdown hook and returns. Everything below is the Server thread
MS->>DS: runServer calls initServer
DS->>DS: the console daemon thread, the properties into fields, the key pair
DS->>DS: startTcpServerListener binds the port, then convertOldUsers
DS->>MS: loadLevel
MS->>SL: createLevels builds the overworld first, then one level per LevelStem on DerivedLevelData
MS->>SL: prepareLevels re-arms the persisted tickets and waits in 10 ms slices
SL-->>MS: nothing pending. On an ordinary world nothing was ever asked for
MS-->>DS: loadLevel returns
DS->>DS: Done is logged here, before the loop is ever entered
DS->>DS: query, RCON, the watchdog, JMX, one flush save, serverStarted
DS-->>MS: initServer returns true
MS->>MS: the icon and the status response are built, then the tick loop begins
```
Read the note bar as a wall: above it, one thread does all the work and
the server object does not exist for most of it; below it, *main* has
returned and everything that remains is the Server thread and the daemons
arranged around it. [Anatomy](../anatomy/anatomy.md) draws the same hand-off
from the client's side, where the thread that spins the server is the one
drawing frames.
## Everything *main* does before there is a second thread
`Main.main` runs on the thread the JVM handed it and stays there for all of
what follows. `SharedConstants.tryDetectVersion` reads *version.json* out of
the jar first, so everything downstream knows what version it is. Then the
flags are parsed — *--nogui*, *--port*, *--universe*, *--world*,
*--forceUpgrade*, *--recreateRegionFiles*, *--safeMode*, *--initSettings*,
*--pidFile* and the rest — and `CrashReport.preload` runs: `MemoryReserve`
sets a block of heap aside and one throwaway report is formatted and
discarded, so that the crash-report path is warm and has memory of its own
even when the reason for the crash is that there is none left.
`Bootstrap.bootStrap` and `Bootstrap.validate` build and freeze the static
registries ([identifiers and registries](../foundations/identifiers-and-registries.md)),
and `Util.startTimerHackThread` starts a daemon that sleeps forever and
touches nothing.
Only then does the server read its own configuration. `DedicatedServerSettings`
parses `server.properties`, and `DedicatedServerSettings.forceSave` writes it
straight back — which is why a properties file carried over from an older jar
comes back with the new keys filled in. `RegionFileVersion.configure` takes
the region compression out of it before any chunk file is ever opened
([chunk storage](../world/chunk-storage.md)). And
`Eula` reads `eula.txt`, or writes the default and reports it missing. That is
the gate: `Eula.hasAgreedToEULA` false means one log line and *main* returns.
No world has been opened, no server exists, and everything *main* has started
so far is a daemon — which does not hold the JVM open, so the process ends
with the return. *--initSettings* returns one step earlier still, having
written both files on purpose.
### The management port opens before the world does
With the EULA agreed, `Services.create` builds the authentication services and
the name cache in the universe directory, and `JsonRpc.create` starts a
`ManagementServer` if *management-server-enabled* is set — and throws, ending
the boot, if it is set and the secret is not forty alphanumeric characters
rather than quietly going without one: a Netty WebSocket listener with its own
event-loop group named *Management server IO*, TLS on by default, and a
`JsonRpcNotificationService` registered on the `NotificationManager` that
everything later in the boot reports through. It binds before `session.lock`
is taken, and `DedicatedServer.onServerExit` stops it last, so the management
protocol is reachable at both ends of the server's life — including while
there is no world to ask it about ([how a server dies](how-a-server-dies.md)).
### Taking the lock, and fixing `level.dat` twice
`LevelStorageSource.createDefault` points at the universe directory — the
working directory unless *--universe* says otherwise — and
`LevelStorageSource.validateAndCreateAccess` opens the world named by
*--world* or by *level-name*. The validation is a symlink check; the lock is
neither optional nor deferred. `DirectoryLock.create`, called from the
`LevelStorageSource.LevelStorageAccess` constructor, opens
`DirectoryLock.LOCK_FILE`, writes a snowman into it and takes a
`FileChannel.tryLock` on it. That is an operating-system advisory lock, not a
value in the file: a second server on the same directory fails at once with
`DirectoryLock.LockException`, a crashed JVM releases it with nothing to clean
up, and a world folder copied out from under a running server carries a
`session.lock` that means nothing. The client's world list greys out a world
that `DirectoryLock.isLocked` reports held.
If the directory already holds world data, *main* parses `level.dat` once and
runs the result through the datafixers twice.
`LevelStorageSource.LevelStorageAccess.getUnfixedDataTagWithFallback` reads
the file and, on a parse failure, falls back to `level.dat_old` and restores
it over the original. That raw tag goes through
`LevelStorageSource.LevelStorageAccess.fixAndGetSummaryFromTag` for a
`LevelSummary`, which is enough to answer
`LevelSummary.requiresManualConversion` and `LevelSummary.isCompatible` — two
gates that each end the boot with one explanatory line — and separately
through `DataFixers.getFileFixer` for the fully upgraded tag the world is
actually built from.
### The world load turns the main thread into an executor
`ServerPacksSource.createPackRepository` builds the repository over the
world's *datapacks/* folder and `WorldLoader.load` does the rest: the staged
load that [the resource system](../foundations/resource-system.md) describes,
run here for server data. `WorldLoader.PackConfig.createResourceManager`
selects and opens the packs, `TagLoader.loadTagsForExistingRegistries`
collects tags for the static registries, `RegistryDataLoader` loads
`RegistryDataLoader.WORLDGEN_REGISTRIES` and then
`RegistryDataLoader.DIMENSION_REGISTRIES`, the `WorldLoader.WorldDataSupplier`
turns the fixed `level.dat` into a `PrimaryLevelData` through
`LevelStorageSource.getLevelDataAndDimensions` — or, with no world data at
all, `Main.createNewWorldData` builds one out of `server.properties` — and
`ReloadableServerResources.loadResources` compiles the recipes, loot tables,
functions and advancements. What comes back is a `WorldStem`.
The threading is the part worth noticing. `WorldLoader.load` takes two
executors: `Util.backgroundExecutor` for the work, and a main-thread executor
for the stages that must be single-threaded. `Util.blockUntilDone` supplies
the second by handing the loader a queue's *add* method and then draining that
queue until the future completes. For the length of the world load the JVM
main thread is an event loop, running the pack-opening stage and the final
assembly itself between bouts of waiting on the workers.
Two things then happen before the server object exists. With *--forceUpgrade*
or *--recreateRegionFiles*, a `WorldUpgrader` rewrites every region file while
*main* polls it once a second and logs a percentage. And either way
`LevelStorageSource.LevelStorageAccess.saveDataTag` writes `level.dat` back
out through a temp file, rotating the previous copy into `level.dat_old`. That
is unconditional: a server started and killed one second later has already
rewritten its world data.
## `MinecraftServer.spin`, and the last thing *main* does
`MinecraftServer.spin` takes a factory rather than a server, and its order is
the point. It builds the `Thread` object first, sets priority 8 on a machine
with more than four processors, calls the factory *on the calling thread*, and
only then starts the thread — so the whole of the `DedicatedServer`
constructor runs on the JVM main thread, and the new thread cannot begin
before there is a server for it to run.
That constructor is real work. `MinecraftServer`'s own opens the server-wide
`SavedDataStorage`, takes the `WorldData` and `WorldGenSettings` out of the
stem ([creating a world](../worldgen/creating-a-world.md) is where the stem
was built, by a screen or by *server.properties*), builds the `ServerConnectionListener`, the `PlayerDataStorage`, the
`GameRules`, the `StructureTemplateManager` and the `PacketProcessor`,
finalises recipe loading, and refuses outright a stem whose `LevelStem`
registry has no overworld. `DedicatedServer`'s adds the `ServerTextFilter`,
the `ServerLinks` built from the bug-report property, and — when
*enable-code-of-conduct* is set — every *.txt* file under the *codeofconduct*
folder, which throws if that folder is missing. A misconfigured code of
conduct kills the server on the main thread, before a Server thread exists to
be killed.
Back in *main*, the factory has already applied *--port*, *--demo* and
*--serverId* and, unless *--nogui* or a headless JVM says otherwise, opened
the Swing window through `DedicatedServer.showGui`. A *Server Shutdown Thread*
hook goes on the runtime — its whole body is
[one `MinecraftServer.halt` call](how-a-server-dies.md) — and *main* returns.
## The Server thread wakes up, and can still fail twice
`MinecraftServer.runServer` is the Server thread's body and its first act is
`DedicatedServer.initServer`, which begins with the console: a daemon thread
named *Server console handler* reading `System.in` line by line. It runs
nothing itself. Each line becomes a `ConsoleInput` — the text plus a
`CommandSourceStack` built there on the console thread — appended to a
synchronized list that `DedicatedServer.handleConsoleInputs` drains from
`DedicatedServer.tickConnection`, so a typed command executes inside a tick
like every other command.
Then the properties become fields: online mode, the local IP, the default game
type, the port. `MinecraftServer.initializeKeyPair` generates the RSA pair that
login encryption uses ([players and sessions](players-and-sessions.md)), and
`ServerConnectionListener.startTcpServerListener` binds the port with a Netty
server bootstrap. Two things can end the boot at this point, and they end it
identically.
| the failure | what the console says | the check |
|---|---|---|
| the port is already taken | *FAILED TO BIND TO PORT!* and the exception | `ServerConnectionListener.startTcpServerListener` throws, and `DedicatedServer.initServer` returns false |
| a legacy user list survived conversion | *FAILED TO START THE SERVER AFTER ACCOUNT CONVERSION!* and the files to delete by hand | `OldUsersConverter.areOldUserlistsRemoved` looks for *banned-players.txt*, *banned-ips.txt*, *ops.txt* and *white-list.txt*, and is false if any of the four is still there |
The conversion itself is `DedicatedServer.convertOldUsers`, which attempts
five migrations — the two ban lists, the op list, the whitelist and the player
save files — retrying each up to twice more, five seconds apart, and reporting
whether *any* of them did something. The gate is the separate check above, so
what stops the boot is the file nobody could convert. Either failure makes
`MinecraftServer.runServer` throw, which lands in `MinecraftServer.runServer`'s own catch,
writes a crash report and falls into the same *finally* that `/stop` reaches —
a server that failed to bind still walks the whole shutdown path and releases
`session.lock` on the way out ([how a server dies](how-a-server-dies.md)).
Past them, a `DedicatedPlayerList` is built, offline names are resolved in the
cache, and the log says *Preparing level "world"*.
## Building the levels
`MinecraftServer.loadLevel` is three calls: `MinecraftServer.createLevels`,
`MinecraftServer.forceDifficulty` — empty in the base class, and overridden
only by `DedicatedServer.forceDifficulty`, which pushes *server.properties*'
difficulty onto the world on every boot — and
`MinecraftServer.prepareLevels`.
`MinecraftServer.createLevels` builds the overworld first and by name, and it
is the only level that is special. It gets the custom spawners — the
`PhantomSpawner`, the `PatrolSpawner`, the `CatSpawner`, the `VillageSiege`
and the `WanderingTraderSpawner` — and its `ServerLevelData` is the real
`PrimaryLevelData` out of `level.dat`. With it in `MinecraftServer.levels` (a
`LinkedHashMap`, so it stays first for every later walk over the levels), the
scoreboard, the `CommandStorage` and the `Stopwatches` come out of the
server-wide saved data. Every other `LevelStem` in the registry then gets a
`ServerLevel` over a `DerivedLevelData`, a view of the overworld's data —
which is why the time of day, the weather, the difficulty and the world spawn
are one set of numbers every dimension shares
([level data and rules](../../reference/level-data-and-rules.md)).
A brand-new world takes one detour, and it is the detour that actually
generates terrain at boot. When `ServerLevelData.isInitialized` is false,
`MinecraftServer.setInitialSpawn` asks the biome sampler for a spawn chunk,
reads the generator's spawn height, and walks a spiral over the chunks five
in each direction — the value `MinecraftServer.SPAWN_POSITION_SEARCH_RADIUS`
names, though the method spells it as literals rather than reading it —
calling `PlayerSpawnFinder.getSpawnPosInChunk` until one of them offers a
standable block. The bonus chest is placed here if *--bonusChest* asked for
one. Then the flag is set, and no later boot of that world repeats any of it.
## Preparing the levels, which prepares nothing
`MinecraftServer.prepareLevels` replays a list. For each level a
`ChunkLoadCounter` records which chunks are already `ChunkStatus.FULL`, calls
`TicketStorage.activateAllDeactivatedTickets`, runs the distance manager again
and counts what is new. The tickets it replays are the ones the last shutdown
parked rather than dropped, and of the nine `TicketType`s exactly two carry
`TicketType.FLAG_PERSIST` — `TicketType.FORCED`, from */forceload*, and
`TicketType.PORTAL` — so those two are the only entries a *chunk_tickets* file
contains ([tickets and loading](../world/tickets-and-loading.md)). The world
spawn is kept by nothing.
**Zero** — chunks `MinecraftServer.prepareLevels` loads on a world with no
forceloads and no live portal ticket.
What follows is `MinecraftServer.waitUntilNextTick` with the deadline set
`MinecraftServer.PREPARE_LEVELS_DEFAULT_DELAY_NANOS` — 10 ms — out, repeated
while `ChunkLoadCounter.pendingChunks` is above zero: the Server thread
pumping its own queue in slices, the same way a tick waits for a chunk
([the server tick](server-tick.md)). With a total of zero it runs once and
leaves. `MinecraftServer.updateMobSpawningFlags` and the effective respawn
data are recomputed, and the step is over.
### What the console prints while nothing is prepared
The output is worth reading literally. `LoggingLevelLoadListener` logs
*Loading N persistent chunks...* when
`LevelLoadListener.Stage.LOAD_INITIAL_CHUNKS` starts and *Time elapsed: N ms*
when it finishes, with *Preparing spawn area: N%* — still the
*menu.preparingSpawn* string, its percentage computed by
`LevelLoadProgressTracker` — printed at most twice a second in between. On an
ordinary world there is no in between: N is zero and the percentage line never
gets a chance to run. Of the four values `LevelLoadListener.Stage` declares,
`LevelLoadListener.Stage.PREPARE_GLOBAL_SPAWN` fires only on a world's first
boot, `LevelLoadListener.Stage.LOAD_PLAYER_CHUNKS` belongs to a player joining
rather than to boot — `PrepareSpawnTask`, in the configuration phase
([players and sessions](players-and-sessions.md)) — and
`LevelLoadListener.Stage.START_SERVER` is declared and fired by nothing.
> **For a 1.21-era reader.** There is no *spawnChunkRadius* game rule:
> `GameRuleRegistryFix` deletes it out of any save that still carries one, and
> the chunks around the world spawn are kept alive by whoever stands in them
> and by nothing else. *spawn-protection* in `server.properties` survives and
> is unrelated — it is a permission check in
> `DedicatedServer.isUnderSpawnProtection`, not a loader.
## *Done* comes before the loop
`DedicatedServer.initServer` logs *Done (1.284s)! For help, type "help"* the
moment `MinecraftServer.loadLevel` returns — and only then starts the optional
listeners. `QueryThreadGs4.create` runs if *enable-query*, `RconThread.create`
if *enable-rcon*, a *Server Watchdog* thread if
`DedicatedServer.getMaxTickLength` is above zero, JMX if
*enable-jmx-monitoring*. After those come one `MinecraftServer.saveEverything`
with flush and force and `NotificationManager.serverStarted` on the JSON-RPC
feed. `DedicatedServer.initServer` returns true, `MinecraftServer.runServer`
loads `server-icon.png` — the server directory's, or failing that the world's,
and it must be 64 by 64 — builds the first `ServerStatus`, and enters the tick
loop.
So *Done* is a claim about the world being ready rather than about the server
being reachable in every sense. The play port has been open since before the
level loaded, and RCON is still not listening when the line prints.
`MinecraftServer.isReady` — read by the singleplayer client's loading screen
and by the JSON-RPC status method — is set at the bottom of the loop's first
iteration, one tick later still.
## The threads startup leaves behind
Boot creates every thread on this list and then hands the process to one of
them. [Threads](../../reference/threads.md) has the complete set, including
the pools and the situational ones.
| thread | made by | daemon | what it may touch |
|---|---|---|---|
| **Server thread** | `MinecraftServer.spin`, from *main* | no | everything — it is the only thread allowed to change the world |
| *Server console handler* | `DedicatedServer.initServer`, first statement | yes | `System.in` and the `ConsoleInput` list. The command itself runs on the Server thread |
| *Server Watchdog* | `DedicatedServer.initServer`, only if `DedicatedServer.getMaxTickLength` is above zero | yes | reads `MinecraftServer.getNextTickTime`, the game rules and `ServerLevel.getWatchdogStats` off-thread, mid-tick |
| *RCON Listener*, plus one *RCON Client* per connection | `RconThread.create`, after *Done* | no | TCP accept. `DedicatedServer.runCommand` hops the command onto the Server thread with `BlockableEventLoop.executeBlocking` |
| *Query Listener* | `QueryThreadGs4.create`, after *Done* | no | a UDP status protocol, read-only |
| *Management server IO* | `JsonRpc.create`, back in *main* | yes | the JSON-RPC socket. `ManagementServer.tick` runs from `DedicatedServer.tickServer` |
| *Timer hack thread* | `Util.startTimerHackThread`, in *main* | yes | nothing. It sleeps and is never woken |
Two rows carry a consequence for the other end of the story. `RconThread` and
`QueryThreadGs4` are both `GenericThread`s, created from the Server thread and
never marked daemon, so they are the only non-daemon threads in this table
besides the Server thread itself — `Util.ioPool`'s *IO-Worker* threads, made
outside it and squarely in the boot path, are non-daemon too — and each polls
its socket with a half-second timeout so
that it notices `GenericThread.running` going false. And `RconThread.create`
returns nothing — logging that RCON is disabled — when *rcon.password* is
empty or *rcon.port* is out of range, so setting *enable-rcon* on its own
starts no thread at all.
One more thing outlives boot without being a thread: `server.properties` stays
live. Nineteen `DedicatedServerProperties` fields are `Settings.MutableValue`s,
and `Settings.MutableValue.update` rebuilds the whole properties object and
`DedicatedServerSettings.update` writes the file back, so */difficulty*,
*/whitelist*, the spawn-protection setter and the JSON-RPC settings calls all
edit `server.properties` on disk while the server runs.
## Singleplayer boots the same server with a shorter list
`IntegratedServer` uses the same `MinecraftServer.spin`,
`MinecraftServer.runServer` and `MinecraftServer.loadLevel`, and almost
nothing else on this page. `IntegratedServer.initServer` turns authentication
on, generates the key pair, calls `MinecraftServer.loadLevel`, sets the MOTD
from the host's name and the level name, saves once and returns true: no
console thread, no TCP bind, no legacy conversion, no RCON, no query, no
watchdog and no *Done* line. Its player list is built in the constructor
rather than in `DedicatedServer.initServer`, and its `LevelLoadListener` is a
`LevelLoadTracker` composed with the logging one, so the client's progress bar
and a dedicated server's log lines are two readings of the same callbacks. The
whole of it is constructed from `Minecraft.doWorldLoad` on the Render thread,
by a caller that goes back to drawing frames rather than returning from
*main* — [anatomy](../anatomy/anatomy.md) is that hand-off, and its diagram is
the one to read for how the two loops meet.
## Where to look
`server/Main` · `CrashReport.preload` · `Bootstrap.bootStrap` · `Eula` ·
`DedicatedServerSettings` · `DedicatedServerProperties` · `JsonRpc.create` ·
`LevelStorageSource.createDefault` · `LevelStorageSource.validateAndCreateAccess` ·
`LevelStorageSource.LevelStorageAccess` · `DirectoryLock.create` ·
`WorldLoader.load` · `WorldStem` · `Util.blockUntilDone` · `WorldUpgrader` ·
`LevelStorageSource.LevelStorageAccess.saveDataTag` · `MinecraftServer.spin` ·
`MinecraftServer.runServer` · `DedicatedServer.initServer` ·
`OldUsersConverter.areOldUserlistsRemoved` · `MinecraftServer.loadLevel` ·
`MinecraftServer.createLevels` · `MinecraftServer.setInitialSpawn` ·
`MinecraftServer.prepareLevels` · `ChunkLoadCounter` ·
`TicketStorage.activateAllDeactivatedTickets` · `LevelLoadListener` ·
`LoggingLevelLoadListener` · `RconThread.create` · `QueryThreadGs4.create` ·
`ServerWatchdog` · `IntegratedServer.initServer`
---
*Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes `tools/verify_names.py`.*
---
# How a server dies
> Verified against **Minecraft 26.2** · Part III · `/stop` typed at the console, an exception out of the tick loop, and a tick that never ends — three endings that write three different amounts of your world to disk.
An admin types `/stop`. The command sets one boolean and returns, and the
tick already in progress carries on to its end. Everything a player would
call *shutting down* — the players written, the unloads drained, `level.dat`
rotated, `session.lock` released — happens afterwards, inside the *finally*
of the loop that just exited, on the same Server thread that was ticking
mobs a moment ago. Which is what makes the second ending strange, and worth
a lecture: **a crash saves your world and the watchdog does not.** An
exception out of the tick loop lands in that same *finally*, so a server
that dies of a bad block entity writes exactly what `/stop` writes.
`ServerWatchdog` instead calls `System.exit`, which runs the Server Shutdown
Thread hook, which calls `MinecraftServer.halt` with *wait* true and waits
for the Server thread to finish — the very thread wedged in the tick that
tripped the watchdog. That wait never returns. Ten seconds later the
watchdog's own scheduled `Runtime.halt` ends the JVM with nothing written.
## The cast
| class | what it decides | thread |
|---|---|---|
| `MinecraftServer` | the three booleans, the tick loop, and the *finally* that is the whole of shutdown | Server |
| `StopCommand` | that `/stop` is one call to `MinecraftServer.halt` with *wait* false, at `Commands.LEVEL_OWNERS` | Server |
| `DedicatedServer` | what wraps the base teardown: the JSON-RPC notification, `Util.shutdownExecutors`, and the side threads in `DedicatedServer.onServerExit` | Server |
| `ServerWatchdog` | that a tick past *max-tick-time* is a dead server, and that the JVM goes with it | Server Watchdog, a daemon |
| `PlayerList` | that every player is written before anyone is disconnected | Server |
| `ServerChunkCache` · `ChunkMap` | when the world is quiet enough to stop draining, and what a flush save means | Server, with the writes on the IO pool |
| `LevelStorageSource.LevelStorageAccess` | `level.dat` and the `DirectoryLock` on `session.lock` | Server |
| `Util` | the process-wide pools, and the three-second grace each of the two it shuts down gets | any |
## Three endings, side by side
| | `/stop` | a tick-loop crash | a watchdog kill |
|---|---|---|---|
| **what clears `MinecraftServer.running`** | `MinecraftServer.halt`, with *wait* false from `StopCommand`, the JSON-RPC state service or the singleplayer host logging out, with *wait* true from the shutdown hook and the server GUI's close button | nothing: the loop is left by the throw, not by the condition | the shutdown hook, eventually — `System.exit` runs it and it calls `MinecraftServer.halt` with *wait* true |
| **does `MinecraftServer.runServer`'s *finally* run** | yes, on the Server thread | yes, on the Server thread, after the crash report | no: the Server thread never leaves the tick |
| **are players saved** | yes, `PlayerList.saveAll` then `PlayerList.removeAll` | yes, identically | no |
| **are chunks saved** | yes: the unload drain, then `MinecraftServer.saveAllChunks` with *flush* | yes, identically | no — only what the last autosave happened to write |
| **is `level.dat` written** | yes, `LevelStorageSource.LevelStorageAccess.saveDataTag` | yes, identically | no |
| **is `session.lock` released** | yes, `LevelStorageSource.LevelStorageAccess.close` drops the `DirectoryLock` | yes, identically | not by the game — the OS drops it when the process dies |
| **is a crash report written** | no | yes, into *crash-reports/*, from `MinecraftServer.constructOrExtractCrashReport` | yes, into *crash-reports/*, from `ServerWatchdog.createWatchdogCrashReport`, before the exit |
| **what ends the JVM** | nothing explicit: the Server thread returns and no non-daemon thread is left | the same | `Runtime.halt` from the watchdog's own timer, ten seconds after its `System.exit` |
The first two columns differ in three of the eight rows: what clears the
flag, when the *finally* runs relative to the crash report, and whether there
is a crash report at all. The third differs from the first in all eight, and
the rest of this page is why.
## `/stop`, in full
```mermaid
sequenceDiagram
participant SC as StopCommand
participant MS as MinecraftServer
participant PL as PlayerList
participant SL as ServerLevel
participant SCC as ServerChunkCache
participant LSA as LevelStorageSource.LevelStorageAccess
participant Disk
SC->>MS: halt with wait false, so running becomes false
Note over MS: the tick in progress finishes, then the loop condition fails
MS->>MS: stopped = true, then stopServer, from runServer's finally
MS->>MS: PacketProcessor.close, then ServerConnectionListener.stop
MS->>PL: saveAll, then removeAll
PL->>Disk: each player's dat file, stats and advancements
MS->>SL: noSave cleared on every level
loop while any ChunkMap.hasWork
MS->>SCC: the deadline is pushed one millisecond out, then deactivateTicketsOnClosing and tick
end
MS->>SL: saveAllChunks with flush, reaching ChunkMap.saveAllChunks
SL->>Disk: region files, entities, poi, and the chunk_tickets saved data
MS->>LSA: saveDataTag, level.dat built into a temp file
LSA->>Disk: the temp file replaces level.dat, the old one rotated to level.dat_old
MS->>MS: savedDataStorage.saveAndJoin, after level.dat and not before
MS->>SL: close, ServerChunkCache.close then the entity manager
MS->>LSA: close, releasing the DirectoryLock on session.lock
MS->>MS: Util.shutdownExecutors, then onServerExit stops RCON and query
Note over MS: the Server thread returns, and no non-daemon thread is left
```
### The command is a flag
`StopCommand` registers one literal at `Commands.LEVEL_OWNERS`, sends
*commands.stop.stopping* and calls `MinecraftServer.halt` with *wait* false.
That call assigns `MinecraftServer.running` and returns. Nothing else
happens on that line of the console: the tick that was running the command
finishes its entities, its block entities and its packet flush, and the loop
condition at the top of `MinecraftServer.runServer` fails on the next pass.
Five other places on this side of the jar call the same method: the server
GUI's window-close listener and `Main`'s shutdown hook, both with *wait* true,
so that they block until the Server thread has finished; the JSON-RPC
management API's `MinecraftServerStateServiceImpl`, which takes the flag from
its caller; `ServerCommonPacketListenerImpl.onDisconnect`, which stops a
singleplayer server when its host logs out; and `GameTestServer`, which halts
itself when its test run is over. The client adds three more of its own.
Teardown itself is the loop's *finally*. `MinecraftServer.runServer` sets
`MinecraftServer.stopped` and calls `MinecraftServer.stopServer`, then calls
`MinecraftServer.onServerExit` from a nested *finally*, so that a teardown
which throws still stops the side threads. `DedicatedServer.stopServer`
wraps the base with `NotificationManager.serverShuttingDown` before and
`Util.shutdownExecutors` after.
### The front door closes, the guests do not leave
`PacketProcessor.close` is first, before anything is even logged. Afterwards
`PacketProcessor.scheduleIfPossible` refuses a packet a Netty thread has just
decoded, and `PacketProcessor.processQueuedPackets` returns without draining
— so packets already in the queue go the same way as the ones still
arriving. Then `ServerConnectionListener.stop` closes the channels it
*bound*, and only those: closing a Netty parent channel does not close the
connections accepted through it. Live sessions are severed one step later by
`PlayerList.removeAll`, with the *multiplayer.disconnect.server_shutdown*
reason. A connection still in handshake, login or configuration has no
`ServerPlayer` and is in neither list, so it is closed by neither, and simply
dies with the process ([players and sessions](players-and-sessions.md)).
`MinecraftServer.stopped` also changes how work is accepted:
`MinecraftServer.executeIfPossible` rejects anything new outright, and
`MinecraftServer.scheduleExecutables` reports false, so a caller reaching
`BlockableEventLoop.execute` from another thread runs its task inline rather
than queueing it for a loop that has stopped looping.
### Players before chunks, and never `MinecraftServer.saveEverything`
Shutdown does not use the save entry point everything else uses.
`MinecraftServer.saveEverything` — autosave, `/save-all`, the integrated
server's pause, the JSON-RPC save call — is players *then* chunks in one
call. `MinecraftServer.stopServer` does the two halves by hand:
`PlayerList.saveAll` (each player's data through `PlayerDataStorage`, plus
their `ServerStatsCounter` and `PlayerAdvancements`), then
`PlayerList.removeAll`, and only much later `MinecraftServer.saveAllChunks`.
`PlayerList.removeAll` is thinner than its name: it disconnects each
connection and nothing else, so the tickets those players hold are *not* what
goes with them. What lets the next step finish is
`ServerChunkCache.deactivateTicketsOnClosing`, called on every level inside
the drain loop itself.
`ServerLevel.noSave` is then cleared on every level. `/save-off` does not
survive `/stop`.
### The drain
`ChunkMap.hasWork` is the question, and it is a broad one — nine things in
one *or*: pending light, pending unloads, a non-empty updating map, POI work,
chunks queued to drop, a non-empty unload queue, the worldgen and light
dispatchers, and — the reason the loop terminates at all —
`DistanceManager.hasTickets`. While any level answers yes, the server pushes
the tick deadline one millisecond out, calls
`ServerChunkCache.deactivateTicketsOnClosing` and `ServerChunkCache.tick` on
each level, and runs `MinecraftServer.waitUntilNextTick`, which drains the
main-thread queue and polls each level's chunk executor for what is left of
that millisecond.
**One millisecond** — each slice of the drain, so unloads and their saves
proceed while the main-thread queue keeps taking chunk results.
`TicketStorage.deactivateTicketsOnClosing` moves every ticket except
`TicketType.UNKNOWN` into a parked map. Parked tickets stop holding chunks —
`TicketStorage.hasTickets` counts only the live map, which is how the loop
ends — but they are not forgotten. `TicketStorage.packTickets` writes both
maps, and the types that `TicketType.persist`, forced and portal, go into the
level's *chunk_tickets* saved data. On the next boot they load back parked
and `TicketStorage.activateAllDeactivatedTickets` re-arms them during
`MinecraftServer.prepareLevels`
([tickets and loading](../world/tickets-and-loading.md),
[starting a server](starting-a-server.md)).
### The flush save
`MinecraftServer.saveAllChunks` with *flush* true is the real save. The
scoreboard is pushed into its saved data, then each `ServerLevel.save`:
`ServerLevel.saveLevelData` joins the level's own `SavedDataStorage`, and
`ServerChunkCache.save` runs the distance manager once more before
`ChunkMap.saveAllChunks` in flush mode. That last one is a *loop*, not a
pass — every holder that `ChunkHolder.wasAccessibleSinceLastSave`, waited on
with `BlockableEventLoop.managedBlock` until `ChunkHolder.isReadyForSaving`,
repeated until a whole round saves nothing new — and then
`SectionStorage.flushAll` for the POI sections, the unloads processed, and
`SimpleRegionStorage.synchronize` joined so that the `IOWorker` has actually
put the bytes down ([chunk storage](../world/chunk-storage.md)). Entities
follow, through the level's `PersistentEntitySectionManager`.
`level.dat` is written the same way at every save, flush or not:
`LevelStorageSource.LevelStorageAccess.saveDataTag` builds the tag from
`PrimaryLevelData.createTag`, wraps it under *Data*, writes it gzipped to a
temp file in the world directory with `NbtIo.writeCompressed`, and
`Util.safeReplaceFile` swaps it in, rotating the previous file to
`level.dat_old`. Only after that does the *server-wide* `SavedDataStorage`
get its `SavedDataStorage.saveAndJoin`. There are two tiers of saved data,
and they are flushed at opposite ends of this section.
### The closes, and the last thread
`ServerLevel.close` is `ServerChunkCache.close` — which saves once more, then
closes the level's saved data, the `ThreadedLevelLightEngine` and `ChunkMap`
— followed by the entity manager. Then the server's own `SavedDataStorage`
(whose `SavedDataStorage.close` is itself a final
`SavedDataStorage.saveAndJoin`), the `MinecraftServer.ReloadableResources`,
and last `LevelStorageSource.LevelStorageAccess.close`, which releases the
`DirectoryLock`. From that moment the world is openable by anything else.
`Util.shutdownExecutors` then stops `Util.backgroundExecutor` and
`Util.ioPool` with a three-second grace each, inside
`DedicatedServer.stopServer` and so *before* `DedicatedServer.onServerExit`
— nothing may need a worker after that point. `Util.nonCriticalIoPool` is
untouched, and survives only because its threads are daemons.
`DedicatedServer.onServerExit` closes the text filter and the GUI and stops
`RconThread`, `QueryThreadGs4` and the `ManagementServer`. There is no
`System.exit` anywhere on this path, and none is needed. Every other thread
the server started is a daemon — the console reader, the Netty groups, the
management server's group, the watchdog — except the RCON and query threads,
which `GenericThread.stop` joins here in one-second slices, and the IO
pool's workers, which went a step earlier with `Util.shutdownExecutors`. So
when `MinecraftServer.runServer` returns, the Server thread is the last one
left, and the JVM ends because there is nothing to keep it
([the thread reference](../../reference/threads.md)).
## The crash that saves
`MinecraftServer.runServer` wraps the entire loop, `DedicatedServer.initServer`
included. Anything thrown out of a tick — a block entity, a mob's AI, a
command, a packet handler that did not catch its own trouble — is logged,
turned into a report, saved, and then falls into the same *finally*.
`MinecraftServer.constructOrExtractCrashReport` walks the cause chain and
keeps the *innermost* `ReportedException` it finds, using that exception's
own report and noting the outer throwable under a *Wrapped in* category. A
throwable with no `ReportedException` anywhere in it becomes a fresh report
titled *Exception in server tick loop*. Either way
`MinecraftServer.fillSystemReport` fills it in — the value of
`MinecraftServer.running`, the player count and roster, the selected and
available data packs, the enabled feature flags, the world-generation
lifecycle, the world seed, and the contents of the server's
`SuppressedExceptionCollector`, which has been quietly watching every chunk
load failure, chunk save failure and packet-handler exception since boot —
keeping the latest eight of them in full, and a running count of the rest.
`DedicatedServer.fillServerSystemReport` adds two lines, the modded
status and the words *Dedicated Server*. The file lands in *crash-reports/*
under `MinecraftServer.getServerDirectory`, named by
`Util.getFilenameFormattedDateTime`. `MinecraftServer.onServerCrash` is a
hook the dedicated server does not override.
**A crash on another thread arrives here too.** `BlockableEventLoop` keeps
one static parked report. `Util.onThreadException` — the uncaught-exception
handler on every `Util.backgroundExecutor` and IO-pool thread — and
`GenerationChunkHolder`, when a generation step completes exceptionally,
both call `BlockableEventLoop.relayDelayCrash`, which parks the report or
suppresses the new one under a report already parked. The next
`BlockableEventLoop.pollTask` on a loop constructed with crash propagation
throws it as a `ReportedException`, and the dedicated server is constructed
that way. So a worker that dies does not die silently: it dies as a
tick-loop crash, on the Server thread, at whatever moment that thread next
looks for a task ([the server tick](server-tick.md)). The integrated server
is constructed with propagation off and hands its report to the client
instead, through `IntegratedServer.onServerCrash`.
## The watchdog that does not
```mermaid
sequenceDiagram
participant SW as ServerWatchdog
participant MS as MinecraftServer
participant JVM
participant Hook as Server Shutdown Thread
Note over MS: wedged inside one tick, past max-tick-time
SW->>MS: getNextTickTime, a deadline now far in the past
SW->>SW: createWatchdogCrashReport, every thread dumped, the Server thread's stack grafted on
SW->>MS: fillSystemReport, read off-thread while the tick is still running
SW->>JVM: schedule Runtime.halt for ten seconds from now
SW->>JVM: System.exit
JVM->>Hook: run the shutdown hooks
Hook->>MS: halt with wait true, running becomes false
Hook->>MS: then waits for the Server thread, which is the wedged one
MS-->>Hook: nothing, because the tick never returns
Note over JVM: ten seconds later, Runtime.halt, nothing written
```
`ServerWatchdog` is a daemon thread started by `DedicatedServer.initServer`
whenever `DedicatedServer.getMaxTickLength` is positive — that is
`DedicatedServerProperties.maxTickTime`, *max-tick-time*, default sixty
thousand milliseconds, and setting it to zero or less means the thread is
never created and there is no backstop at all. Its loop is short: while
`MinecraftServer.isRunning`, compare `Util.getNanos` with
`MinecraftServer.getNextTickTime`, then sleep exactly until the earliest
moment a violation could be true.
What it compares matters. `MinecraftServer.getNextTickTime` is the *deadline*
the loop set for the tick it is running, not a timestamp of when that tick
began, and the tick loop advances it before each tick and again when catching
up after an overload. The watchdog fires when the server is that far past
where it promised to be.
The report comes first, and it is the good part of the design.
`ServerWatchdog.createWatchdogCrashReport` dumps every thread in the JVM,
sorts them daemon-last, appends the lot as a *Thread Dump* category, and
grafts the Server thread's stack trace onto a synthetic error — so the
report's headline stack is the code that hung. `MinecraftServer.fillSystemReport`
adds the usual, plus a *Performance stats* category holding the random-tick
game rule and `ServerLevel.getWatchdogStats` for every level: players,
entities by type, block entities by type, block and fluid tick counts, chunk
source stats. All of it is read from another thread with no synchronisation
whatsoever, off a world that is mid-tick — which is exactly the trade the
class makes, because the alternative is asking a wedged thread for the
answer. It goes to real stdout through `Bootstrap.realStdoutPrintln` and to
*crash-reports/* like any other report.
Then the deadlock. The watchdog schedules `Runtime.halt` on a timer and calls
`System.exit`, which runs the registered shutdown hooks — including the
"Server Shutdown Thread" that `Main` registered at boot, whose whole body is
`MinecraftServer.halt` with *wait* true. That sets `MinecraftServer.running`
false, which the wedged tick will never read, and then waits for the Server
thread to end. It does not end. `System.exit` will not return until its hooks
do, so the JVM sits there until the watchdog's timer fires.
**Ten seconds** — from the watchdog's `System.exit` to its `Runtime.halt`
(`ServerWatchdog.MAX_SHUTDOWN_TIME`), and the world is not touched in any of
them.
The watchdog is a liveness backstop, and reading it as a safe stop gets the
guarantee backwards. Whether anything guards shutdown depends on how
shutdown was reached, because the watchdog loops while
`MinecraftServer.running` is true and only `MinecraftServer.halt` ever clears
that flag. After `/stop` it is cleared before the drain begins, so a server
stuck on "Saving chunks" is stuck with no watchdog left watching it. After a
crash nothing clears it — the crash path never calls `MinecraftServer.halt` —
so the watchdog
is still counting. The drain loop survives that by resetting the deadline
every pass; the flush save that follows it does not reset anything, so a slow
enough save after a crash can be shot by the watchdog mid-write.
## Ctrl-C, the window, and a singleplayer world
Ctrl-C at the console and a *SIGTERM* from a service manager are the same
thing as far as the game is concerned: the JVM runs its shutdown hooks, and
the one `Main` registered calls `MinecraftServer.halt` with *wait* true. The
contrast with the watchdog is only in the health of the thread being waited
for. Here the Server thread is fine, notices the cleared flag at the top of
its next tick, and runs the entire `/stop` teardown while the hook thread
waits. A Ctrl-C on a healthy server *is* a `/stop`, and the JVM does not exit
until the world is on disk. The server GUI's window-close button does the
same thing from the AWT thread.
Singleplayer ends on a poll. `Minecraft.disconnect` — reached from *Save and
Quit*, from a disconnect, and from `Minecraft.emergencySave` — closes the
client's connection, then calls `IntegratedServer.halt` with *wait* false.
That override first uses `BlockableEventLoop.executeBlocking` to remove every
player who is not the host, then clears `MinecraftServer.running` and stops
the LAN pinger. The client then puts up a `GenericMessageScreen` reading
`Gui.SAVING_LEVEL` and calls `Minecraft.renderFrame` in a loop while
`MinecraftServer.isShutdown` is false. The "Saving world" screen is not a
progress bar and is not driven by the server: it is a render loop spinning on
one question, *is the Server thread dead yet*
([the client loop](../client/the-client-loop.md)). Closing the game window
reaches the same place by a different road: `Window.shouldClose` makes
`Minecraft.runTick` call `Minecraft.stop`, which ends the frame loop, and
`Main` then calls `Minecraft.exitWorldAndClose` on its way out. The client's
"Client Shutdown Thread" is a JVM shutdown hook rather than that path — the
backstop for a kill signal — and after an ordinary exit it finds
`Minecraft.singleplayerServer` already null.
The integrated server never calls `Util.shutdownExecutors`.
`IntegratedServer.stopServer` tears down published state and defers to the
base. The client owns those pools and shuts them down at the very end of its
own life, long after the world is closed.
## Three booleans and a question
`MinecraftServer.running` is volatile and is the loop condition, and it is
the only one of the three that anything sets in order to stop the server.
`MinecraftServer.stopped` is a plain field set in the *finally* just before
teardown, read from other threads through `MinecraftServer.isStopped`, and
it is what closes the task queue. `MinecraftServer.isReady` is volatile, set
at the bottom of every loop iteration, and is not what prints *Done* — that
is logged in `DedicatedServer.initServer`, before the loop is entered.
`MinecraftServer.isShutdown` is the odd one out, and is not a field at all:
it asks whether the Server thread is still alive. Nothing sets it, nothing
can lie about it, and it stays false through the whole teardown whichever
ending is running — which is precisely why the singleplayer client waits on
it rather than on `MinecraftServer.isStopped`, which goes true at the *start*
of teardown, when nothing has been saved yet.
## What you lose if you kill the process
Ordinary autosave is `MinecraftServer.saveEverything` with neither *flush*
nor *force*, every 6000 ticks — five minutes of game clock, floored at 100
ticks. It writes every player, every dirty chunk — `ChunkMap.saveAllChunks`
clears `ChunkMap.nextChunkSaveTime` on the way in, so the ten-second per-chunk
spacing that throttles ordinary saving never gates an autosave — and,
unconditionally, `level.dat`, followed by a scheduled write of the level's
`SavedData`. Between the two, the spawn point and the world time (in
`level.dat`) and the weather, the game rules and the world clocks (each its
own `SavedData` file — *weather*, *game_rules*, *world_clocks*) on disk are
never more than one autosave stale, even on a server nobody ever stops
cleanly. Everything else — a chest filled two minutes ago, a mob that walked
into a new chunk, an inventory change — lives in the `LevelChunk` and the
entity sections until something saves them.
That gives an honest answer per ending. After `/stop` or a tick-loop crash,
nothing is lost: the drain, the flush save and the joined `IOWorker` mean the
process does not end until the bytes are down. The crash has one asterisk the
clean stop does not — the watchdog is still armed all the way through it — but
short of a save slow enough to trip it, both endings land the same. After a
watchdog kill, or a
*kill -9*, or a power cut, you lose everything since the last autosave, plus
anything still queued inside the `IOWorker` — those writes run on
`Util.ioPool`, and `Runtime.halt` does not wait for a pool.
What you never lose is access to the world. `session.lock` is an OS advisory
lock taken with `FileChannel.tryLock`, not a file whose contents mean
anything, and the operating system releases it when the process dies however
it dies. A world left behind by a killed server opens on the next start.
Copying a world directory copies a `session.lock` that means nothing at all.
Individual failures are quieter than any of this. A chunk that cannot be
written calls `MinecraftServer.reportChunkSaveFailure`: logged, added to the
`SuppressedExceptionCollector` that the next crash report will print, written
out as its own `ReportType.CHUNK_IO_ERROR` file under *debug/*, and followed
by a disk-space check. The tick does not stop, the server does not stop, and
the only sign at the time is a line in the log.
## Where to look
`StopCommand` · `MinecraftServer.halt` · `MinecraftServer.runServer` ·
`MinecraftServer.constructOrExtractCrashReport` · `MinecraftServer.stopServer` ·
`MinecraftServer.saveAllChunks` · `MinecraftServer.saveEverything` ·
`ServerLevel.save` · `ServerChunkCache.save` · `ChunkMap.saveAllChunks` ·
`ChunkMap.hasWork` · `TicketStorage.deactivateTicketsOnClosing` ·
`LevelStorageSource.LevelStorageAccess.saveDataTag` ·
`LevelStorageSource.LevelStorageAccess.close` · `DirectoryLock` ·
`Util.shutdownExecutors` · `DedicatedServer.stopServer` ·
`DedicatedServer.onServerExit` · `ServerWatchdog.run` ·
`ServerWatchdog.createWatchdogCrashReport` · `Main` (the shutdown hook) ·
`BlockableEventLoop.relayDelayCrash` · `IntegratedServer.halt` ·
`Minecraft.disconnect`
---
*Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes `tools/verify_names.py`.*
---
# IV · The world
> Verified against **Minecraft 26.2** · Part IV · The machinery that turns a place you have walked to into a place that exists: chunks made, lit, sent, saved and forgotten, and the four side-systems that make the world they hold feel alive.
Part III was one thread going round. This part is what it goes round *on*.
A world is too big to hold, so the server holds a moving window of it, and
almost everything in this part exists to decide the window's edge: which
chunks are worth building, how far past the edge to build them, which of
them tick, which of them are sent to you, and when one is finally written
down and let go. A player recognises the part by its edge — the ring of
half-made terrain past render distance, the mobs that stop moving when you
fly away from them, the *Saving world* bar. Almost none of that edge is one
number: **render distance, simulation distance and the mob-spawning radius
are three different radii, answered by three different mechanisms, and only
two of them are settings.**
## The shape of the part
Part IV is a conveyor with a vocabulary page in front of it. Four pages are
the conveyor and they hand a chunk to each other in order; the fifth page
defines the thing being handed. The other five are not on the line at all —
one is what the place and the hour decide, and four are about the world the
conveyor delivers.
```mermaid
flowchart TD
CA["Chunk anatomy: sections, palettes, heightmaps"]
TL["Tickets and loading: which chunk, at what level"]
GP["The generation pipeline: EMPTY to FULL"]
LI["Lighting: two 4-bit fields, off the tick"]
CS["Chunk storage: copy, encode, sectors"]
LC["a live LevelChunk in a ticking world"]
EA["Environment attributes: what the place and the hour decide"]
ST["Scheduled ticks: the appointment book"]
FL["Fluids: the book's biggest customer"]
GV["Game events and vibrations: what just happened"]
PI["Points of interest: what is worth going to"]
CA -- "the vocabulary every page below spends" --> TL
TL -- "a holder, a ceiling, three futures" --> GP
GP -- "a chunk that still needs its light finished" --> LI
LI -- "sections dirtied, one packet of them" --> LC
LC -- "the level rises past 44, nobody needs it" --> CS
CS -- "a ticket wants it back, and it is read in" --> TL
LC --> ST
ST -- "one customer, big enough for its own page" --> FL
LC --> GV
LC --> PI
EA -- "read by fluids and by the villagers, and by Part III" --> LC
```
What the part hands forward, and what it does not: [blocks and
states](../blocks/blocks-and-states.md) in Part V assumes the section and
palette model [chunk anatomy](chunk-anatomy.md) defines, and Part XII's
terrain generation is the cargo on the conveyor [the generation
pipeline](chunk-generation-pipeline.md) describes. Neither is a dependency
of this part; both are parts that depend on it.
## Before you start
[The server tick](../server/server-tick.md) and [the level
tick](../server/server-level-tick.md). Almost everything here happens on the Server
thread inside that loop, or on a worker the loop is waiting for — the IO lane
below is the exception the storage page is about — and the level tick is where
the chunk source is asked to do its five things.
Part II's [codecs](../foundations/codecs-nbt-json.md) and
[registries](../foundations/identifiers-and-registries.md) are assumed
wherever a chunk is written to disk or a type is looked up by name, and
[tags](../foundations/tags.md) wherever a behaviour is defined by a set the
data pack owns — which is most of what the last two pages do.
Nothing in this part needs Part V or beyond.
## Watch in this order
Lectures two to six are the chain — nothing later in it can be watched
first. The first is off the chain on purpose, and the last four can be
watched in any order once you have the vocabulary page.
1. [Environment attributes and timelines](environment-attributes-and-timelines.md)
— the one page here that depends on nothing else in the part, and the
page [the level tick](../server/server-level-tick.md) already asked you
to watch. Whether lava flows fast, what colour the sky is and when a
villager goes to work are one mechanism. The night does not *set* the
sky's colour — it multiplies whatever the biome produced.
2. [Chunk anatomy](chunk-anatomy.md) — what a chunk is made of, down to
the bit storage. The vocabulary the rest of the part spends. A section
holding two block states costs exactly what one holding sixteen costs.
3. [Tickets and loading](tickets-and-loading.md) — a player takes one step
east and a column twenty-one chunks wide is asked for. Nothing ever asks
whether a chunk is loaded: it asks for a *level*, and two graphs reading
one ticket store answer different questions about it.
4. [The chunk generation pipeline](chunk-generation-pipeline.md) — one
chunk from *EMPTY* to *FULL* through twelve statuses and a pyramid of
neighbour requirements. Asking for one chunk asks for 529.
5. [Lighting](lighting.md) — a torch is placed. Two 4-bit fields flooded on
a worker and published as a copy. There is no light thread, and no light
phase of the tick.
6. [Chunk storage](chunk-storage.md) — a chunk nobody needs is copied,
encoded and written, on three different threads, and the save path waits
for none of it. Most of your world's writes are ones nobody asked for.
7. [Scheduled ticks](scheduled-ticks.md) — how anything happens *later*: an
appointment book of two queues per chunk, and a dedup rule that quietly
drops the second appointment even when it is sooner.
8. [Fluids](fluids.md) — a bucket of water on flat stone. Water finds a
hole four blocks away because every side runs its own search, and a side
the water cannot even enter still votes on where the rest of it goes.
9. [Game events and vibrations](game-events-and-vibrations.md) — a footstep
reaches a sculk sensor, through a cascade of tests that is most of the
lecture. The sensor hears you one tick late by design.
10. [Points of interest](points-of-interest.md) — a villager claims a bed
from 48 blocks away, the moment a path to it exists. Going to sleep in it
tells the index nothing, and the one behaviour that acts on the flag can
only take a claim away.
## Reference this part uses
[Level data and rules](../../reference/level-data-and-rules.md) — who owns
the seed, the spawn, the rules, the border and the dimensions, and which
file remembers each. [Game rules](../../reference/gamerules.md) — five of
which this part reads. [Math and
primitives](../../reference/math-and-primitives.md) — `ChunkPos` and
`SectionPos`, and the packings the conveyor pages assume. [Block update
flags](../../reference/block-update-flags.md) — the flag word three pages here
spend. [Threads](../../reference/threads.md) — the worker pool and the IO
lane. [Diagram lanes](../../reference/lanes.md).
---
*Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes `tools/verify_names.py`.*
---
# Environment attributes and timelines
> Verified against **Minecraft 26.2** · Part IV · The trace: dusk falls — one value resolved through a stack of layers, on the server and again on the client.
At tick 12542 on the overworld clock the sun goes under, and three things a
player would never connect happen at once: the sky over a taiga slides from
its pale blue towards black, the sky over a pale garden slides from its grey
towards black by the same proportion, and every mob in the open stops being
in danger of burning at dawn. In 26.2 those are one mechanism. An **environment
attribute** is a named, typed, registered property of the world —
`EnvironmentAttributes` puts 48 of them in
`BuiltInRegistries.ENVIRONMENT_ATTRIBUTE` — and the world answers one for a
position and an instant by running a short stack of **layers** over the
attribute's default value: the dimension, the biome, the **timelines**, the
weather. The surprise is in what a timeline holds. The day timeline does not
know what colour a taiga sky is and never learns: its keyframes are not
values but *modifier arguments*, and the night segment of the sky track is a
multiply. **Night does not set the sky's colour — it multiplies whatever the
biome produced**, which is how one data-driven curve darkens every overworld
biome correctly without being told about any of them.
> **For a 1.21-era reader.** The gameplay booleans you would look for on
> `DimensionType` are entries in `DimensionType.attributes` now: where the
> nether once said *ultrawarm*, *bed_works*, *piglin_safe* and
> *respawn_anchor_works*, it sets `EnvironmentAttributes.FAST_LAVA`,
> `EnvironmentAttributes.WATER_EVAPORATES`, `EnvironmentAttributes.BED_RULE`,
> `EnvironmentAttributes.PIGLINS_ZOMBIFY` and
> `EnvironmentAttributes.RESPAWN_ANCHOR_WORKS`, while
> `DimensionType.hasFixedTime` and `DimensionType.ambientLight` stayed put.
> `BiomeSpecialEffects` has shrunk to the water, foliage and grass tints —
> sky and fog are `Biome.getAttributes` — and the villager *Schedule* class
> is `Timelines.VILLAGER_SCHEDULE`, a data-pack `Timeline` like any other.
## The cast
| class | what it decides | thread |
|---|---|---|
| `EnvironmentAttribute` | the key: a type, a default, an `AttributeRange` and three flags. It holds no value and no state | — (registry constant) |
| `EnvironmentAttributeMap` | what one dimension or one biome contributes — a modifier and an argument per attribute, never a bare value | — (loaded from data) |
| `EnvironmentAttributeSystem` | the baked per-level resolver: one `EnvironmentAttributeSystem.ValueSampler` for each attribute some layer mentions | built in the level constructor, read on that level's thread |
| `Timeline` | a clock, an optional period, one `AttributeTrack` per attribute, and the named instants on that clock | — (loaded from data) |
| `AttributeTrackSampler` | one track baked against a clock, with a one-tick cache of the sampled argument | Server or Render |
| `ServerClockManager` | **the owner of day time** — one `ServerClockManager.ClockInstance` per registered `WorldClock`, saved as *world_clocks* | Server |
| `ClientClockManager` | the client's copy: free-runs each clock forward between packets | Render |
| `EnvironmentAttributeProbe` | the client's smoothing layer, living on `Camera`: 216 biome samples a tick, a lerp every frame | Render |
## The stack a value falls through
```mermaid
flowchart BT
DEF["the attribute's own EnvironmentAttribute.defaultValue enters here"] --> DIM
DIM["1 — dimension: DimensionType.attributes as EnvironmentAttributeLayer.Constant, folded into the baked base by EnvironmentAttributeSystem.bakeLayerSampler"] --> BIO
BIO["2 — biome: one EnvironmentAttributeLayer.Positional for each attribute any biome in the registry mentions"] --> TLS
TLS["3 — timelines: DimensionType.timelines, one EnvironmentAttributeLayer.TimeBased for each track a timeline carries"] --> WEA
WEA["4 — weather: WeatherAttributes.addBuiltinLayers, added only when Level.canHaveWeather"] --> LF1
LF1["client only — the sky colour lerped toward the lightning flash colour"] --> LF2
LF2["client only — the sky light factor pinned to 1 while the flash lasts"] --> SAN
SAN["EnvironmentAttribute.sanitizeValue clamps the result to the attribute's AttributeRange"]
```
`EnvironmentAttributeSystem.Builder.addDefaultLayers` stacks those four in
that order and only that order. There is no priority number anywhere and no
ordering data: a biome cannot run before its dimension, and weather is the
last word on the server — on the client the two lightning-flash layers sit
above it. The stack is *per attribute*, too — an attribute nothing
in the level mentions has no `EnvironmentAttributeSystem.ValueSampler` at
all, and `EnvironmentAttributeSystem.getValue` hands back its default. And it
is baked once: the whole thing is built in the `ServerLevel` and
`ClientLevel` constructors and never rebuilt, the only writer being
`ServerLevel.setEnvironmentAttributes`, which is deprecated, marked for
testing and called only from `TestEnvironmentDefinition`. A data-pack reload
does not touch it.
Each rung earns its shape. The dimension's is an
`EnvironmentAttributeLayer.Constant`, and
`EnvironmentAttributeSystem.bakeLayerSampler` walks off the front of the list
folding every *leading* constant into one baked base value, so a dimension
costs nothing at read time. The biome's is an
`EnvironmentAttributeLayer.Positional`, added once per attribute that any
biome in the whole registry mentions — in vanilla, eleven attributes across
sixty-six biome files, with *visual/sky_color* in fifty-six of them. Timeline
layers are `EnvironmentAttributeLayer.TimeBased`, one per track, and so is
weather: one for each of the nine attributes named by
`WeatherAttributes.RAIN` or `WeatherAttributes.THUNDER`, blending rain in
first and thunder second — rain at `Level.getRainLevel` *minus* the thunder
level, so a thunderstorm never counts twice. `Level.canHaveWeather` wants sky
light, no ceiling and not the End, so a rain-free dimension does not carry a
weather layer that does nothing: it carries none.
`ClientLevel` adds two more after those four, both keyed on the lightning
flash that `LightningBolt` sets through `Level.setSkyFlashTime`. One lerps
`EnvironmentAttributes.SKY_COLOR` a fixed 22% toward a pale blue-white, the
other pins `EnvironmentAttributes.SKY_LIGHT_FACTOR` to 1 outright, and both
read the flash through the accessibility option *Hide Lightning Flashes*,
which reports a flash time of zero. (The End's sky flash is a different thing
entirely — `EndFlashState`, read by the renderers rather than through the
stack; see [lightmap, fog and sky](../rendering/lightmap-fog-and-sky.md).)
Two rules police what may enter. `Biome.getAttributes` is read through
`EnvironmentAttributeMap.CODEC_ONLY_POSITIONAL`, which makes it a load error
for a biome to name a non-positional attribute — so no biome can locally
change sky light level or lava speed. And
`WorldGenRegion.environmentAttributes` returns
`EnvironmentAttributeReader.EMPTY`, answering everything with its default: a
feature that asks about the environment during generation gets a constant,
deliberately, because generation must not depend on the hour.
## Arguments, not values
`EnvironmentAttributeMap` is not a map of values. It is a map of
`EnvironmentAttributeMap.Entry`, and an entry is an *argument* plus an
`AttributeModifier`. `EnvironmentAttributeMap.Builder.set` is sugar for
`EnvironmentAttributeMap.Builder.modify` with `AttributeModifier.override`;
the interesting entries multiply, blend, maximise or *or* into whatever the
layer below produced. `AttributeTrack` is the same shape — a modifier plus a
`KeyframeTrack` of arguments — which is why `Timeline.Builder.addTrack` is
only the override case and `Timeline.Builder.addModifierTrack` is the general
one, and why the day timeline can say *multiply sky light by 0.267 at night*
rather than *sky light is 4 at night*. In a data pack the shorthand shows: an
entry written as a bare value means override, one written as an object
carries a *modifier* and an *argument*.
### What a type allows
`AttributeType` is a record of a
value codec, an `AttributeType.modifierLibrary` of the operations legal on
that type, and **four** separate `LerpFunction`s, one for each way two values
of it can meet.
| lerp slot | used when |
|---|---|
| `AttributeType.keyframeLerp` | between two keyframes of a timeline track that overrides the value — a track of *modifier arguments* uses the modifier's own `AttributeModifier.argumentKeyframeLerp` instead |
| `AttributeType.stateChangeLerp` | fading weather in and out |
| `AttributeType.spatialLerp` | across a biome boundary |
| `AttributeType.partialTickLerp` | between two client ticks, inside a frame |
`AttributeTypes` registers fourteen types — *boolean*, *tri_state*, *float*,
*angle_degrees*, *rgb_color*, *argb_color*, *integer*, *moon_phase*,
*activity*, *bed_rule*, *particle*, *ambient_particles*, *background_music*
and *ambient_sounds*. One built by `AttributeType.ofNotInterpolated` gets a
step function in all four slots, each with its own threshold, which is how a
`MoonPhase` snaps while a colour slides; `AttributeType.toFloat` is nullable,
and its presence decides whether an attribute can be read as a loot number.
The library is small — `BooleanModifier` is six logic gates, `FloatModifier`
adds, subtracts, multiplies, minimises, maximises and alpha-blends a
`FloatWithAlpha`, `ColorModifier` multiplies RGB or ARGB, alpha-blends or
blends toward grey through a `ColorModifier.BlendToGray`, and
`IntegerModifier` rounds it out — and `AttributeType.checkAllowedModifier`
throws at build time when a track or an entry asks for an operation the type
does not publish, so an illegal combination is a load error rather than a
runtime surprise. Three codecs then decide who may write what:
| codec | used by | effect |
|---|---|---|
| `EnvironmentAttributeMap.CODEC` | `DimensionType.attributes` | anything |
| `EnvironmentAttributeMap.CODEC_ONLY_POSITIONAL` | `Biome.getAttributes` | **rejects non-positional attributes** |
| `EnvironmentAttributeMap.NETWORK_CODEC` | `DimensionType.NETWORK_CODEC`, `Biome.NETWORK_CODEC` | drops every non-syncable entry before the wire |
## Who owns the clock
`WorldClock` is a unit record. It holds nothing at all: it is an identity
token in the `Registries.WORLD_CLOCK` registry, and vanilla registers two,
`WorldClocks.OVERWORLD` and `WorldClocks.THE_END`. Every piece of state lives
in `ServerClockManager.ClockInstance` — a total tick count, a fractional
partial tick, a rate and a paused flag — and the manager holding those is a
`SavedData` under `ServerClockManager.TYPE`, saved once for the whole server
as *world_clocks*. **`ServerClockManager` is the owner of day time**; both
[level data and rules](../../reference/level-data-and-rules.md) and
[the level tick](../server/server-level-tick.md) point here for it.
`MinecraftServer` calls `ServerClockManager.tick` once per server tick,
inside the *clocks* profiler zone and only while the tick-rate manager runs
normally; the `GameRules.ADVANCE_TIME` check sits inside the method itself,
and gates every clock at once where `ServerClockManager.setPaused` gates one.
Each unpaused instance then gains its rate, accumulating the fraction, so a
clock at rate 0.5 gains a tick every other server tick and one at rate 1000
gains a thousand — the command accepts anything from 0.00001 to 1000.
A `ClockTimeMarker` is a named instant on a clock: `ClockTimeMarkers.DAY`,
*NOON*, *NIGHT*, *MIDNIGHT*, *WAKE_UP_FROM_SLEEP*, *ROLL_VILLAGE_SIEGE*.
Markers are declared *inside* timelines and collected onto the clock by
`ServerClockManager.init`, and `Timeline.validateRegistry` fails the whole
registry load if two timelines on one clock declare the same one. The subset
a player can name is the one flagged `ClockTimeMarker.showInCommands`;
`ServerClockManager.isAtTimeMarker` is how `VillageSiege` asks whether the
siege roll is due, and `ServerLevel.tick` calls
`ServerClockManager.moveToTimeMarker` to jump the clock when enough players
are asleep. `TimeCommand` registers its whole subtree twice — once directly
on `/time`, against the source level's `DimensionType.defaultClock`, and once
under `/time of` against a clock the player names — so *set*, *add*, *pause*,
*resume*, *rate* and *query* exist in both forms. Only `/time query gametime`
sits outside the clock nodes.
## The four timelines
| timeline | period | what it carries |
|---|---:|---|
| `Timelines.OVERWORLD_DAY` | 24000 | the whole day/night curve — sun, moon and star angles, sky and fog colours, sky light, and the gameplay flags that flip at dusk |
| `Timelines.MOON` | 24000 × `MoonPhase.COUNT` | the moon phase, and the surface slime spawn chance riding the same steps |
| `Timelines.VILLAGER_SCHEDULE` | 24000 | `EnvironmentAttributes.VILLAGER_ACTIVITY` and `EnvironmentAttributes.BABY_VILLAGER_ACTIVITY` |
| `Timelines.EARLY_GAME` | none | one ramp that *and*s `EnvironmentAttributes.CAN_PILLAGER_PATROL_SPAWN` with false until tick 120000 |
All four run on `WorldClocks.OVERWORLD`; nothing in vanilla is bound to the
End's clock. Which of them a dimension runs is a tag on
`DimensionType.timelines`: `TimelineTags.IN_OVERWORLD` names the day, moon
and early-game timelines on top of `TimelineTags.UNIVERSAL`, while
`TimelineTags.IN_NETHER` and `TimelineTags.IN_END` name only the universal
one, which holds the villager schedule.
## What crosses the wire
The *rules* travel, never the resolved values. `Registries.TIMELINE` and
`Registries.WORLD_CLOCK` are in `RegistryDataLoader.SYNCHRONIZED_REGISTRIES`
— the timeline through `Timeline.NETWORK_CODEC`, so only syncable tracks go —
while `Registries.ENVIRONMENT_ATTRIBUTE` and `Registries.ATTRIBUTE_TYPE` are
built-in code registries that never go out at all. Clock *state* rides
`ClientboundSetTimePacket`: a game time plus a `ClockNetworkState` — total
ticks, partial tick, rate — per clock in its map.
`ServerClockManager.createFullSyncPacket` fills that map on join and on a
`GameRules.ADVANCE_TIME` change and every mutator broadcasts a one-clock
update, but the routine broadcast from
`MinecraftServer.forceGameTimeSynchronization`, once every twenty ticks,
sends an *empty* map and nothing but the game time.
`ClientClockManager.handleUpdates` adopts what arrives and
`ClientClockManager.tick` free-runs the rest — which is why a paused clock
travels as rate 0: the client has no paused flag to receive.
## The trace: dusk falls
A mob asks whether it should be burning, and the camera asks what colour the
sky is. They are the same question asked twice.
```mermaid
sequenceDiagram
participant SL as ServerLevel
participant EAS as EnvironmentAttributeSystem
participant EVS as EnvironmentAttributeSystem.ValueSampler
participant ATS as AttributeTrackSampler
participant SCM as ServerClockManager
participant KTS as KeyframeTrackSampler
participant Mob as Mob
Note over SL,Mob: one server tick
SL->>EAS: invalidateTickCache — before the world border, before the weather
EAS->>EVS: drop the cached value, bump the cache tick id
Mob->>EAS: getValue(MONSTERS_BURN, position)
EAS->>EVS: getValue — is any layer of this attribute positional?
EVS->>EVS: none is — start from the baked base, the default false
EVS->>ATS: applyTimeBased(value, cache tick id)
ATS->>SCM: getTotalTicks(the overworld clock)
ATS->>KTS: sample — which keyframe segment, and how far into it
KTS-->>ATS: the argument — false at 12542, true again at 23460
ATS-->>EVS: BooleanModifier.OR applied to the value
EVS->>EVS: weather adds a layer for nine attributes only, and this is not one
EVS-->>Mob: sanitizeValue — false, and cached for the rest of the tick
```
Read the arrows as decisions. `EnvironmentAttributeSystem.invalidateTickCache`
computes nothing: it drops each sampler's cached value and bumps a counter,
and that counter is the identity every downstream sampler compares against.
`AttributeTrackSampler.applyTimeBased` keeps a one-entry cache of the sampled
*argument* and reuses it for every reader arriving with the same tick id, so
a thousand mobs asking `EnvironmentAttributes.MONSTERS_BURN` cost one
keyframe sample between them.
The step that reads oddly is the fifth. `EnvironmentAttributes.MONSTERS_BURN`
is a positional attribute — everything is, unless a builder says
`EnvironmentAttribute.Builder.notPositional` — and yet its stack is one layer
deep, because in vanilla nothing but the day timeline mentions it: no
dimension type, no biome. `EnvironmentAttributeSystem.ValueSampler` decides
by *layers*, not by the flag, so with no positional layer present the
position is ignored and the whole answer is memoised for the tick. The flag
still governs where it is read: it is what
`EnvironmentAttributeMap.CODEC_ONLY_POSITIONAL` checks, what makes
`EnvironmentAttributeCheck` and `EnvironmentAttributeValue` declare
`LootContextParams.ORIGIN` a required parameter, and what makes
`EnvironmentAttributeSystem.getDimensionValue` throw in a development build
if asked for a positional attribute at all. Three call sites name an attribute and read it
that positionless way: `Level.updateSkyBrightness` for
`EnvironmentAttributes.SKY_LIGHT_LEVEL`, and `LavaFluid.isFastLava` and
`Entity` for `EnvironmentAttributes.FAST_LAVA` — the only two attributes
built `EnvironmentAttribute.Builder.notPositional`, and the pair that decides
[how fast lava flows](fluids.md). A fourth site names none:
`EnvironmentAttributeReader` sends any non-positional attribute down this road
when a loot context asks for one.
`KeyframeTrackSampler.sample` is where the period matters: for a periodic
track it bakes two extra segments, last keyframe to first on either side of
the loop, so a value interpolates *across the wrap* — tick 0, which on this
clock is dawn — instead of snapping, and it reduces the clock's total ticks
with a floor-mod before choosing one. `EasingType` supplies the curve, and
the day timeline's sun, moon and star angles share one symmetric cubic
Bézier whose two keyframes both sit at tick 6000, so the baked segment runs
noon to noon. The sun therefore turns slowest at its zenith — two thirds of
the linear rate — and fastest at midnight, at about six fifths of it. That is
why a Minecraft day is not two equal halves: the sun spends roughly 13,560
ticks above the horizon against 10,440 below.
### The same value on the client
```mermaid
sequenceDiagram
participant Camera as Camera
participant EAP as EnvironmentAttributeProbe
participant GS as GaussianSampler
participant SAI as SpatialAttributeInterpolator
participant EAS as EnvironmentAttributeSystem
participant SR as SkyRenderer
Note over Camera,SR: one client tick
Camera->>EAP: tick(level, position) — once per client tick
EAP->>GS: sample around the camera
GS->>SAI: accumulate(weight, that biome's attributes) — 216 times
Note over Camera,SR: between ticks, once per frame
SR->>EAP: getValue(SKY_COLOR, partialTicks)
EAP->>EAS: getValue(attribute, position, interpolator)
EAS->>SAI: applyAttributeLayer — weighted blend of every biome in range
EAP-->>SR: partialTickLerp between last tick's value and this one
```
The client resolves the *same* stack from the *same* data — it is never sent
a resolved value. What it adds is two kinds of smoothing the server never
does. In space, `EnvironmentAttributeProbe.tick` prunes, clears, then runs
`GaussianSampler.sample` over a 6×6×6 neighbourhood of quart-resolution biome
cells — 216 samples, a 1-4-6-4-1 kernel lerped by the sub-cell offset on each
axis — accumulating weights into a `SpatialAttributeInterpolator`, whose
`SpatialAttributeInterpolator.applyAttributeLayer` applies each contributing
biome's modifier to the base value and lerps the *results* together by
weight. That is only for the 21 attributes flagged
`EnvironmentAttribute.isSpatiallyInterpolated`; anything else takes the
single biome under the position. In time, each probed value keeps last tick's
answer beside this tick's and returns `AttributeType.partialTickLerp` between
them — and prunes itself, dropping any value nobody read during a tick.
The probe lives on `Camera`, ticked from `Camera.tick` and emptied by
`Camera.reset`, and six consumers go through it:
`SkyRenderer`, `LightmapRenderStateExtractor`, `AtmosphericFogEnvironment`,
`WaterFogEnvironment`, `LevelExtractor` for clouds and `Minecraft` for music.
It is not a wall: the clock item reads *sun_angle* and *moon_phase* off
`ClientLevel.environmentAttributes` directly, and so does `ClientLevel` itself
for ambient particles.
That is why [lightmap, fog and sky](../rendering/lightmap-fog-and-sky.md)
never touches `EnvironmentAttributeSystem` directly.
## Questions players ask
**Why does the nether have no night?** It has no day timeline: the nether's
and the End's `DimensionType.timelines` both resolve to
`TimelineTags.UNIVERSAL` alone. Its environment is constants instead — the
dimension type pins *gameplay/sky_light_level*, *gameplay/fast_lava*,
*gameplay/water_evaporates* and eleven more, which
`EnvironmentAttributeSystem.bakeLayerSampler` folds into a base value that
never changes again.
**Does setting the time in the overworld move the End?** No: each clock keeps
its own `ServerClockManager.ClockInstance`. Every mutator does invalidate the
cache on *every* level at once, though — `ServerClockManager` walks
`MinecraftServer.getAllLevels` on each change, because a time jump must not
leave half a tick of stale sky behind.
**Why do the server and the client disagree by a tick?** They invalidate at
opposite ends of it. `ServerLevel.tick` calls
`EnvironmentAttributeSystem.invalidateTickCache` before the world border and
the weather, then runs `Level.updateSkyBrightness` later in the same method,
once sleeping and weather have resolved; `ClientLevel.tick` does the reverse,
`Level.updateSkyBrightness` first and invalidation last, so the client's
sky-darken value comes from the previous tick's clock.
**Where did the villager schedule go?** Into `Timelines.VILLAGER_SCHEDULE`.
`Brain.setSchedule` takes an `EnvironmentAttribute` of `Activity` and
`Villager` is the only caller — adults get
`EnvironmentAttributes.VILLAGER_ACTIVITY`, babies
`EnvironmentAttributes.BABY_VILLAGER_ACTIVITY`, two tracks on one timeline —
and `Brain.updateActivityFromSchedule` reads it at the villager's own
position, only when more than 20 game ticks have passed since it last looked.
Where that activity then sends a villager is in
[points of interest](points-of-interest.md).
**Why do pillager patrols not show up on day one?** `Timelines.EARLY_GAME`
has no period, and a timeline without one is not a cycle: its track runs once
against total ticks and holds its last value forever. Its single modifier
track *and*s `EnvironmentAttributes.CAN_PILLAGER_PATROL_SPAWN` with false
until tick 120000 — a hundred minutes — and with true after.
## Where to look
`EnvironmentAttributes` · `EnvironmentAttribute.Builder` · `AttributeTypes` ·
`EnvironmentAttributeMap.Entry` · `EnvironmentAttributeSystem.Builder` ·
`EnvironmentAttributeSystem.bakeLayerSampler` ·
`EnvironmentAttributeSystem.invalidateTickCache` ·
`WeatherAttributes.addBuiltinLayers` · `Timelines` · `Timeline.createTrackSampler` ·
`AttributeTrackSampler.applyTimeBased` · `KeyframeTrackSampler.sample` ·
`ServerClockManager.tick` · `ClientClockManager.handleUpdates` ·
`EnvironmentAttributeProbe.tick` · `SpatialAttributeInterpolator.applyAttributeLayer` ·
`GaussianSampler.sample` · `TimeCommand`
---
*Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes `tools/verify_names.py`.*
---
# Chunk anatomy
> Verified against **Minecraft 26.2** · Part IV · One block is placed, and the write travels down through the chunk until it lands in four bits of one long.
You place a single block of deepslate at y −40, in a section that until now
held nothing but stone and air. The click ends in `LevelChunk.setBlockState`,
and the write goes down four objects: a `LevelChunkSection` sixteen blocks
tall, a `PalettedContainer` of 4,096 block states, a palette that turns the
state into a small integer, and a `SimpleBitStorage` that puts that integer
into some fixed number of bits of one long. Everything else in this part
*moves* chunks — tickets load them, the pyramid generates them, the light
engine walks them, the region file stores them. This is the page that looks
inside, and it is the vocabulary the rest of the part spends. Begin with the
part that surprises people: **a section holding two distinct block states
costs exactly what one holding sixteen costs — four bits an entry, 256
longs, on disk as well as in memory — and the block that makes it seventeen
re-encodes all 4,096 entries into a wider storage before it can be written.**
## The cast
| class | what it decides | thread |
|---|---|---|
| `ChunkAccess` | everything a chunk has whatever its shape: position, height, sections, heightmaps, block entities, structures, the two volatile flags | abstract — whichever thread owns the shape below |
| `ProtoChunk` | a chunk under construction: status, carving mask, entities as NBT, the light engine it reports to | written on the worker pool, one writer at a time |
| `LevelChunk` | a chunk that is part of a `Level`: block entities, tickers, tick containers, the full-status supplier | the server thread — on the client, the client's main thread |
| `ImposterProtoChunk` | what a still-generating neighbour sees when the chunk it asked for is already live | the server thread |
| `LevelChunkSection` | 16×16×16: two palette containers and four counters that let a whole section be skipped | whichever thread holds its permit |
| `PalettedContainer` | the mapping from 4,096 (or 64) entries to values, and when to widen it | one writer at a time, enforced by `ThreadingDetector`; reads are lock-free |
| `Strategy` | which palette and which bit width each entry count deserves, for block states and for biomes | immutable, shared by every container in the level |
| `Heightmap` | the top of each of 256 columns, for one definition of *top* | with the chunk that owns it |
## The four shapes a chunk takes
```mermaid
flowchart LR
NEW["nothing on disk: ChunkMap.createEmptyChunk"] --> PC
DISK["the region file"] -->|"SerializableChunkData.parse, on the worker pool"| SCD["SerializableChunkData"]
SCD -->|"read, on the server thread, stored status below full"| PC
SCD -->|"read, stored status full: a LevelChunk is built, then wrapped"| IPC
PC["ProtoChunk: generation state, written on the worker pool"] -->|"ChunkStatusTasks.full, on the server thread"| LC
LC["LevelChunk: the live chunk, owned by the server thread"] -->|"GenerationChunkHolder.replaceProtoChunk"| IPC
IPC["ImposterProtoChunk: a ProtoChunk-shaped view over a LevelChunk"] -->|"ImposterProtoChunk.getWrapped"| LC
MISS["a lookup that finds nothing there"] --> ELC["EmptyLevelChunk: void air, and a LevelChunk itself"]
```
`ChunkAccess` is the abstract chunk and has exactly two direct concrete
lines — `ProtoChunk` (`ChunkType.PROTOCHUNK`) and `LevelChunk`
(`ChunkType.LEVELCHUNK`) — with `ImposterProtoChunk` a subclass of the first
and `EmptyLevelChunk` of the second. Nothing else extends it.
Every one of them carries the same core. `ChunkAccess.chunkPos` says where
it is, and a `LevelHeightAccessor` says how tall: `LevelHeightAccessor.getMinY`
and `LevelHeightAccessor.getHeight` are the only two facts about height there
are, and the overworld's −64 and 384 give `LevelHeightAccessor.getSectionsCount`
of **24**, section Y −4 through 19. Beside them sit the heightmaps, the block
entities in two maps (`ChunkAccess.blockEntities` live, `ChunkAccess.pendingBlockEntities`
still NBT, `ChunkAccess.getBlockEntitiesPos` the union),
`ChunkAccess.structureStarts` and `ChunkAccess.structuresRefences` (Mojang's
spelling), the per-section `ChunkAccess.postProcessing` offsets to revisit
after load (`ProtoChunk.packOffsetCoordinates` packs four bits each of x, y
and z into a short), `ChunkAccess.inhabitedTime` behind local difficulty
([the level tick](../server/server-level-tick.md)), `ChunkAccess.upgradeData`
and the nullable `ChunkAccess.blendingData` whose presence *is*
`ChunkAccess.isOldNoiseGeneration` ([blending](../worldgen/blending.md)),
and two *volatile* flags —
`ChunkAccess.unsaved`, whose test-and-clear `ChunkAccess.tryMarkSaved` the
saver uses, and `ChunkAccess.isLightCorrect`, saved as *isLightOn*. It is
also three interfaces at once — `LightChunk`, which is what the light engine
reads through `LightChunk.findBlockLightSources` and
`LightChunk.getSkyLightSources`, plus `StructureAccess` and
`BiomeManager.NoiseBiomeSource` — and `ChunkAccess.getPersistedStatus` is
the `ChunkStatus` that goes to disk, with `ChunkAccess.getHighestGeneratedStatus`
folding in `BelowZeroRetrogen.targetStatus` for a chunk still being deepened.
A `ProtoChunk` adds what only generation needs: a volatile
`ProtoChunk.status` (`ProtoChunk.setPersistedStatus` also retires a finished
`BelowZeroRetrogen`), a `ProtoChunk.lightEngine` from
`ProtoChunk.setLightEngine` that it reports to only once the status
`ChunkStatus.isOrAfter` `ChunkStatus.INITIALIZE_LIGHT`, its entities as a
list of `CompoundTag` (`ProtoChunk.addEntity` serialises on the spot), a
`ProtoChunk.carvingMask`, and `ProtoChunkTicks` that
`ProtoChunk.unpackBlockTicks` turns into `LevelChunkTicks` on promotion
([scheduled ticks](scheduled-ticks.md)). The pool that fills all of that in
is [the generation pipeline](chunk-generation-pipeline.md). Ask it for a
biome before `ChunkStatus.BIOMES` and `ProtoChunk.getNoiseBiome` throws
*Asking for biomes before we have biomes*.
A `LevelChunk` adds what only a live chunk needs: `LevelChunk.level`,
`LevelChunk.setLoaded`, a supplier of `FullChunkStatus` the holder owns
([tickets](tickets-and-loading.md)), two `LevelChunkTicks` that
`LevelChunk.registerTickContainerInLevel` attaches to the level's queues and
`LevelChunk.unregisterTickContainerFromLevel` detaches, the ticker map, a
one-shot `LevelChunk.postLoad` processor that `LevelChunk.runPostLoad`
fires, the per-section `LevelChunk.gameEventListenerRegistrySections` — an
`EuclideanGameEventListenerRegistry` each, built on demand and only on a
server ([game events](game-events-and-vibrations.md)) — and a
`LevelChunk.unsavedListener` that `LevelChunk.markUnsaved` fires **only on
the false-to-true edge**, which is how the server's dirty set learns of a
change without scanning. Its `LevelChunk.getPersistedStatus` is always
`ChunkStatus.FULL`.
`ImposterProtoChunk` exists because a neighbour still generating asks the
holder for "the chunk at status X" and must be handed something
`ProtoChunk`-typed even when that chunk is already live. Reads delegate to
`ImposterProtoChunk.getWrapped`; writes are dropped unless *allowWrites*,
which both of the two places that construct one pass as **false**, so in
26.2 every write to an imposter is dropped — heightmaps, structure starts and
references and block-entity NBT unconditionally, and the rest for want of the
flag. `ImposterProtoChunk.getSections` hands back the wrapped
chunk's array unconditionally — only the single-section
`ImposterProtoChunk.getSection` is gated — while
`ImposterProtoChunk.markUnsaved` and `ImposterProtoChunk.setLightCorrect`
always pass through and `ImposterProtoChunk.canBeSerialized` is false,
because the `LevelChunk` under it is what gets saved. Its
`ImposterProtoChunk.fixType` maps a request for a *_WG* heightmap onto the
live one, but only inside `ImposterProtoChunk.getHeight`: asking it to
*create* a *_WG* heightmap creates a real one on the live chunk.
`EmptyLevelChunk` is the other direction: `Blocks.VOID_AIR` everywhere,
`EmptyLevelChunk.isEmpty` true where `LevelChunk.isEmpty` is false, one
fixed biome, and `EmptyLevelChunk.getFullStatus` a flat
`FullChunkStatus.FULL`. `ClientChunkCache.emptyChunk` is one shared instance
handed out for *any* client miss, and only when the caller asked to load or
generate — otherwise the miss returns null; `PathNavigationRegion` builds
its own for whatever a mob's pathfinder cannot see. The real client chunks
live in `ClientChunkCache.Storage`, an `AtomicReferenceArray` ring whose
`ClientChunkCache.Storage.onSectionEmptinessChanged` and its double-buffered
added and removed sets are the renderer's feed of which sections exist.
## Sections and their four counters
```mermaid
flowchart TD
LC["LevelChunk: one 16 by 16 column of the whole build height"] --> ARR["ChunkAccess.sections: an array of LevelChunkSection, 24 in the overworld, never a null slot"]
LC --> HM["four Heightmaps: 256 entries of 9 bits each"]
ARR --> ST["LevelChunkSection.states: PalettedContainer of BlockState, 4096 entries"]
ARR --> BIO["LevelChunkSection.biomes: PalettedContainerRO of Biome, 64 entries, one per 4 by 4 by 4 quart"]
ARR --> CNT["four shorts: nonEmptyBlockCount, fluidCount, tickingBlockCount, tickingFluidCount"]
ST --> DATA["PalettedContainer.Data: one volatile record of configuration, palette and storage"]
BIO --> DATA
DATA --> PAL["the palette, on the block-state ladder: one value at 0 bits, then anything from 2 to 16 values at 4 bits, 17 to 256 hashed, then the registry itself. Biomes climb a shorter ladder"]
DATA --> BST["the BitStorage: ZeroBitStorage, or a SimpleBitStorage of 256 longs at 4 bits, 342 at 5, 512 at 8"]
```
The array never has a hole: `ChunkAccess.replaceMissingSections` runs in the
constructor and fills every empty slot with a fresh all-air section from the
level's `PalettedContainerFactory`, so nothing that walks sections checks
for null.
The four counters are what make a section cheap to skip.
`LevelChunkSection.setBlockState` adjusts all four from the outgoing and
incoming state on every single write — `LevelChunkSection.nonEmptyBlockCount`
(zero is `LevelChunkSection.hasOnlyAir`, which is also how
`LevelChunk.getBlockState` answers air without touching a palette),
`LevelChunkSection.fluidCount`, `LevelChunkSection.tickingBlockCount` and
`LevelChunkSection.tickingFluidCount` — and `LevelChunkSection.isRandomlyTicking`
is the *or* of the last two. That one boolean lets `ServerLevel` skip a
section of solid stone without looking at any of its 4,096 blocks. Only a
load from disk recounts: `LevelChunkSection.recalcBlockCounts` runs from the
two-container constructor, whose only caller is `SerializableChunkData`.
Biomes share the section but are coarse and read-only.
Two bits per axis, 64 entries of 4×4×4 blocks each — the number
`LevelChunkSection.BIOME_CONTAINER_BITS` names, though the 2 that matters is
the literal in `Strategy.createForBiomes` and no reader of the constant
survives the decompile. The field is a `PalettedContainerRO` and the
*published* one is never mutated: `LevelChunkSection.fillBiomesFromNoise`,
`LevelChunkSection.read` and `LevelChunkSection.readBiomes` each build a
replacement through `PalettedContainerRO.recreate`, fill it, and swap the
reference. The block-state container is the only one anything writes to in
place.
Two different copies leave a section. The saver takes
`LevelChunkSection.copy`, a deep copy of both containers and all four
counters, on the server thread inside `SerializableChunkData.copyOf` — the
IO lane never sees a live section, and even the NBT encoding of the copy
runs on the background pool ([chunk storage](chunk-storage.md)). The client
mesher takes something cheaper: a `SectionCopy` takes `PalettedContainer.copy`
of the block-state container alone (nothing at all when the section is air)
plus an immutable snapshot of the chunk's block-entity map. A worker that
means to write instead brackets its work with `LevelChunkSection.acquire`
and `LevelChunkSection.release`.
## The palette and the ladder it climbs
A `PalettedContainer`'s whole state is one *volatile* record,
`PalettedContainer.Data`, holding a `Configuration`, a `Palette` and a
`BitStorage`. `PalettedContainer.get` reads the record once into a local and
takes no lock, because a resize swaps in a whole new record rather than
editing the old one. `PalettedContainer.read` from the wire is the exception
that proves it: `PalettedContainer.createOrReuseData` hands back the *existing*
record whenever the incoming bit count wants the same configuration, and the
palette and the long array are then overwritten in place — which is the
ordinary case on a client, where `ClientChunkCache` reuses the live
`LevelChunk`. Which record a given entry count deserves is
decided by a top-level `Strategy` — no longer nested inside the container —
whose `Strategy.createForBlockStates` and `Strategy.createForBiomes` are
called once per level by `PalettedContainerFactory.create` over
`Block.BLOCK_STATE_REGISTRY` and the biome registry
([registries](../foundations/identifiers-and-registries.md)), which is why the
global palette's width is a runtime number and not a constant.
`Strategy.getConfigurationForBitCount` is the ladder, and block states and
biomes climb different ones:
| distinct values (bits needed) | block states | biomes |
|---|---|---|
| 1 (0 bits) | `Strategy.ZERO_BITS` → `SingleValuePalette` + `ZeroBitStorage` | the same |
| 2 … 8 (1–3 bits) | `Strategy.FOUR_BITS_LINEAR` → `LinearPalette`, **already 4 bits** | `Strategy.ONE_BIT_LINEAR` / `Strategy.TWO_BITS_LINEAR` / `Strategy.THREE_BITS_LINEAR` → `LinearPalette` at its own width |
| 9 … 16 (4 bits) | `Strategy.FOUR_BITS_LINEAR`, **still 4 bits** | `Configuration.Global` already |
| 17 … 256 (5–8 bits) | `Strategy.FIVE_BITS_HASHMAP` … `Strategy.EIGHT_BITS_HASHMAP` → `HashMapPalette` | `Configuration.Global` — there is no hashed tier for biomes |
| more | `Configuration.Global` → `GlobalPalette`, the registry's own `IdMap` | `Configuration.Global` |
`SingleValuePalette` holds one value and asks for width 1 the moment a
second arrives — which for block states means jumping straight to the 4-bit
rung. `LinearPalette` is a flat array of *2^bits* slots scanned by identity,
`HashMapPalette` a `CrudeIncrementalIntIdentityHashBiMap`, and
`GlobalPalette` writes nothing on the wire, maps an unknown value to id 0
and answers `Palette.maybeHas` with an unconditional yes. Each of them calls
`PaletteResize.onResize` when it fills, and the container *is* its own
`PaletteResize`: `PalettedContainer.onResize` builds the next record,
`PalettedContainer.Data.copyFrom` walks every entry of the old storage
through the old palette into the new one, the record is published, and only
then is the value that triggered the growth added — under
`PaletteResize.noResizeExpected`, which throws if a second growth were
somehow needed.
`SimpleBitStorage` never lets an entry straddle a long: its
`SimpleBitStorage.valuesPerLong` is 64 divided by the width, so 4,096
entries are 256 longs at four bits, 342 at five and 512 at eight, and the
cell index comes from a multiply-shift table (`SimpleBitStorage.MAGIC`)
rather than a division. `ZeroBitStorage` answers 0 for everything and shares
one empty `ZeroBitStorage.RAW` array. An array of the wrong length raises
`SimpleBitStorage.InitializationException`, which `PalettedContainer.unpack`
turns into a `DataResult` error instead of a crash.
### What packing actually buys
The two serialised forms differ. `PalettedContainer.write` is the wire: a
bits byte, the palette, a fixed-size long array at exactly the in-memory
width. `PalettedContainer.pack` is the disk (the *palette* and optional
*data* fields of a `PalettedContainerRO.PackedData`, behind
`PalettedContainer.codecRW` and
`PalettedContainer.codecRO` — [codecs](../foundations/codecs-nbt-json.md)),
and it re-encodes into a fresh `HashMapPalette` before asking
`Strategy.getConfigurationForPaletteSize` for the width — **the same ladder
memory climbs**. Packing therefore buys a smaller palette, not narrower
entries: unreferenced entries are dropped, which can demote a container a
whole rung, and a `Configuration.Global` container shrinks from
`Configuration.bitsInMemory` to `Configuration.bitsInStorage`.
`PalettedContainer.unpack` re-encodes on the way back only for
`Configuration.Global`, whose `Configuration.alwaysRepack` is true — every
`Configuration.Simple` rung reports one width for both, so its long array is
adopted exactly as it lies on disk.
## The six heightmaps
A `Heightmap` is 256 entries — one per column, indexed *x + z·16* — in a
`SimpleBitStorage` whose width is `Mth.ceillog2` of height + 1, so **9 bits**
for a 384-tall world, stored relative to the minimum Y.
`Heightmap.getFirstAvailable` is the first free Y and `Heightmap.getHighestTaken`
the one below it. `Heightmap.primeHeightmaps` fills several types in a
single top-down column scan (starting from the deprecated-for-removal
`ChunkAccess.getHighestSectionPosition`) into maps that
`ChunkAccess.getOrCreateHeightmapUnprimed` makes on demand. `Heightmap.update`
is the incremental path, raising the height when an opaque block is placed
at or above it and rescanning downward only when the block that turned
transparent was the top one.
| type | *opaque* means | usage | saved | sent |
|---|---|---|---|---|
| `Heightmap.Types.WORLD_SURFACE_WG` | not air | `Heightmap.Usage.WORLDGEN` | proto only | no |
| `Heightmap.Types.WORLD_SURFACE` | not air | `Heightmap.Usage.CLIENT` | yes | yes |
| `Heightmap.Types.OCEAN_FLOOR_WG` | blocks motion | `Heightmap.Usage.WORLDGEN` | proto only | no |
| `Heightmap.Types.OCEAN_FLOOR` | blocks motion | `Heightmap.Usage.LIVE_WORLD` | yes | **no** |
| `Heightmap.Types.MOTION_BLOCKING` | blocks motion or holds fluid | `Heightmap.Usage.CLIENT` | yes | yes |
| `Heightmap.Types.MOTION_BLOCKING_NO_LEAVES` | the same, but not a `LeavesBlock` | `Heightmap.Usage.CLIENT` | yes | yes |
Which of the six a chunk carries follows its status:
`ChunkStatus.heightmapsAfter` is the two *_WG* maps through
`ChunkStatus.SURFACE` and `ChunkStatus.FINAL_HEIGHTMAPS` — the other four —
from `ChunkStatus.CARVERS` on, and a `LevelChunk` is constructed with
exactly those four. A `ProtoChunk` primes any of its status's maps that are
missing the first time a block is written. What is *saved*, though, is not
`Heightmap.Types.keepAfterWorldgen`: the saver writes whatever the chunk's
**persisted** status names, so a proto chunk stored below
`ChunkStatus.CARVERS` does save its two *_WG* maps. Separately and privately,
`ChunkAccess.skyLightSources` is a *second* 256-entry bit storage — a
`ChunkSkyLightSources` — that only the sky-light engine reads
([lighting](lighting.md)).
## What placing a block actually does
`LevelChunk.setBlockState` is the one write path into a live chunk, and its
order matters more than any single step in it:
| in order | what happens | when it is skipped |
|---|---|---|
| 1 | the section is fetched and its emptiness remembered | air into an all-air section returns null immediately |
| 2 | `LevelChunkSection.setBlockState` writes the palette entry and moves all four counters | — |
| 3 | the old state is compared to the new | identical state returns null, and nothing below runs |
| 4 | all four heightmaps take `Heightmap.update` | — |
| 5 | if the section's emptiness flipped: `LevelLightEngine.updateSectionStatus` and `ChunkSource.onSectionEmptinessChanged` | when it did not flip |
| 6 | if `LightEngine.hasDifferentLightProperties`: `ChunkSkyLightSources.update`, then `LevelLightEngine.checkBlock` | when opacity and emission are unchanged **and** neither state uses a shape for light occlusion |
| 7 | the old block entity is dropped, preceded on the server by `BlockEntity.preRemoveSideEffects` | when the block did not change, had no block entity, or `BlockBehaviour.BlockStateBase.shouldChangedStateKeepBlockEntity` — the side effects alone are skipped on the client and under `Block.UPDATE_SKIP_BLOCK_ENTITY_SIDEEFFECTS` |
| 8 | `BlockBehaviour.BlockStateBase.affectNeighborsAfterRemoval` | when the block did not change and the new one is no `BaseRailBlock`, off the server, or without `Block.UPDATE_NEIGHBORS` and not moved by a piston |
| 9 | the section is re-read — if step 8 changed the block again, the call returns null | — |
| 10 | `BlockBehaviour.BlockStateBase.onPlace` | on the client, or under `Block.UPDATE_SKIP_ON_PLACE` |
| 11 | the new block entity is created or re-validated, and its ticker rebound | when the new state has no block entity |
| 12 | `LevelChunk.markUnsaved` | — |
Two steps there are easy to misread. Step 8 is a genuine neighbour side
effect *inside* the chunk write, but it is the removed block's own
clean-up — shape updates and redstone notifications belong to
`Level.setBlock`, after the chunk returns
([blocks and states](../blocks/blocks-and-states.md)). Step 9 exists because
step 8 runs arbitrary block code that may write the same position again, and
`LevelChunk.setBlockState` will not claim a placement it no longer owns.
`ProtoChunk.setBlockState` is the same idea with everything live stripped
out: section write, light only past `ChunkStatus.INITIALIZE_LIGHT`, the
status's heightmaps updated (primed first if absent), and no block entity,
no `BlockBehaviour.BlockStateBase.onPlace`, no neighbours at all.
### The double indirection behind step 11
`LevelChunk` holds every block-entity ticker in the world's hot loop, and it
does so at one remove.
`LevelChunk.addAndRegisterBlockEntity` sets the entity, registers its
game-event listener and asks for its ticker; a
`LevelChunk.BoundTickingBlockEntity` binds the entity to its
`BlockEntityTicker` and gates every tick on `LevelChunk.isTicking` (inside
the world border, at `FullChunkStatus.BLOCK_TICKING` or beyond, entities
loaded). That sits inside a `LevelChunk.RebindableTickingBlockEntityWrapper`
held in `LevelChunk.tickersInLevel`, so `Level.blockEntityTickers` keeps one
stable handle per position for the life of the chunk and removal is only a
`LevelChunk.RebindableTickingBlockEntityWrapper.rebind` to
`LevelChunk.NULL_TICKER`, whose `TickingBlockEntity.isRemoved` is true and
lets the level's list prune itself. When the chunk first goes live,
`LevelChunk.postProcessGeneration` replays the post-processing offsets,
promotes every pending block entity and applies `UpgradeData.upgrade`.
## Questions players ask
**Why do two threads writing one section crash the game rather than block?**
Because `PalettedContainer.threadingDetector` is a detector, not a mutex.
`ThreadingDetector.checkAndLock` tries a one-permit semaphore and, on
failure, records itself as the loser and then blocks. It is the **winner**
that notices, in `ThreadingDetector.checkAndUnlock`: it builds
`ThreadingDetector.makeThreadingException` — *Accessing PalettedContainer
from multiple threads*, with both stack traces — and throws it, and the
loser re-throws the same report the instant it acquires the permit. Both
threads die, deliberately: an interleaved section write would be a corrupt
world rather than a crash. Exactly one thread writes a section at a time —
the server thread for a live chunk, and on the worker pool whoever holds
`LevelChunkSection.acquire`, either `NoiseBasedChunkGenerator`, which holds every
section across its noise range, or the `BulkSectionAccess` that `OreFeature` — its only user — holds over
every section it touches until it closes. Those hold the permit already, so they
write through the unchecked five-argument `LevelChunkSection.setBlockState`
and `PalettedContainer.getAndSetUnchecked`.
**Why are a client chunk's ticking counters zero?** Because
`LevelChunkSection.write` carries only two of the four shorts, the
non-empty-block and fluid counts, and `LevelChunkSection.read` takes exactly
those two and never recounts. A client section therefore starts with
`LevelChunkSection.tickingBlockCount` and
`LevelChunkSection.tickingFluidCount` at zero and only ever counts what has
changed since the chunk arrived. Nothing notices: the only reader of
`LevelChunkSection.isRandomlyTicking` is `ServerLevel`, and the client runs
no random ticks.
**Why does the proto chunk keep working after the level chunk exists?**
Because the two share the sections but not the array. `ChunkAccess` always
allocates its own array and copies the references in, so promotion leaves
two chunks holding two arrays over one set of `LevelChunkSection` objects —
writing through either is writing the same blocks. That is also what makes
the `ImposterProtoChunk` honest: it is a third handle on the same sections.
**Why does a chest in a freshly loaded chunk not exist yet?** Because it is
still a `CompoundTag` in `ChunkAccess.pendingBlockEntities`
([block entities](../blocks/block-entities.md)). Any call to
`LevelChunk.getBlockEntity` promotes it through
`LevelChunk.promotePendingBlockEntity` on the first touch, whatever the
`LevelChunk.EntityCreationType` asked for; `LevelChunk.postProcessGeneration`
and `LevelChunk.registerAllBlockEntitiesAfterLevelLoad` promote the rest in
bulk when the chunk goes live. Of the three creation types only
`LevelChunk.EntityCreationType.IMMEDIATE` and
`LevelChunk.EntityCreationType.CHECK` have callers left in the game —
`LevelChunk.EntityCreationType.QUEUED` has none.
**Why can a search skip a whole section without reading it?** Because
`LevelChunkSection.maybeHas` puts the predicate to the palette alone, so
`ChunkAccess.findBlocks` can rule out 4,096 blocks with a handful of
comparisons — unless the section is on the global palette, whose answer is
always *maybe*.
**What does the client actually receive?** `ClientboundLevelChunkWithLightPacket`,
whose `ClientboundLevelChunkPacketData` carries the heightmaps for which
`Heightmap.Types.sendToClient` is true (three of the six), one buffer
holding *every* section's `LevelChunkSection.write` — empty ones included —
and the block-entity update tags. The writer pre-sizes that buffer from the
sum of `LevelChunkSection.getSerializedSize` and throws if
`ClientboundLevelChunkPacketData.extractChunkData` does not fill it to the
byte, and the reader refuses anything over two megabytes. Light rides beside
it in `ClientboundLightUpdatePacketData`. The client applies the lot through
`ClientPacketListener.updateLevelChunk` → `ClientChunkCache.replaceWithPacketData`
→ `LevelChunk.replaceWithPacketData`, which clears the block entities, gives
each section `LevelChunkSection.read`, installs the raw heightmaps with
`ChunkAccess.setHeightmap` and rebuilds the sky-light sources. Biome-only
refreshes come later as `ClientboundChunksBiomesPacket` →
`LevelChunk.replaceBiomes`, and the block-entity tags travel as
`ClientboundLevelChunkPacketData.BlockEntityInfo`.
## Where to look
`ChunkAccess` · `LevelChunk.setBlockState` · `LevelChunk.getBlockEntity` ·
`ProtoChunk.setBlockState` · `ImposterProtoChunk` · `ChunkStatusTasks.full` ·
`LevelChunkSection.setBlockState` · `LevelChunkSection.write` ·
`PalettedContainer.onResize` · `PalettedContainer.pack` ·
`PalettedContainer.unpack` · `Strategy.createForBlockStates` ·
`Configuration.Global` · `SimpleBitStorage` · `ThreadingDetector.checkAndLock` ·
`BulkSectionAccess` · `Heightmap.Types` · `ChunkStatus.heightmapsAfter` ·
`ClientChunkCache.Storage` · `ClientboundLevelChunkPacketData.extractChunkData` ·
the [class index](../../reference/class-index.md) for every field no page names
---
*Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes `tools/verify_names.py`.*
---
# Tickets and loading
> Verified against **Minecraft 26.2** · Part IV · A player walks one block east across a chunk boundary, and a column of chunks thirteen past the edge of view is asked for.
A player standing at the eastern edge of a chunk takes one step. On the
server, `ChunkMap.move` notices that the player's section changed, and
before the tick is out a column of twenty-one chunks to the east has been
asked for. The nearest of them will be generated, lit, sent and alive within
a second or two. The furthest, thirteen chunks past the ticket that
asked for it, will exist only as a `ChunkHolder` at level 44, allowed no
further than `ChunkStatus.STRUCTURE_STARTS` and never becoming a
`LevelChunk` at all. Nothing in that machinery ever asks for a
chunk *because* it is loaded — even `ChunkLevel.isLoaded` is a question
about a number. Everything asks for a chunk at a *level*, and the ticket
system decides what the level means. That is the whole design, and it has
one consequence a player can see: **there are two graphs reading one ticket
store, and they answer different questions** — so a chunk can be
`FullChunkStatus.ENTITY_TICKING` by every measure the holder knows and tick
nothing, because render distance is how far you can see and simulation
distance is how far the world is alive.
## The cast
| class | what it decides | thread |
|---|---|---|
| `TicketStorage` | which tickets exist on which chunk, and which survive a restart — it is the *chunk_tickets* `SavedData` | Server |
| `DistanceManager` | owns both graphs, the two player-radius trackers and the four-slot throttle | Server |
| `LoadingChunkTracker` · `SimulationChunkTracker` | the two graphs: `ChunkTracker`s over `DynamicGraphMinFixedPoint`, one level higher per ring | Server |
| `ChunkLevel` | the number line — which level means which `FullChunkStatus` and which generation status | static |
| `ChunkHolder` | one chunk's level and its three futures | Server; futures complete on workers, confirmations hop back |
| `ChunkMap` | the holders (an updating map and a visible clone), each player's `ChunkTrackingView`, unloads | Server; workers read the visible map |
| `ServerChunkCache` | the level's `ChunkSource`: the tick slot, the synchronous `ServerChunkCache.getChunk`, and a `BlockableEventLoop` pinned to the server thread | Server |
| `PlayerChunkSender` | the per-player batches, paced by the client's acknowledgements | Server |
## From a ticket to a future
```mermaid
flowchart TD
SRC["a ticket source: a player, a portal, a pearl, the dragon, /forceload, a synchronous getChunk"] --> TS["TicketStorage.addTicket"]
TS -- "FLAG_LOADING" --> LCT["LoadingChunkTracker, levels 0 to 45"]
TS -- "FLAG_SIMULATION" --> SCT["SimulationChunkTracker, levels 0 to 33"]
LCT --> FLOOD["flood: each ring one level higher than the last, every chunk at the minimum of what reaches it"]
SCT --> FLOOD2["the same flood, in its own map"]
FLOOD --> UCS["ChunkMap.updateChunkScheduling: a holder exists at level 44 or below"]
UCS --> UF["ChunkHolder.updateFutures compares ChunkLevel.fullStatus of the old and new level"]
UF --> F33["33 and below: fullChunkFuture, the generation pipeline to FULL"]
UF --> F32["32 and below: tickingChunkFuture, then postProcessGeneration, unpackTicks, send"]
UF --> F31["31 and below: entityTickingChunkFuture, then EntityTickList"]
FLOOD2 --> Q["DistanceManager.inBlockTickingRange and inEntityTickingRange: does anything here tick"]
```
The figure is the page. A ticket lands on one chunk; each graph the ticket's
flags name floods outward from it; the loading graph's levels decide which
holders exist and what futures they arm; the simulation graph's levels decide
what ticks. The rest of the page is one section per decision on that path.
### What a ticket asks for
A `Ticket` is a `TicketType`, a `Ticket.ticketLevel` and a countdown,
`Ticket.ticksLeft`. Its identity is the pair (type, level): there is no key
object and no owner, and re-adding an identical ticket only
`Ticket.resetTicksLeft`. The type is a record of a timeout and a set of
flags, registered in `BuiltInRegistries.TICKET_TYPE`, and the flags say what
the ticket *does*: `TicketType.FLAG_LOADING` feeds the loading graph,
`TicketType.FLAG_SIMULATION` the simulation graph, `TicketType.FLAG_PERSIST`
writes it to disk, `TicketType.FLAG_KEEP_DIMENSION_ACTIVE` stops the level's
empty-tick countdown and `TicketType.FLAG_CAN_EXPIRE_IF_UNLOADED` lets its
countdown run under a chunk that has no holder yet. There are exactly nine
types, and every reason a chunk is ever loaded is one of them:
| type | timeout | loads | simulates | keeps dimension active | persists | who adds it |
|---|---:|---|---|---|---|---|
| `TicketType.PLAYER_LOADING` | — | ✓ | | | | `DistanceManager.PlayerTicketTracker`, one per chunk in view |
| `TicketType.PLAYER_SIMULATION` | — | | ✓ | ✓ | | `DistanceManager.addPlayer`, the player's own chunk |
| `TicketType.FORCED` | — | ✓ | ✓ | ✓ | ✓ | `/forceload` via `TicketStorage.updateChunkForced` |
| `TicketType.PORTAL` | 300 | ✓ | ✓ | ✓ | ✓ | `Entity` on portal travel, radius 3 |
| `TicketType.ENDER_PEARL` | 40 | ✓ | ✓ | ✓ | | `ServerPlayer`, the pearl's chunk, radius 2 |
| `TicketType.DRAGON` | — | ✓ | ✓ | | | `EnderDragonFight`, radius 9 |
| `TicketType.PLAYER_SPAWN` | 20 | ✓ | | | | `PrepareSpawnTask` during configuration, radius 3 |
| `TicketType.SPAWN_SEARCH` | 1 | ✓ | | | | `PlayerSpawnFinder` |
| `TicketType.UNKNOWN` | 1 | ✓ | | | | a synchronous `ServerChunkCache.getChunk` that may generate, at the level for its target status |
Two things in that table are easy to read past. The ticket that keeps a
dimension alive is the player's *simulation* ticket, not the loading
tickets: `TicketStorage.shouldKeepDimensionActive` feeds
`ServerChunkCache.hasActiveTickets`, which resets `ServerLevel.emptyTime`,
the counter that past 300 makes a dimension skip its entity loop and its
block entities ([the level tick](../server/server-level-tick.md)). And only
two types come back after a restart: `TicketStorage.packTickets` writes the
types that `TicketType.persist`, so forced and portal tickets are in the
dimension's *chunk_tickets* file and everything else evaporates. On
shutdown `TicketStorage.deactivateTicketsOnClosing` parks every ticket
except `TicketType.UNKNOWN` in `TicketStorage.deactivatedTickets`, and
`TicketStorage.activateAllDeactivatedTickets` replays them during
`MinecraftServer.prepareLevels`.
> **For a 1.21-era reader.** There is no *LIGHT*, *PLAYER*, *START* or
> *POST_TELEPORT* ticket, and there is no forced-chunks file: `TicketStorage`
> (in `world/level`, not `server/level`) is the saved data now. The two
> graphs are separate top-level classes, not inner classes of
> `DistanceManager`, and the whole `ChunkHolder` generation half is a
> superclass, `GenerationChunkHolder`.
### The number line
`ChunkLevel` is the scale every ticket is measured on. Its thresholds are
declared and its ceiling is derived. `ChunkLevel.byStatus` gives 31 for
`FullChunkStatus.ENTITY_TICKING`, 32 for `FullChunkStatus.BLOCK_TICKING`
and 33 for `FullChunkStatus.FULL`. Above 33 a chunk is
`FullChunkStatus.INACCESSIBLE` but still *generating*: the FULL step of
`ChunkPyramid.GENERATION_PYRAMID` needs a neighbourhood of eleven
(`ChunkLevel.RADIUS_AROUND_FULL_CHUNK`, computed from the pyramid), so
`ChunkLevel.MAX_LEVEL` is 44 and `ChunkLevel.generationStatus` maps
34 … 44 onto ever-earlier `ChunkStatus`es — but not one per level:
`ChunkStatus.INITIALIZE_LIGHT` at 34, `ChunkStatus.CARVERS` at 35,
`ChunkStatus.BIOMES` at 36, and `ChunkStatus.STRUCTURE_STARTS` for all eight
of 37 … 44. `ChunkStatus.NOISE` is on that list nowhere. Level 45 means no
holder. Change the pyramid and the loading radius changes with it.
**Thirteen** — chunks past a level-31 ticket that get a holder: two rings to
reach level 33, and eleven more because FULL needs that many neighbours
generated.
### Two graphs, one store
Both graphs are `ChunkTracker`s over the same `TicketStorage`, but each
reads only the tickets whose flags name it, and each is asked a different
kind of question:
| the question | who asks it | which graph answers |
|---|---|---|
| does a holder exist, and how far may it generate | `ChunkMap.updateChunkScheduling`, `GenerationChunkHolder.updateHighestAllowedStatus` | loading |
| may this chunk be sent to a player | `ChunkMap.onChunkReadyToSend`, from the level-32 future | loading |
| do its blocks and fluids tick | `ServerLevel.shouldTickBlocksAt` | simulation |
| do its entities tick | the entity loop, through `DistanceManager.inEntityTickingRange` | simulation |
| which chunks does the level walk for random ticks | `ChunkMap.forEachBlockTickingChunk`, a wrapper over `DistanceManager.forEachEntityTickingChunk` | simulation |
| may mobs spawn here | `DistanceManager.naturalSpawnChunkCounter`, feeding `ChunkMap.anyPlayerCloseEnoughForSpawning` | neither: a fixed radius-8 tracker of its own |
The last row is the third radius, and it is the only one no setting moves:
the tracker's radius of 8 is a constant. It is not the only gate, though —
`ChunkMap.collectSpawningChunks` keeps a candidate only if the holder has a
ticking chunk and some non-spectating player is within 128 blocks of it. And the two graphs have different sizes: the loading graph
runs 0 … 45 and the simulation graph 0 … 33, where 33 is not a ticking level
but the tracker's word for *no simulation ticket at all* —
`SimulationChunkTracker.setLevel` drops the entry at 33 and the map answers
33 for anything absent. A `TicketType.PLAYER_LOADING` ticket puts a chunk at level 31 in the
loading graph — `FullChunkStatus.ENTITY_TICKING` by holder status, all three
futures armed — and contributes nothing to the simulation graph, which is
why the far edge of a large render distance is generated, lit, sent and
inert.
## The four statuses
```mermaid
stateDiagram-v2
direction LR
[*] --> INACCESSIBLE : level 44 or below, a holder is made
INACCESSIBLE --> FULL : level 33, fullChunkFuture succeeds
FULL --> BLOCK_TICKING : level 32, tickingChunkFuture succeeds
BLOCK_TICKING --> ENTITY_TICKING : level 31, entityTickingChunkFuture succeeds
ENTITY_TICKING --> BLOCK_TICKING : level above 31, immediate
BLOCK_TICKING --> FULL : level above 32, immediate
FULL --> INACCESSIBLE : level above 33, immediate
INACCESSIBLE --> [*] : level above 44, toDrop then processUnloads
note right of ENTITY_TICKING : promotion waits for a future, demotion does not
```
`ChunkHolder.updateFutures` compares `ChunkLevel.fullStatus` of
`ChunkHolder.oldTicketLevel` (what the futures reflect) with that of
`ChunkHolder.ticketLevel` (what the graph last said). Each threshold crossed
upward arms one future — `ChunkMap.prepareAccessibleChunk` at 33,
`ChunkMap.prepareTickingChunk` at 32, `ChunkMap.prepareEntityTickingChunk`
at 31 — and a chunk that goes from 45 to 31 in one update arms all three at
once. Every one is wrapped by `ChunkHolder.scheduleFullChunkPromotion`, so
that success fires `ChunkMap.onFullChunkStatusChange` on the main thread,
and chained into `ChunkHolder.addSaveDependency`, so the chunk cannot be
saved or unloaded mid-promotion. Under the hood each future is a
`GenerationChunkHolder.scheduleChunkGenerationTask` — the
[generation pipeline](chunk-generation-pipeline.md).
Demotion is the asymmetry. A threshold crossed downward completes the
matching future with `ChunkHolder.UNLOADED_LEVEL_CHUNK` and
`ChunkHolder.demoteFullChunk` fires the status change *now*, cancelling any
promotion still pending. Entities in a chunk whose level rose past 31 stop
ticking in the same update; nothing waits for a worker.
What each promotion means when it lands: at FULL,
`PersistentEntitySectionManager.updateChunkStatus` makes the chunk's
entities `Visibility.TRACKED` and queues their data to load (Part VI). At
BLOCK_TICKING the continuation runs `LevelChunk.postProcessGeneration`,
`ServerLevel.startTickingChunk` — which is `LevelChunk.unpackTicks`, the
saved scheduled ticks becoming real — and `ChunkMap.onChunkReadyToSend`. At
ENTITY_TICKING, `PersistentEntitySectionManager.startTicking` puts the
entities on the `EntityTickList`.
## When the graphs run
All of it on the **Server thread**, in three slots:
1. **The tick.** `ServerChunkCache.tick`, from the level tick, runs
`TicketStorage.purgeStaleTickets` and then
`ServerChunkCache.runDistanceManagerUpdates`.
2. **Idle time.** Whenever the server thread would otherwise wait,
`MinecraftServer.pollTaskInternal` polls every level's
`ServerChunkCache.MainThreadExecutor.pollTask`, which runs the distance
updates *first* and, if they did any work, returns at once. The light
schedule and the one queued chunk task only happen on a poll where the
graphs were already settled — propagation does not share the queue with
chunk work, it starves it until quiescent.
3. **A synchronous ask.** `ServerChunkCache.getChunk` from anywhere on the
server thread checks a four-entry cache, then
`ServerChunkCache.getChunkFutureMainThread` adds a `TicketType.UNKNOWN`
ticket and, if `ServerChunkCache.chunkAbsent`, runs the distance updates
synchronously so the holder exists in this call, then
`BlockableEventLoop.managedBlock` until the future is done. The server
thread never sleeps on a chunk: it runs chunk tasks while it waits.
Off-thread callers are bounced to the main thread and joined.
Inside `DistanceManager.runAllUpdates` the order is fixed: the spawn
counter, the simulation tracker, the player ticket tracker, the loading
tracker, and then **two passes** over `DistanceManager.chunksToUpdateFutures`
— `GenerationChunkHolder.updateHighestAllowedStatus` for every changed
holder first, `ChunkHolder.updateFutures` for every holder second — because
a holder's range future depends on its neighbours' allowed status. Nothing
here adds a ticket from a worker. The throttle's
`ThrottlingChunkTaskDispatcher` is built over a `TaskScheduler` wrapping
`DistanceManager.mainThreadExecutor`, so the ticket task it releases runs
on the main thread; only the dispatcher's own priority-queue bookkeeping
runs on the worker pool.
## The walk east
View distance 10, simulation distance 10.
```mermaid
sequenceDiagram
participant SGPL as ServerGamePacketListenerImpl
participant CM as ChunkMap
participant DM as DistanceManager
participant TS as TicketStorage
participant CH as ChunkHolder
participant PCS as PlayerChunkSender
SGPL->>CM: move: the section changed
CM->>DM: removePlayer(old) then addPlayer(new)
DM->>TS: PLAYER_SIMULATION ticket moves, level 21
CM->>SGPL: ClientboundSetChunkCacheCenterPacket, the two crescents marked or dropped
Note over DM: runAllUpdates, this tick or the next idle poll
DM->>DM: simulation graph floods: entity range 10, block range 11
DM->>DM: PlayerTicketTracker: 21 chunks entered view, 21 left, four submitted at a time
DM->>TS: PLAYER_LOADING added at level 31 (east), removed (west)
DM->>CM: loading graph floods: updateChunkScheduling makes holders out to level 44
DM->>CH: updateFutures: 45 to 31 arms all three futures
CH-->>CM: (a later tick) FULL, then BLOCK_TICKING: onChunkReadyToSend
CM->>PCS: markChunkPendingToSend for every player whose view holds it
CH-->>DM: ENTITY_TICKING completes, the throttle slot is released
PCS->>SGPL: sendNextChunks: batch start, nearest first up to the quota, batch finished
```
The move is `ServerGamePacketListenerImpl.handleMovePlayer` →
`ServerChunkCache.move` → `ChunkMap.move`, which updates every
`ChunkMap.TrackedEntity` for the player and then compares
`ServerPlayer.getLastSectionPos` with the new `SectionPos`.
`DistanceManager.removePlayer` finds the old chunk's
`DistanceManager.playersPerChunk` set empty and removes the
`TicketType.PLAYER_SIMULATION` ticket; `DistanceManager.addPlayer` mirrors
it at `DistanceManager.getPlayerTicketLevel`, 31 minus the simulation
distance. `TicketStorage.addTicket` compares the new ticket's level against the
lowest each graph the flags name already had, and tells the registered
`TicketStorage.ChunkUpdated` listener only when the new one is lower; the
removal path is the one that recomputes a minimum. Either way the listener
only *queues* the change; the flood happens in
`DistanceManager.runAllUpdates`.
The simulation graph settles first and needs no futures and no IO: level 31
out to distance 10, 32 at 11, and from that moment
`DistanceManager.inEntityTickingRange` and
`DistanceManager.inBlockTickingRange` answer differently for the far
western edge, whose block ticking has already stopped. Then
`DistanceManager.PlayerTicketTracker.runAllUpdates` floods its own radius-32
graph. For each chunk newly within `DistanceManager.PlayerTicketTracker.haveTicketFor`
it submits a task to the dispatcher at priority = distance; for each chunk
that left, it releases the slot with a continuation that removes the
`TicketType.PLAYER_LOADING` ticket. The dispatcher lets **four** through at
a time; each adds a ticket at `DistanceManager.PLAYER_TICKET_LEVEL`, 31, and
records the key in `DistanceManager.ticketsToRelease`. The loading graph
floods from it — 31 at the chunk, 32 and 33 in the rings, then 34 … 44
eleven chunks further east — and `LoadingChunkTracker.setLevel` →
`ChunkMap.updateChunkScheduling` creates a `ChunkHolder` for every chunk
whose level dropped to 44 or below, or resurrects one from
`ChunkMap.pendingUnloads`. Finally `ChunkMap.promoteChunkMap` publishes the
new holders to the visible map and `ServerChunkCache.clearCache`.
The release is what makes sprinting outrun the loader by design.
`DistanceManager.runAllUpdates` does not wait for the entity-ticking
future: on a pass where nothing else needed updating, it hangs a
continuation on each pending key's future and clears the set. A busy tick
defers every release, and the slot frees whenever the already-attached
future completes, so at most four view chunks are ever loading at once and
they are the four nearest.
The west unloads without a timeout. The removed loading tickets raise the
western column past 44 → `ChunkMap.toDrop` → the futures complete with
`ChunkHolder.UNLOADED_LEVEL_CHUNK` and the demotion fires at once → the next
`ChunkMap.tick` with the time supplier runs `ChunkMap.processUnloads` →
`ChunkMap.scheduleUnload`, save and `ServerLevel.unload`
([chunk storage](chunk-storage.md)). If a ticket re-adopts the chunk first,
`ChunkMap.updateChunkScheduling` pulls it back out of
`ChunkMap.pendingUnloads` and the unload task finds nothing to do.
## What the player is sent, and when
| the moment | what goes out | the gate |
|---|---|---|
| the player crosses a section boundary | `ClientboundSetChunkCacheCenterPacket` | only if the chunk column changed — `ChunkMap.updateChunkTracking` returns early on the same centre and view distance, and `ChunkMap.applyChunkTrackingView` sends the packet only when the centre moved |
| a chunk enters the view | `ChunkMap.markChunkPendingToSend` | only if `ChunkMap.getChunkToSend` already has a ticking chunk; a fresh chunk waits for its promotion |
| a chunk reaches BLOCK_TICKING | `ChunkMap.onChunkReadyToSend` → pending for every player whose view holds it | `ChunkHolder.sendSync`, which starts complete; the one thing that delays it is `ChunkMap.waitForLightBeforeSending`, whose single caller is `EnderDragonFight` after building the exit portal |
| once a tick, from `MinecraftServer.tickChildren` | `ClientboundChunkBatchStartPacket`, up to the quota of `ClientboundLevelChunkWithLightPacket` nearest first, `ClientboundChunkBatchFinishedPacket` | under the acknowledgement limit: one batch until the first reply, then `PlayerChunkSender.MAX_UNACKNOWLEDGED_BATCHES`, 10 |
| the client replies | `ServerboundChunkBatchReceivedPacket` carries how many chunks per tick it wants | clamped `PlayerChunkSender.MIN_CHUNKS_PER_TICK` 0.01 … `PlayerChunkSender.MAX_CHUNKS_PER_TICK` 64, starting at `PlayerChunkSender.START_CHUNKS_PER_TICK` 9 |
| a chunk leaves the view | `ClientboundForgetLevelChunkPacket` | only if it was not still pending, and only to a living player — you cannot forget what was never delivered |
| a block changes in a chunk not yet delivered | nothing | `ChunkMap.isChunkTracked` is false while the chunk sits in `PlayerChunkSender.pendingChunks`; the full chunk will carry it |
| the settings change | `ClientboundSetChunkCacheRadiusPacket` · `ClientboundSetSimulationDistancePacket` | `PlayerList.setViewDistance` · `PlayerList.setSimulationDistance`, which swaps every simulation ticket's level through `TicketStorage.replaceTicketLevelOfType` |
Two shapes hide in that table. The view is a rounded square, not a disc:
`ChunkTrackingView.isWithinDistance` subtracts a buffer of two from each
axis *before* squaring, so at view distance 10 it reaches eleven chunks
along the axes and nine on the diagonal. And view distance shapes what is
*sent*, not what is loaded: `ChunkMap.getPlayerViewDistance` clamps a
player's request to the server's, but the ticket tracker's radius comes from
the **server** view distance through `DistanceManager.updatePlayerTickets`.
Singleplayer skips the size cap, not the pacing: `PlayerChunkSender` still
wants an acknowledgement slot and a whole chunk of quota before it builds a
batch, but on an in-memory connection the batch it then builds is the whole
pending set.
## When a ticket dies
| the ticket | dies when |
|---|---|
| no timeout — player, forced, dragon | its source removes it: the player leaves the chunk, `/forceload remove`, the fight ends |
| timed and `TicketType.canExpireIfUnloaded` — only `TicketType.UNKNOWN` | the countdown runs every tick regardless, so it can expire before the chunk it asked for loads; `ServerChunkCache.addTicketAndLoadWithRadius` refuses such types for that reason |
| timed, everything else — portal, pearl, spawn | the countdown runs only while there is **no holder at all** or the holder `ChunkHolder.isReadyForSaving`; a portal ticket never expires under a chunk still loading, and one over a chunk nothing tracks expires normally |
| the server stops | every type but `TicketType.UNKNOWN` is parked and replayed on the next start; only the persisting types reach disk |
`TicketStorage.purgeStaleTickets` runs from `ServerChunkCache.tick` and
applies exactly those rules — once a tick, unless the level is frozen and
chunk ticking is on, in which case it does not run at all.
## Questions players ask
**Why does turning my render distance down not help the server?** Because
the server loads to *its* view distance, not yours. Your request only
clamps what you are sent.
**Why does the world load in a square?** It does not, quite: the tracking
view is a square with its corners cut by the buffer-of-two test above.
Loading, though, follows the graph, and the graph floods in Chebyshev rings
— every ring is a square.
**Why do mobs spawn where I did not expect?** The spawn set is a fixed
radius of eight around each player, on a tracker that reads neither graph
and no setting.
**Why does a portal keep its chunks after I have gone?** A
`TicketType.PORTAL` ticket lasts 300 ticks, persists across restarts with
its remaining `Ticket.ticksLeft`, and only counts down while its chunk is
saveable.
**Why does sprinting outrun chunk loading?** Four in flight, nearest
first, one slot released per completed entity-ticking future. It is a
throttle, not a bug.
**Do spectators load chunks?** Only if `GameRules.SPECTATORS_GENERATE_CHUNKS`
says so: `ChunkMap.skipPlayer` is the gate, and a skipped player is still
sent chunks that exist, but places no tickets that would generate them.
## Where to look
`TicketType` · `TicketStorage.addTicket` · `TicketStorage.purgeStaleTickets` ·
`ChunkLevel.byStatus` · `ChunkLevel.fullStatus` · `DistanceManager.addPlayer` ·
`DistanceManager.runAllUpdates` · `DistanceManager.PlayerTicketTracker.onLevelChange` ·
`LoadingChunkTracker` · `SimulationChunkTracker` · `ChunkTracker.computeLevelFromNeighbor` ·
`ChunkMap.updateChunkScheduling` · `ChunkMap.move` · `ChunkMap.applyChunkTrackingView` ·
`ChunkMap.prepareTickingChunk` · `ChunkHolder.updateFutures` ·
`ChunkHolder.scheduleFullChunkPromotion` · `ServerChunkCache.getChunk` ·
`ServerChunkCache.getChunkFutureMainThread` · `ServerChunkCache.runDistanceManagerUpdates` ·
`ChunkTrackingView.difference` · `PlayerChunkSender.sendNextChunks`
---
*Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes `tools/verify_names.py`.*
---
# The chunk generation pipeline
> Verified against **Minecraft 26.2** · Part IV · A ticket asks for one chunk at *FULL*, and the server claims five hundred and twenty-nine of them before it runs a single step.
A player walks east, a loading ticket lands on the chunk that just entered
view, and `ChunkHolder.updateFutures` asks that chunk for `ChunkStatus.FULL`.
Nothing in the request mentions neighbours. But *FULL* is the last of twelve
steps, and the steps below it read — and four of them write — the chunks
around the one being built, so the first thing `ChunkGenerationTask.create`
does is walk out to Chebyshev distance 11 and take a claim on every holder in
that square: **asking for one chunk asks for 529 of them, and the eleven
rings it claims will never, on this task's account, become chunks you could
stand on.** Eleven is not a tuning constant. It is the index of the last entry of
`ChunkStep.accumulatedDependencies` for the FULL step — a twelve-entry list,
one per ring from the centre out: `ChunkLevel.RADIUS_AROUND_FULL_CHUNK` reads
that radius off the pyramid, and `ChunkLevel.MAX_LEVEL`, 44, is 33 plus it.
Change the pyramid and the world's loading radius changes with it.
## The cast
| class | what it decides | thread |
|---|---|---|
| `ChunkStatus` | the twelve names and their order, and nothing else — no task, no radius, no work | static, a `BuiltInRegistries.CHUNK_STATUS` entry |
| `ChunkPyramid` | the two step lists — one for generating, one for loading — that say what each status needs and what runs it | static |
| `ChunkStep` | one status's direct and accumulated dependencies, its block-state write radius, and its body | static |
| `ChunkGenerationTask` | one (chunk, target) walk: which layer is in flight, which pyramid it is using, and when to yield | the *worldgen* executor |
| `GenerationChunkHolder` | one chunk's twelve futures, its ticket-derived ceiling, and the compare-and-set that runs each step exactly once | any — every field of it is atomic |
| `ChunkMap` | makes the tasks, owns both executors, and turns the *EMPTY* step into a disk read | Server, but `ChunkMap.applyStep` runs on whatever thread reached it |
| `ChunkTaskDispatcher` | which chunk's batch of work the executor gets next, and re-sorts the queue when tickets move | its own single-file queue on the worker pool |
| `WorldGenRegion` | what a running step may read and what it may write, checked per call | the thread running the step |
## The pyramid, drawn
```mermaid
flowchart TD
EM[("EMPTY, radius 11 — the disk read: region file, parse on the pool, chunk object on the server thread")]
SS["STRUCTURE_STARTS, radius 11 — inline on the worldgen executor"]
SR["STRUCTURE_REFERENCES, radius 3 — inline on the worldgen executor"]
BI(["BIOMES, radius 3 — forked to the worker pool as init_biomes"])
NO(["NOISE, radius 2 — forked to the worker pool as wgen_fill_noise, may write radius 0"])
SU["SURFACE, radius 2 — inline, may write radius 0"]
CA["CARVERS, radius 2 — inline, may write radius 0"]
FE["FEATURES, radius 1 — inline, may write radius 1"]
IL(["INITIALIZE_LIGHT, radius 1 — the light executor"])
LI(["LIGHT, radius 0 — the light executor"])
SP["SPAWN, radius 0 — inline on the worldgen executor"]
FU(["FULL, radius 0 — the server thread"])
ACC["accumulated for FULL: SPAWN at distance 0, INITIALIZE_LIGHT at 1, CARVERS at 2, BIOMES at 3, STRUCTURE_STARTS from 4 out to 11 — twelve entries, so a radius of 11"]
EM --> SS --> SR --> BI --> NO --> SU --> CA --> FE --> IL --> LI --> SP --> FU
FU -- "ChunkStep.accumulatedDependencies, counted" --> ACC
```
Read it downward: that is the whole pipeline. The rounded steps are the five
that leave the *worldgen* executor, the cylinder is the one step that is not
worldgen at all, and the six plain boxes run inline. The radius on each node
is how wide that layer is swept **when the target is FULL and the task has
decided it must generate** — `ChunkGenerationTask.getRadiusForLayer` asks the
FULL step of whichever pyramid is in play for
`ChunkStep.getAccumulatedRadiusOf` that status. A task aiming lower sweeps
narrower rings, and a chunk that only ever reaches *STRUCTURE_STARTS* is
swept at radius 0 by its own task. *EMPTY* is the node to read twice: the
first sweep is the loading pyramid's radius 1, and only a chunk that turns
out to need generating is swept at 11, as the load-or-generate section below
explains.
A `ChunkStatus` carries no work. It is a registry entry with an index, a
parent, a `ChunkType` (`ChunkType.PROTOCHUNK` for the first eleven,
`ChunkType.LEVELCHUNK` for `ChunkStatus.FULL`) and the heightmaps that become
valid after it, `ChunkStatus.heightmapsAfter`. Everything else — the
dependencies, the write radius, the body — lives in the `ChunkStep` that
`ChunkPyramid` holds for that status. Each step is built from its
predecessor, so every step silently requires its own parent status at
distance 0 before it declares anything; `ChunkStep.Builder.addRequirement`
then widens the array outward, taking the later of the two statuses at every
distance already covered.
The generation pyramid's declared requirements, with the parent requirement
resolved in:
| step | needs | may write |
|---|---|---|
| *STRUCTURE_STARTS* | *EMPTY* at 0 | — |
| *STRUCTURE_REFERENCES* | *STRUCTURE_STARTS* from 0 out to 8 | — |
| *BIOMES* | *STRUCTURE_REFERENCES* at 0, *STRUCTURE_STARTS* out to 8 | — |
| *NOISE* | *BIOMES* within 1, *STRUCTURE_STARTS* out to 8 | radius 0 |
| *SURFACE* | *NOISE* at 0, *BIOMES* at 1, *STRUCTURE_STARTS* out to 8 | radius 0 |
| *CARVERS* | *SURFACE* at 0, *STRUCTURE_STARTS* out to 8 | radius 0 |
| *FEATURES* | *CARVERS* within 1, *STRUCTURE_STARTS* out to 8 | radius 1 |
| *LIGHT* | *INITIALIZE_LIGHT* within 1 | — |
| *SPAWN* | *LIGHT* at 0, *BIOMES* at 1 | — |
The rows that do the work are the radius-1 ones: they force a neighbour to
run one step ahead of the chunk being built. Five requirements in the pyramid
have radius 1, but only three of them widen the accumulated list, because
`ChunkStep.Builder.getRadiusOfParent` counts a debt only when the step's own
parent already sits a ring out. *NOISE* wanting *BIOMES*, *FEATURES* wanting
*CARVERS* and *LIGHT* wanting *INITIALIZE_LIGHT* each add one; *SURFACE* and
*SPAWN*, which also ask for *BIOMES* within 1, add nothing. Three ones on top
of *STRUCTURE_STARTS* out to 8 is where the 11 comes from. `ChunkStatus.MAX_STRUCTURE_DISTANCE` is declared as 8 and the pyramid writes
the literal each time — no reader of the constant survives the decompile.
The same arithmetic sets the edge of the world. `ChunkPyramid.SAFETY_MARGIN_CHUNKS`
is 32 plus the twelve accumulated entries plus one, doubled — 90 chunks —
subtracted from the coordinate maximum to give
`ChunkPyramid.MAX_CHUNK_COORDINATE_VALUE`, which `ChunkPos.isValid` enforces
and the `GenerationChunkHolder` constructor throws on. It is a guard against
arithmetic, not against players: at about 33.5 million blocks it sits three
and a half million blocks *outside* `Level.MAX_LEVEL_SIZE`, the ±30 000 000
nobody can build past anyway.
## A ticket sets a ceiling, and a separate call names the target
Two different numbers reach a holder from the ticket system
([tickets and loading](tickets-and-loading.md)). `DistanceManager.runAllUpdates`
first gives every touched holder `GenerationChunkHolder.updateHighestAllowedStatus`,
which is `ChunkLevel.generationStatus` of the new ticket level — 33 is *FULL*,
34 is *INITIALIZE_LIGHT*, 35 *CARVERS*, 36 *BIOMES*, 37 through 44
*STRUCTURE_STARTS*, and 45 is no status at all. That is a ceiling, not a
goal: `GenerationChunkHolder.isStatusDisallowed` gates every request against
it and hands back `GenerationChunkHolder.UNLOADED_CHUNK_FUTURE` for anything
above. Then `ChunkHolder.updateFutures` crosses `FullChunkStatus.FULL`,
calls `ChunkMap.prepareAccessibleChunk`, and *that* names the target:
`ChunkMap.getChunkRangeFuture` over the 3×3, `ChunkStatus.FULL` on the
centre and `ChunkLevel.getStatusAroundFullChunk` — *INITIALIZE_LIGHT* — on
the eight around it, each through `GenerationChunkHolder.scheduleChunkGenerationTask`.
If the ceiling later drops, `GenerationChunkHolder.updateHighestAllowedStatus`
fails every pending future between the new ceiling and the old with
`GenerationChunkHolder.UNLOADED_CHUNK` and reschedules the task at the
highest status anyone is still waiting for. Nothing is interrupted; a worker
mid-step finishes it and finds nobody listening.
The other way in is synchronous. `ServerChunkCache.getChunk` from the server
thread adds a `TicketType.UNKNOWN` ticket, runs the distance updates inline
so the holder exists, and then `BlockableEventLoop.managedBlock`s on the
future — with `ServerChunkCache.MainThreadExecutor.pollTask` overridden to
drain chunk work while it waits, so the thread that is blocked on generation
is also the thread finishing it.
## The task claims its 529 before it runs anything
`GenerationChunkHolder.scheduleChunkGenerationTask` finds no task in flight,
so `GenerationChunkHolder.rescheduleChunkTask` calls
`ChunkMap.scheduleGenerationTask` and `ChunkGenerationTask.create` builds the
holder set: a `StaticCache2D` whose radius is the *generation* pyramid's
accumulated radius of `ChunkStatus.EMPTY` for the target — 11 for *FULL* —
filled by `GeneratingChunkMap.acquireGeneration`, the five-method interface
`ChunkMap` implements and the pipeline actually holds. That radius is taken
from the generation pyramid unconditionally, before anything has looked at
the disk, so even a chunk that turns out to be sitting complete in a region
file claims all 529 holders first.
The claim is a reference count. `GenerationChunkHolder.increaseGenerationRefCount`
on the first claim arms `GenerationChunkHolder.generationSaveSyncFuture` and
hangs it off the holder's save dependency, so nothing in the square can be
saved or unloaded while the task lives ([chunk storage](chunk-storage.md)).
The task itself waits in `ChunkMap.pendingGenerationTasks` until
`ChunkMap.runGenerationTasks`, at the end of the same
`ServerChunkCache.runDistanceManagerUpdates` that created it.
## Dispatch, and why the parallelism is smaller than the thread names
`ChunkMap` builds two `ConsecutiveExecutor`s over the shared worker pool,
named *worldgen* and *light*, each wrapped in a `ChunkTaskDispatcher`. A
`ConsecutiveExecutor` runs **one task at a time**: `AbstractConsecutiveExecutor.run`
pops a single item, runs it under the executor's name, and re-registers
itself on the pool. The dispatcher in front of it is a
`ChunkTaskPriorityQueue` of `ChunkTaskPriorityQueue.PRIORITY_LEVEL_COUNT`
buckets — 46, `ChunkLevel.MAX_LEVEL` plus two — keyed by the holder's queue
level, and `ChunkTaskDispatcher.scheduleForExecution` hands over one chunk's
runnables at a time, polling again only when they have all completed. So all
worldgen for a dimension is a single file, however many `Worker-Main-n`
threads the pool has (`Util.maxAllowedExecutorThreads`: cores minus one,
capped by the *max.bg.threads* property; there is no generation thread
setting).
**One** — worldgen runnables executing at a time per dimension
(`ChunkMap.worldgenTaskDispatcher`, over a single `ConsecutiveExecutor`).
Overlap comes from yielding, not from threads.
`ChunkGenerationTask.runUntilWait` returns the moment a layer holds a future
that is not yet done; `ChunkMap.runGenerationTask` chains a resubmit onto
that future and the executor moves to another chunk's task at once. No worldgen thread
ever blocks waiting for a neighbour, and a task parked on a biome fork costs
nothing.
Priority is live, not fixed at submission. `ChunkHolder.updateFutures` ends
by telling both dispatchers through `ChunkTaskDispatcher.onLevelChange`, and
`ChunkTaskPriorityQueue.resortChunkTasks` moves work already queued into its
new bucket — at a higher priority inside the dispatcher's own four-slot queue
than new submissions get, so "closer to a player runs first" stays true while
the player is moving. `ThrottlingChunkTaskDispatcher` is a subclass of the
same thing but is *not* worldgen: it caps how many player-view chunks the
ticket tracker may have in flight, on the main thread.
## The EMPTY step asks the only question that changes the walk
`ChunkGenerationTask.scheduleNextLayer` always begins with `ChunkStatus.EMPTY`
at the *loading* pyramid's radius, which for a *FULL* target is 1.
`ChunkMap.applyStep` special-cases that status: instead of a step body it
runs `ChunkMap.scheduleChunkLoad` — the region read, `ChunkMap.upgradeChunkTag`
on the pool under *upgradeChunk*, `SerializableChunkData.parse` on the pool
under *parseChunk*, the POI file prefetched alongside through
`SectionStorage.prefetch`, then `SerializableChunkData.read` **on the server
thread**. What comes out is a `ProtoChunk` at whatever status the file
recorded, an `ImposterProtoChunk` wrapping a real `LevelChunk` if the file
was already at *FULL*, or `ChunkMap.createEmptyChunk` when there was no file.
The futures are not done, so the task yields and is re-entered when they land
([chunk storage](chunk-storage.md)).
Now `ChunkGenerationTask.canLoadWithoutGeneration` decides. It wants the
centre persisted at or past the target, and every chunk in the loading
pyramid's accumulated square — for *FULL*, the 3×3 — at or past what its
distance requires there: *SPAWN* at the centre, *INITIALIZE_LIGHT* on the
ring. If that holds, the walk stays narrow. `ChunkPyramid.LOADING_PYRAMID`
passes seven of the twelve steps straight through and only four do anything —
`ChunkStatusTasks.loadStructureStarts`, which just posts the saved starts to
`StructureCheck`, the two light steps, and `ChunkStatusTasks.full`. **A
loaded chunk still walks all twelve steps**, and it still needs its 3×3
neighbours at *INITIALIZE_LIGHT* before its own *LIGHT* step will run.
If it does not hold, `ChunkGenerationTask.needsGeneration` goes true and
*EMPTY* is scheduled a second time, now at radius 11 — reading only the
chunks the first sweep did not touch.
`GenerationChunkHolder.applyStep` runs `GenerationChunkHolder.acquireStatusBump`,
a compare-and-set on `GenerationChunkHolder.startedWork` from a status's
parent to the status itself, so exactly one caller ever runs a step for a
holder and every other caller is handed the existing future.
And the choice of pyramid is made again for **every chunk in every layer**,
not once for the task. `ChunkGenerationTask.scheduleChunkInLayer` compares
that chunk's persisted status with the layer being applied and takes the
generation pyramid only if the chunk is genuinely behind, so a generating
task's 23×23 square routinely mixes both — which is exactly what stops
already-finished neighbours being generated a second time.
## Four steps may write, and only four
`ChunkStep`'s default block-state write radius is **−1**, not 0 — so for
eight of the twelve steps `WorldGenRegion.ensureCanWrite` fails even for the
chunk's own column, and `WorldGenRegion.setBlock` logs and returns false
rather than doing anything. Only *NOISE*, *SURFACE* and *CARVERS* (radius 0)
and *FEATURES* (radius 1) can change a block at all. What rides on those
steps — the density functions, the surface rules, the carvers, the features
and the structures they place — is Part XII's subject
([terrain](../worldgen/terrain.md),
[density functions](../worldgen/density-functions.md),
[structure placement](../worldgen/structure-placement.md)). This page is the conveyor.
*STRUCTURE_STARTS* runs `ChunkGenerator.createStructures` for every chunk in
the radius-11 square that is not already past it — seed and placement state
only, no terrain — and is skipped entirely when `WorldOptions.generateStructures`
is off. Either way `ServerLevel.onStructureStartsAvailable` posts the chunk's
starts to the server thread. *STRUCTURE_REFERENCES* then records, per chunk,
which starts within eight chunks reach into it: the reason starts needed a
radius of 8 around *it*. *BIOMES* forks — both `ChunkGenerator.createBiomes`
and `NoiseBasedChunkGenerator`'s override put the work on the pool under
*init_biomes* — so biomes always leave the worldgen executor. *NOISE* forks
only for `NoiseBasedChunkGenerator`, under *wgen_fill_noise*, and applies the
`BelowZeroRetrogen` bedrock fix-ups afterwards if the chunk is being deepened;
`FlatLevelSource` and `DebugLevelSource` return a completed future and stay
inline. *SURFACE* and *CARVERS* run inline at write radius 0. *FEATURES*
primes the four final heightmaps with `Heightmap.primeHeightmaps`, decorates,
and calls `Blender.generateBorderTicks`
([blending](../worldgen/blending.md)).
*FEATURES* is the interesting one, because a tree at a chunk edge writes into
a neighbour and nothing about that neighbour's status says it is safe. What
makes it safe is the executor: the layer steps its chunks one at a time, and
every worldgen task in the dimension is serialised behind the one
`ConsecutiveExecutor`, so no two feature steps in a dimension are ever
running at once. The ordinary cross-chunk write is the plain
`ChunkAccess.setBlockState`, which takes and releases the section per write;
only `OreFeature` holds a section open across many writes, through
`BulkSectionAccess` ([chunk anatomy](chunk-anatomy.md)).
### A read too far crashes, a read too wide only warns
Both bad accesses are caught, and they are caught differently. A read outside
the step's `ChunkStep.directDependencies` — too far away, or at a status that
distance does not guarantee — throws out of `WorldGenRegion.getChunk` as a
crash report naming the step, the requested and actual statuses, the distance
and the whole dependency list. A read that is merely outside the *write* zone
is a log warning from `WorldGenRegion.warnIfReadOutsideWriteZone`, naming the
feature through `WorldGenRegion.currentlyGenerating`. The first is a bug in
the pyramid; the second is a bug in a feature, and the game keeps going.
## Light runs on a second executor, and the task waits for it
`ChunkStatusTasks.initializeLight` calls `ChunkAccess.initializeLightSources`
and `ProtoChunk.setLightEngine` — from that moment the proto chunk forwards
block changes to the engine — and then hands the chunk to
`ThreadedLevelLightEngine.initializeLight`. `ChunkStatusTasks.light` follows
with `ThreadedLevelLightEngine.lightChunk`. Both queue through the *light*
`ChunkTaskDispatcher` onto the *light* `ConsecutiveExecutor`, and the future
each returns is completed by a later task on that same executor, so the
generation task genuinely parks here ([lighting](lighting.md)).
Both are passed a *lighted* flag from `ChunkStatusTasks.isLighted`: persisted
status at or past *LIGHT* **and** `ChunkAccess.isLightCorrect`. When it is
true, `ThreadedLevelLightEngine.lightChunk` skips propagation entirely and
only marks the chunk correct again. Light saved on disk is re-enabled, never
recomputed.
## FULL is assembled on the server thread
`ChunkStatusTasks.full` is scheduled exactly like the other eleven steps, but
its body is a *supplyAsync* on `WorldGenContext.mainThreadExecutor` — the
`ServerChunkCache.MainThreadExecutor`, a `BlockableEventLoop` pinned to the
server thread. There are two shapes it can take. If the chunk is already an
`ImposterProtoChunk`, because the file held a finished chunk, it unwraps the
`LevelChunk` inside and replaces nothing. Otherwise a `LevelChunk` is built
from the `ProtoChunk`, sharing its sections, and
`GenerationChunkHolder.replaceProtoChunk` rewrites slots 0 through 10 of the
holder's future array to an `ImposterProtoChunk` over it with writes
disallowed — every slot checked, and the whole step thrown out if any of them
is not a `ProtoChunk` or was changed by another thread in the meantime.
Then the chunk becomes part of the world, in order: `LevelChunk.setFullStatus`
wired to the holder, `LevelChunk.runPostLoad` turning `ProtoChunk.getEntities`
into real entities through `ServerLevel.addWorldGenChunkEntities`,
`LevelChunk.setLoaded`, `LevelChunk.registerAllBlockEntitiesAfterLevelLoad`,
`LevelChunk.registerTickContainerInLevel` and `LevelChunk.setUnsavedListener`.
`GenerationChunkHolder.completeFuture` publishes it at *FULL*.
Nothing here crosses the network. A chunk reaches a client only after the
separate promotion to `FullChunkStatus.BLOCK_TICKING`
([tickets and loading](tickets-and-loading.md)).
## Release, and what the ring is left as
`ChunkGenerationTask.runUntilWait` comes round, finds the scheduled status
equal to the target, and calls `ChunkGenerationTask.releaseClaim`:
`GenerationChunkHolder.removeTask` on the centre, then
`ChunkMap.releaseGeneration` on all 529. Every holder whose count reaches zero
completes its save-sync future, and the square is free to be saved or dropped
as its own tickets dictate — which the outer rings, only ever raised to the
status their distance demanded, mostly are.
`ChunkHolder.scheduleFullChunkPromotion` was called back in
`ChunkHolder.updateFutures`, long before any of this; what happens now is its
confirmation landing on the server thread and `ChunkMap.onFullChunkStatusChange` tells
the entity manager. `ChunkLoadCounter` watches this from outside — it counts
holders that reach *FULL* for the spawn progress bar, and that count is what
`MinecraftServer.prepareLevels` loops on until it is zero.
## The whole walk, once
```mermaid
sequenceDiagram
participant DM as DistanceManager
participant CM as ChunkMap
participant CTD as ChunkTaskDispatcher
participant CGT as ChunkGenerationTask
participant Worker as Worker
participant TLE as ThreadedLevelLightEngine
participant SL as ServerLevel
Note over DM,SL: the Server thread, inside runDistanceManagerUpdates
DM->>CM: the holder reaches level 33 — updateHighestAllowedStatus, then updateFutures
CM->>CGT: prepareAccessibleChunk, getChunkRangeFuture, scheduleGenerationTask — create claims 529 holders
CM->>CTD: runGenerationTasks submits runUntilWait at the holder's queue level
Note over CTD,CGT: thread hop — the worldgen ConsecutiveExecutor, one task at a time per dimension
CTD->>CGT: scheduleForExecution hands this chunk's batch over
CGT->>CM: layer EMPTY at radius 1 — applyStep becomes scheduleChunkLoad
CM->>Worker: region read, then upgradeChunk and parseChunk on the pool
Worker->>SL: thread hop — SerializableChunkData.read builds the chunk object
Note over CTD,CGT: the task yielded on the first unfinished future and was resubmitted
CGT->>CGT: canLoadWithoutGeneration is false — EMPTY again, now to radius 11
CGT->>CM: STRUCTURE_STARTS to 11, then STRUCTURE_REFERENCES to 3
CM->>SL: onStructureStartsAvailable posts each chunk's starts to the server thread
CGT->>Worker: thread hop — BIOMES to 3 as init_biomes, NOISE to 2 as wgen_fill_noise
CGT->>CM: SURFACE and CARVERS to 2, FEATURES to 1 — inline, the steps that may write
CGT->>TLE: thread hop — INITIALIZE_LIGHT at 1 and LIGHT at 0 on the light executor
CGT->>SL: SPAWN inline, then FULL — thread hop, supplyAsync on the main-thread executor
SL->>CM: LevelChunk built, replaceProtoChunk, setLoaded, tick containers registered
CGT->>CM: releaseClaim — removeTask, then releaseGeneration on all 529
```
## Questions players ask
**Why does adding cores not speed up world generation?** Because a dimension's
worldgen is one `ConsecutiveExecutor` running one task at a time, and the
dispatcher in front of it releases one chunk's work at a time. The pool is busy in parallel with plenty else — the *light*
executor beside it, the disk read and its datafix, the POI prefetch, the biome
and noise forks, the second dimension — but none of that is a second worldgen
lane. There is no thread-count setting for generation.
**Why does a chunk I have visited before still take work to load?** It walks
all twelve steps. Seven of them pass through and cost nothing, but the disk
read, the structure-start replay, both light steps and the *FULL* assembly are
real work, and the light steps need the 3×3 neighbours read first.
**Why does a chunk sometimes hang on the edge of the view forever?** Its
ticket level puts the ceiling below *FULL*.
`GenerationChunkHolder.isStatusDisallowed` refuses anything higher, so the
chunk sits at *STRUCTURE_STARTS* or *BIOMES*, correct and unfinished, for as
long as the level says so.
**Why is there a limit on how far out I can build?** Not because of the
pyramid. `ChunkPyramid.SAFETY_MARGIN_CHUNKS` does reserve 90 chunks at the
coordinate maximum so that a chunk at the edge still has its radius-11 square
to generate in, and `ChunkPos.isValid` refuses a holder outside it — but that
edge is three and a half million blocks further out than
`Level.MAX_LEVEL_SIZE`, which is the ±30 000 000 a player actually meets.
## Where to look
`ChunkPyramid.GENERATION_PYRAMID` · `ChunkPyramid.LOADING_PYRAMID` ·
`ChunkStep.getAccumulatedRadiusOf` · `ChunkDependencies.getRadiusOf` ·
`ChunkLevel.RADIUS_AROUND_FULL_CHUNK` · `ChunkGenerationTask.create` ·
`ChunkGenerationTask.runUntilWait` · `ChunkGenerationTask.scheduleNextLayer` ·
`ChunkGenerationTask.canLoadWithoutGeneration` ·
`ChunkGenerationTask.scheduleChunkInLayer` ·
`GenerationChunkHolder.scheduleChunkGenerationTask` ·
`GenerationChunkHolder.applyStep` · `GenerationChunkHolder.acquireStatusBump` ·
`ChunkMap.applyStep` · `ChunkMap.scheduleChunkLoad` ·
`ChunkMap.runGenerationTask` · `ChunkTaskDispatcher.scheduleForExecution` ·
`ChunkTaskPriorityQueue.resortChunkTasks` · `AbstractConsecutiveExecutor.run` ·
`ChunkStatusTasks.full` · `WorldGenRegion.getChunk` ·
`WorldGenRegion.ensureCanWrite` · `StaticCache2D`
---
*Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes `tools/verify_names.py`.*
---
# Lighting
> Verified against **Minecraft 26.2** · Part IV · A torch is placed on a cave wall: the write queues a task, a worker floods the change, and the sections it touched are published as a copy and mailed to the client.
A player right-clicks a torch onto stone. The block goes into its
`LevelChunkSection` immediately, the heightmaps move, and then
`LevelChunk.setBlockState` reaches the light engine at all only to call
`LevelLightEngine.checkBlock`, which on the server computes nothing at all.
It wraps the call in a runnable and puts it on a queue that nothing in the
level tick will ever drain. There is no light thread and no light phase of
the tick: the flood that turns the torch's 14 into a sphere of falling
numbers runs because **the server thread finished early and went looking for
something to do**. The kick is `ThreadedLevelLightEngine.tryScheduleUpdate`,
whose only routine caller is `ServerChunkCache.MainThreadExecutor.pollTask` —
the idle poll `MinecraftServer.pollTaskInternal` reaches only when the tick
has budget left. Ask the server engine to light something synchronously and
it refuses: `ThreadedLevelLightEngine.runLightUpdates` throws.
## The cast
| class | what it decides | thread |
|---|---|---|
| `LevelLightEngine` | the facade both sides hold — one `LightEngine` per layer, the read side, and the padding above and below the world | whoever calls it |
| `ThreadedLevelLightEngine` | the server's wrapper: every mutator becomes a queued task, and running updates on demand is an error | queued from Server, drained on the light executor |
| `BlockLightEngine` · `SkyLightEngine` | the algorithm — what a changed block means, and how far the change travels | the light executor (Client: the render thread) |
| `LayerLightSectionStorage` | the double buffer: which sections hold data, which changed, and when to publish | the same |
| `DataLayer` | 2048 bytes of nibbles for one section, or no bytes at all | whoever holds it |
| `ChunkSkyLightSources` | the chunk's 256-entry *lowest sky source Y* table — the one piece of light work that is not deferred | Server, inside the block write |
| `ServerChunkCache` | the `LightChunkGetter`: which chunks the engine may read, and where the one callback lands | callback on the light executor, body on Server |
| `ChunkHolder` | which sections each watching player is owed, as two `BitSet`s | Server |
## From a click to a lit wall
```mermaid
sequenceDiagram
participant LC as LevelChunk
participant TLE as ThreadedLevelLightEngine
participant SCC as ServerChunkCache
participant BLE as BlockLightEngine
participant LLSS as LayerLightSectionStorage
participant CH as ChunkHolder
participant CL as ClientLevel
Note over LC,CH: one server tick, inside Level.setBlock
LC->>LC: setBlockState sees different light properties, so ChunkSkyLightSources.update runs here and now
LC->>TLE: checkBlock, wrapped as a PRE_UPDATE task and submitted at the chunk's queue level
Note over LC,SCC: still the same tick, and nothing has been lit
SCC->>TLE: pollTask found no distance-graph work, so tryScheduleUpdate
Note over TLE,LLSS: the light executor, off the server thread
TLE->>BLE: the window's PRE tasks, then LevelLightEngine.runLightUpdates
BLE->>BLE: checkNode enqueues a pull-in decrease and an emission increase of 14
BLE->>LLSS: propagateDecreases to empty, then propagateIncreases writing 14, 13, 12 and down
LLSS->>LLSS: markNewInconsistencies, then swapSectionMap publishes a copy
LLSS->>SCC: onLightUpdate once per affected section
Note over SCC,CH: back on the server thread, whenever the posted task is polled
SCC->>CH: sectionLightChanged, which bails on a chunk not yet at INITIALIZE_LIGHT and otherwise marks it unsaved and sets a bit
Note over SCC,CH: end of ServerChunkCache.tickChunks, normally the next tick
SCC->>CH: broadcastChangedChunks reaches broadcastChanges
CH->>CL: ClientboundLightUpdatePacket to players this chunk borders, put on lightUpdateQueue by ClientPacketListener
Note over CL: the next frame, not the next tick
CL->>CL: pollLightUpdates, applyLightData, then runLightUpdates on the client's own engine
```
Every section below walks one stretch of that diagram. Two classes are absent
because they carry no decision: `ChunkTaskDispatcher`, which holds the queued
task under the chunk's priority and hands it to the executor, and
`ClientPacketListener`, which only wraps the packet in a closure for later.
## Two 4-bit fields, and the sections that may not exist
Light is `LightLayer.SKY` and `LightLayer.BLOCK`, computed by separate
machinery that shares only its shape. `LevelLightEngine` holds one
`LightEngine` per layer — `LevelLightEngine.skyEngine` is null in a dimension
whose `DimensionType.hasSkyLight` is false, and
`LevelLightEngine.getLayerListener` hands out
`LayerLightEventListener.DummyLightLayerEventListener` in its place. Readers
rarely see the layers apart: `LevelLightEngine.getRawBrightness` is the
maximum of block light and sky light minus a darkening term, which is what
`LevelReader.getMaxLocalRawBrightness` passes `LevelReader.getSkyDarken` into,
and `BlockAndLightGetter.canSeeSky` is just sky light at 15.
Storage is per layer and per *section*, one section taller than the world at
each end — `LevelLightEngine.LIGHT_SECTION_PADDING` is 1 and
`LevelLightEngine.getLightSectionCount` is the world's sections plus two. A
section's storage is a `DataLayer`: 16×16×16 nibbles in `DataLayer.SIZE`
bytes, indexed *y* then *z* then *x*, and allocated lazily. `DataLayer.data`
starts null and every read answers `DataLayer.defaultValue` until the first
write forces the array into existence, and `DataLayer.fill` throws it away
again. `DataLayer.isEmpty` therefore means *homogeneous zero*, not *all
zeroes I checked*, and it is the test both the chunk saver and the packet
builder use to charge nothing for a dark section.
Which sections have a `DataLayer` at all is decided by
`LayerLightSectionStorage.sectionStates`, one byte per section holding a
has-data bit and a five-bit count of how many of its 26 neighbours have data
(`LayerLightSectionStorage.SectionState`). A byte of zero means no storage,
so a section of pure air beside a built-up one is allocated and a section in
the middle of nothing is not. Above the sky column's top section there is no
storage at all, and `SkyLightSectionStorage.getLightValue` answers 15 without
looking; the upward walk it is known for happens *below* the top, when a
section inside the column has no `DataLayer` and the search climbs for one.
## The write that queues nothing but a task
`LevelChunk.setBlockState` asks two questions about light. If the section
flipped between all-air and not — the torch is the first block into an empty
section, or the last one out — `LevelLightEngine.updateSectionStatus` goes in.
Then, if `LightEngine.hasDifferentLightProperties` says the old and new states
differ in emission or in dampening, or if *either* of them uses a shape for
light occlusion, two things happen. The first is `ChunkSkyLightSources.update`
on the chunk's own table, and it runs **inline, on the server thread**, inside
the block write: the sky column's *lowest source Y* for that one *(x, z)* is
repaired immediately, because everything downstream reads it. The second is
`LevelLightEngine.checkBlock`, and on the server that is where the work stops.
`ProtoChunk.setBlockState` does the same pair, but only once the chunk's
status has reached `ChunkStatus.INITIALIZE_LIGHT`; before that a generator
writing blocks tells the light engine nothing.
`ThreadedLevelLightEngine` overrides every mutator the same way. The call
becomes a `Runnable` tagged `ThreadedLevelLightEngine.TaskType.PRE_UPDATE` or
`ThreadedLevelLightEngine.TaskType.POST_UPDATE` and goes through
`ChunkMap.lightTaskDispatcher`, at the chunk's own queue level for the five
mutators that belong to one chunk —
`LevelLightEngine.checkBlock`, `LightEventListener.propagateLightSources`,
`LevelLightEngine.setLightEnabled`, `ThreadedLevelLightEngine.initializeLight`
and `ThreadedLevelLightEngine.lightChunk` — and at a flat top priority for
bookkeeping such as `LevelLightEngine.updateSectionStatus` and
`LevelLightEngine.queueSectionData`. When the dispatcher releases the task it
runs on the *light* executor, not on the server thread, and all it does there
is append to `ThreadedLevelLightEngine.lightTasks` — a plain array list with
no synchronisation, safe precisely because only that executor appends to it.
## What actually kicks it
The light executor is a `ConsecutiveExecutor` named *light*, built in
`ChunkMap`'s constructor over the shared background pool. It has no thread of
its own: it takes one task from its queue, runs it on a borrowed pool thread,
and re-registers itself. Two callers ever start a batch on it.
`ServerChunkCache.MainThreadExecutor.pollTask` runs the distance-graph
updates first and calls `ThreadedLevelLightEngine.tryScheduleUpdate` only if
they had nothing to do — so light propagates in the gaps of a tick that
finished early, after the ticket system is quiescent
([tickets](tickets-and-loading.md)). The other caller is
`ChunkMap.scheduleUnload`, kicking the engine right after it has *queued* the
nulling of an unloading chunk's data. `ThreadedLevelLightEngine.scheduled`, an `AtomicBoolean`, keeps
exactly one batch in flight.
If nobody kicks, the queue is still not unbounded:
`ThreadedLevelLightEngine.addTask` runs a batch inline as soon as
`ThreadedLevelLightEngine.lightTasks` has reached a thousand — the value
`ThreadedLevelLightEngine.DEFAULT_BATCH_SIZE` names, though the test is
written as a literal — still on the light executor, still never on the server
thread.
## One batch, and what it publishes
```mermaid
flowchart TD
PRE["ThreadedLevelLightEngine.runUpdate takes a window of up to 1000 queued tasks and runs the PRE_UPDATE ones"]
PRE --> NODES["every checkBlock in that window has now added a position to LightEngine.blockNodesToCheck"]
NODES --> LAYER["LevelLightEngine.runLightUpdates runs the block engine to completion, then the sky engine, each running the stages below"]
LAYER --> C["checkNode on every queued position, deciding what to enqueue, then the set is cleared"]
C --> D["propagateDecreases drains decreaseQueue to empty, including the refills it discovers"]
D --> I["propagateIncreases drains increaseQueue to empty, including those refills"]
I --> M["markNewInconsistencies splices queuedSections in and drops removed sections"]
M --> S["swapSectionMap publishes a fresh copy and fires LightChunkGetter.onLightUpdate once per affected section"]
S --> POST["the POST_UPDATE tasks of that same window run, and the window is dropped"]
UP["updatingSectionData, the engine's own map, cloned per section on its first write of the batch"] -.-> D
UP -.-> I
S -.-> VIS["visibleSectionData, volatile, and what every other thread reads"]
```
The two maps are why no reader of light ever waits on the light engine.
`LayerLightSectionStorage.updatingSectionData` is the engine's scratch copy
and no other thread reads it; `LayerLightSectionStorage.visibleSectionData`
is volatile and is what every reader, saver and packet builder sees.
`LayerLightSectionStorage.setStoredLevel` clones a section's `DataLayer`
through `DataLayerStorageMap.copyDataLayer` the first time the batch touches
that section, recording it in `LayerLightSectionStorage.changedSections`, and
`LayerLightSectionStorage.swapSectionMap` copies the whole updating map into
a new visible map at the end. A reader on another thread sees the state
before the batch or the state after it, never a half-propagated flood.
Inside a layer the order is fixed. Every position from
`LightEngine.blockNodesToCheck` goes through `LightEngine.checkNode`, which only decides
what to enqueue; then `LightEngine.propagateDecreases` runs the decrease
queue to empty, then `LightEngine.propagateIncreases` runs the increase queue
to empty. Decreases always finish first because a decrease discovers brighter
neighbours and enqueues them as *increase back toward me* refills — running
increases first would spread light that is about to be removed. Both queues
hold pairs of longs whose second member is a `LightEngine.QueueEntry`: four
bits of level, six of allowed directions, and the two flags
`LightEngine.QueueEntry.FLAG_FROM_EMPTY_SHAPE` and
`LightEngine.QueueEntry.FLAG_INCREASE_FROM_EMISSION`. The torch's own entries
are `LightEngine.PULL_LIGHT_IN_ENTRY` — a level-1 decrease in all six
directions, meaning *re-pull from my neighbours* — and an emission increase of
14, `Blocks.TORCH` being a `BlockBehaviour.Properties.lightLevel` of 14.
`BlockLightEngine.propagateIncrease` then spreads level minus
`LightEngine.getOpacity`, which is
`BlockBehaviour.BlockStateBase.getLightDampening` floored at
`LightEngine.MIN_OPACITY`, stopping where a neighbour is already brighter,
where `LightEngine.shapeOccludes`, or when the next level would be 1.
The batch's only exit is `LightChunkGetter.onLightUpdate`, fired from the map
swap. The engine has no idea that `ChunkHolder` exists.
## The sky column is a table, not a flood
Sky light does not use the heightmaps, and it does not usually propagate
downward one block at a time. Each chunk owns a `ChunkSkyLightSources`
(`ChunkAccess.skyLightSources`), a bit-packed 256-entry table of *the lowest
Y at this (x, z) that still sees the sky*, filled by
`ChunkSkyLightSources.fillFrom` and repaired per block by
`ChunkSkyLightSources.update`. What ends a column is
`ChunkSkyLightSources.isEdgeOccluded`: the lower block dampens light at all,
or the two faces occlude each other.
`SkyLightEngine.checkNode` reads that table and takes one of three paths. A
block at or above the column's lowest source enqueues a remove-source
decrease and an add-source increase — the expensive case. A block below it
that held light has that light zeroed and decreased away. A block below it
that was already dark, which is what a torch under a solid ceiling is, gets a
pull-in that changes nothing: placing a torch in a cave does no sky work
worth measuring. Before any of the three, `SkyLightEngine.updateSourcesInColumn`
runs `SkyLightEngine.removeSourcesBelow` and `SkyLightEngine.addSourcesAbove`
so the stored 15s agree with the table again.
Two more pieces of the sky model exist because sections may have no storage.
`SkyLightEngine.propagateFromEmptySections` handles light crossing a section
edge sideways at the bottom row of a section: if the source column had no
data for a run of sections below it, light had been continuing downward
implicitly, so the same level is written straight down the destination column
through that run. And `SkyLightSectionStorage.createDataLayer` seeds a newly
needed section below existing data by repeating the bottom slice of the
section above it (`SkyLightSectionStorage.repeatFirstLayer`) rather than
starting dark.
## Lit before you ever see it
Generation lights a chunk in two steps, and the first usually turns light
*off*. `ChunkStatusTasks.initializeLight` builds the sky table with
`ChunkAccess.initializeLightSources`, then calls
`ThreadedLevelLightEngine.initializeLight`, whose PRE task marks every
non-air section as having data and whose POST task sets the column's enabled
flag to whatever `ChunkStatusTasks.isLighted` says — *false* for a freshly
generated chunk, whose persisted status has not reached `ChunkStatus.LIGHT`.
Enabling is the next step's doing: `ChunkStatusTasks.light` calls
`ThreadedLevelLightEngine.lightChunk`, which for an unlit chunk runs
`LevelLightEngine.propagateLightSources`, and both
`BlockLightEngine.propagateLightSources` and
`SkyLightEngine.propagateLightSources` open by enabling the column before
seeding it — the block engine from `LightChunk.findBlockLightSources`, the sky
engine from its own table and its four neighbours'. Only then does a POST
task set `ChunkAccess.setLightCorrect`. A chunk read from disk with
*isLightOn* set skips the propagation entirely, because
`ThreadedLevelLightEngine.initializeLight` already enabled its column.
Two consequences are worth naming. `ServerChunkCache.getChunkForLighting`
hands the engine chunks at `ChunkStatus.FEATURES`, one status below
`ChunkStatus.INITIALIZE_LIGHT` — the engine reads chunks the rest of the game
is not allowed to see yet. And nothing waits for light before sending a chunk:
what stops a half-lit chunk shipping is the pyramid, because
`ChunkPyramid.GENERATION_PYRAMID` gives `ChunkStatus.LIGHT` a requirement of
`ChunkStatus.INITIALIZE_LIGHT` at radius 1, so a chunk cannot climb to
`ChunkStatus.FULL` until its neighbours have their sections marked
([the generation pipeline](chunk-generation-pipeline.md)). The one real send
dependency, `ChunkMap.waitForLightBeforeSending` →
`ThreadedLevelLightEngine.waitForPendingTasks` → `ChunkHolder.addSendDependency`,
has exactly one caller: `EnderDragonFight`, grafting an exit portal's light
onto chunks the client already holds.
Unloading is the mirror: `ChunkMap.scheduleUnload` calls
`ThreadedLevelLightEngine.updateChunkStatus`, which disables the column,
drops the retain flag and nulls every section's data
([chunk storage](chunk-storage.md)). On load `SerializableChunkData.read`
calls `LevelLightEngine.retainData` once and then
`LevelLightEngine.queueSectionData` for each saved `DataLayer`.
## Off the server thread and onto the wire
`ServerChunkCache.onLightUpdate` is the whole of the hop back. It is called
on the light executor, and its body is a task posted to
`ServerChunkCache.mainThreadProcessor` — no `ChunkHolder` is touched
off-thread. When the server thread later runs that task it finds the holder
in the visible map and calls `ChunkHolder.sectionLightChanged`, which marks
the chunk unsaved (light is saved data), gives up if there is no ticking
chunk to broadcast for, and otherwise sets one bit in
`ChunkHolder.skyChangedLightSectionFilter` or
`ChunkHolder.blockChangedLightSectionFilter` and puts the holder in
`ServerChunkCache.chunkHoldersToBroadcast`.
The packet goes out at the end of `ServerChunkCache.tickChunks` — so only on
a tick where the level ticked chunks, and never at all in a debug world,
whose `Level.isDebug` guard wraps that whole method.
`ServerChunkCache.broadcastChangedChunks` calls `ChunkHolder.broadcastChanges`,
which builds **one** `ClientboundLightUpdatePacket` per chunk from the two
filters and sends it before any block-change packet — but not to everyone
watching. It asks `ChunkHolder.PlayerProvider.getPlayers` with *borderOnly*
true, so `ChunkMap.isChunkOnTrackedBorder` keeps only the players for whom
this chunk has an untracked neighbour. A player in the middle of their own
view distance is never sent light for a block change: they were sent the
block, they have every neighbouring chunk, and their own engine will reach
the same numbers. The packet exists for the players who cannot, because the
flood may be arriving from a chunk they do not have. Its
`ClientboundLightUpdatePacketData` carries four bitsets — a data mask and an
empty mask per layer — and the 2048 bytes of each non-empty changed section,
read through `LayerLightEventListener.getDataLayerData`, which answers the
queued layer if there is one and the *visible* map otherwise, never the
updating copy. A section whose `DataLayer` is empty costs one bit; one with
no `DataLayer` at all appears in neither mask. The same
`ClientboundLightUpdatePacketData` rides inside
`ClientboundLevelChunkWithLightPacket` with both filters null — every section
— when `PlayerChunkSender.sendChunk` first sends a chunk.
**Up to 14** — sections one torch can dirty, and not because a write marks
its neighbours. `LayerLightSectionStorage.setStoredLevel` marks only the
sections within one block of the position written
(`SectionPos.aroundAndAtBlockPos`): one for an interior block, up to eight
for a block on a corner. The 14 comes from the flood, which writes as far as
thirteen blocks away in taxicab distance. Grow that octahedron by the
one-block halo and it reaches three sections deep on one axis or another, but
never on all three at once — three per axis would be 27, and a corner section
needs a diagonal the octahedron does not have. Fourteen sections, across as
many as seven chunk columns. The real 3×3×3
marking, `LayerLightSectionStorage.markSectionAndNeighborsAsAffected`, fires
only when a section is first given a `DataLayer`.
## The client lights per frame
The client runs the same `LevelLightEngine` unwrapped: `ClientChunkCache.lightEngine`
is a plain one, and its `LightChunkGetter.onLightUpdate` goes straight to
`LevelExtractor.setSectionDirty`. It runs from `ClientLevel.update`, which
`Minecraft.renderFrame` calls once a **frame**, not once a tick, so light
converges at your framerate. `ClientPacketListener.handleLightUpdatePacket`
applies nothing; it pushes a closure onto `ClientLevel.lightUpdateQueue`, and
`ClientLevel.pollLightUpdates` runs a bounded number of them per frame — the
larger of ten and a tenth of the backlog, or the whole backlog once it reaches
a thousand — so a burst of chunk loads is spread over frames instead of
stalling one. Each closure is `ClientPacketListener.applyLightData`:
`ClientPacketListener.readSectionList` turns every masked section into a
cloned or empty `DataLayer` through `LevelLightEngine.queueSectionData` and
dirties it with its neighbours, and then the chunk's column is enabled with
`LevelLightEngine.setLightEnabled`. Only after the polled closures does
`ClientLevel.update` call `LevelLightEngine.runLightUpdates`, splicing the
queued layers in and swapping the map exactly as the server does.
Enabling a column is a separate thing from lighting it, and on the client it
gates geometry. `SectionUpdateTracker.hasAllNeighbors` asks for each of the eight
surrounding columns both that the chunk is there and that
`LevelLightEngine.lightOnInColumn` is true for it, and `LevelExtractor` queues a never-yet-meshed section for rebuild
only when they all answer yes — a light flag deciding whether a section may
have a mesh at all ([section meshing](../rendering/section-meshing.md)).
Meanwhile the client had already lit this torch itself:
`MultiPlayerGameMode.startPrediction` runs the placement locally through the
same `LevelChunk.setBlockState`, so the packet mostly confirms what the
client computed a frame or two earlier.
> **For a 1.21-era reader.** *getLightBlock* is now
> `BlockBehaviour.BlockStateBase.getLightDampening`, and it is derived rather
> than declared — 15 for a solid render, 0 for a state that
> `BlockBehaviour.BlockStateBase.propagatesSkylightDown`, 1 otherwise.
> *LightTexture* is `Lightmap` (Part XI).
> `DynamicGraphMinFixedPoint`, `LeveledPriorityQueue` and `SpatialLongSet`
> still live in `world/level/lighting`, but no light engine uses them any
> more. `DynamicGraphMinFixedPoint` survives for `ChunkTracker` and
> `SectionTracker` ([tickets](tickets-and-loading.md)) and takes
> `LeveledPriorityQueue` with it; `SpatialLongSet` has no callers at all.
## Questions players ask
**Why does the torch light up a tick after it lands?** The write only queues
a task, the queue is drained in the server thread's idle time, the callback
is posted back to the server thread, and the packet is built at the end of
`ServerChunkCache.tickChunks`. Four hand-offs, none of them scheduled.
**Why does breaking one block re-light half a room?** A block-light change
propagates thirteen blocks, and every written position marks the sections
within one block of it — up to fourteen sections across seven chunk columns,
each of which the client must re-mesh.
**Does an empty sky section cost anything?** No. A `DataLayer` with no array
is homogeneous zero, `SerializableChunkData.copyOf` skips it on disk, and the
packet spends one bit on it instead of 2048 bytes. Sections above the sky
column's top have no `DataLayer` at all and answer 15 by walking upward.
**Why is a newly loaded chunk sometimes a black wall?** Its column's light is
not enabled yet, and enabling is a separate step from lighting: until
`LevelLightEngine.lightOnInColumn` is true for a section's eight neighbouring
columns, `LevelExtractor` will not build that section's first mesh at all.
**Why do the F3 light numbers not match what I see?** They are three
numbers, and none of them is the one you are looking at. `DebugEntryLight`
prints a combined figure and then the two raw layers, and the combined figure
is `LevelLightEngine.getRawBrightness` called with the time-of-day darkening
argument set to **zero** — so even it is a noon answer. What renders is the
two layers looked up in the `Lightmap` texture, which is where the hour, the
dimension and the mob effects arrive ([the lightmap](../rendering/lightmap-fog-and-sky.md)).
## Where to look
`LevelChunk.setBlockState` · `LightEngine.hasDifferentLightProperties` ·
`ChunkSkyLightSources.update` · `ThreadedLevelLightEngine.addTask` ·
`ThreadedLevelLightEngine.tryScheduleUpdate` · `ThreadedLevelLightEngine.runUpdate` ·
`LightEngine.runLightUpdates` · `LightEngine.QueueEntry` ·
`BlockLightEngine.checkNode` · `BlockLightEngine.propagateIncrease` ·
`SkyLightEngine.checkNode` · `SkyLightEngine.propagateFromEmptySections` ·
`LayerLightSectionStorage.setStoredLevel` · `LayerLightSectionStorage.swapSectionMap` ·
`SkyLightSectionStorage.getLightValue` · `DataLayer` ·
`ThreadedLevelLightEngine.initializeLight` · `ThreadedLevelLightEngine.lightChunk` ·
`ServerChunkCache.onLightUpdate` · `ChunkHolder.sectionLightChanged` ·
`ChunkHolder.broadcastChanges` · `ClientboundLightUpdatePacketData` ·
`ClientPacketListener.applyLightData` · `ClientLevel.pollLightUpdates` ·
`SectionUpdateTracker.hasAllNeighbors`
---
*Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes `tools/verify_names.py`.*
---
# Chunk storage
> Verified against **Minecraft 26.2** · 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 neither 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 is five minutes of wall
clock whatever `/tick rate` says. **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
```mermaid
flowchart LR
S["Server thread — ChunkMap.save decides, SerializableChunkData.copyOf takes the snapshot"] --> W["Worker-Main-n — SerializableChunkData.write builds the CompoundTag"]
W --> F["IOWorker.store, foreground priority — joins the encode and parks the tag in pendingWrites"]
F --> B["IOWorker.storePendingChunk, background priority — runs only when the lane has no foreground work"]
B --> R["RegionFileStorage.write — compress through RegionFileVersion, place the sectors with RegionFile.write"]
R --> D["r.X.Z.mca"]
```
That figure is the page's answer to *why doesn't saving lag the server*. Apart from
flushing the position's POI section, 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 the heightmaps the chunk's
persisted status calls for, 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.
## Three folders, and the one thing that is not in *region/*
`LevelStorageSource.LevelStorageAccess.getDimensionPath` defers to
`DimensionType.getStorageFolder`, which puts **every** dimension — the
overworld included — under *dimensions/\/\/* 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 — it is `SavedDataStorage`, on [its own
page](../../reference/level-data-and-rules.md).
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 — and `SerializableChunkData.carvingMask` goes
the same way. A full chunk's entities live in *entities/*, one file 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 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 | nothing — no cooldown, and whatever the queue holds beyond 2000 tasks drains regardless of the tick budget |
| **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` 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` or `ImposterProtoChunk` |
| **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`* and shutdown do unload them,
the first because `MinecraftServer.saveAllChunks` suppresses `ServerLevel.noSave`
when *force* is set and the second because `ServerChunkCache.close` never
consults it at all. 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.
## A chunk nobody needs any more
```mermaid
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 PersistentEntitySectionManager
DM->>CM: the level climbs past ChunkLevel.MAX_LEVEL, updateChunkScheduling adds the key to toDrop
Note over CM: a later tick, in ServerChunkCache.tick's unload phase
CM->>CM: processUnloads moves the holder from updatingChunkMap to pendingUnloads
CM->>CH: scheduleUnload reads getSaveSyncFuture and hangs the unload task off it
CH-->>CM: the future completes, so the task is appended to unloadQueue
Note over CM: a later tick again, while the tick budget still says yes
CM->>CH: is getSaveSyncFuture still the same future — if not, scheduleUnload rearms on the new one
CM->>CM: pendingUnloads.remove of this exact holder — false if a ticket re-adopted it, and the task ends
CM->>CM: setLoaded false, then save — PoiManager.flush, tryMarkSaved, the proto-over-full guard
CM->>SCD: copyOf takes the snapshot, and a Worker-Main-n turns it into a CompoundTag
CM->>IOW: ChunkMap.write hands that encode future to IOWorker.store on the chunk lane
CM->>SL: ServerLevel.unload clears the block entities and the tick containers, then ThreadedLevelLightEngine drops the layers
SL->>PESM: later in the same level tick, processUnloads, then EntityStorage.storeEntities on the entities lane
IOW-->>CM: PendingStore.result completes, activeChunkWrites goes back down
```
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 a later step.
`PersistentEntitySectionManager.updateChunkStatus` saw the same level change
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**, the tag goes to the *entities*
lane, and those entities are removed with
`Entity.RemovalReason.UNLOADED_TO_CHUNK`. The filter runs before the removal,
not after it, so what it turns away — a `Player`, an `EnderDragonPart`, a
passenger, a vehicle carrying exactly one player — is neither written nor
removed.
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 ([lighting](lighting.md)).
## Why the server thread never waits, and the three times it does
`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 actually 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 unloaded and re-loaded a
second later 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.
Three places do 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` never 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. None of the three is on the save path, which is why the save path
costs a copy.
## Inside a region file
```mermaid
flowchart TD
A["IOWorker.storePendingChunk pops the oldest entry of pendingWrites"] --> B["RegionFileStorage.getRegionFile, an LRU of 256 open files"]
B --> C["RegionFile.getChunkDataOutputStream wraps a ChunkBuffer in the selected compressor, NbtIo writes into it"]
C --> D["closing the buffer back-patches the length and calls RegionFile.write"]
D --> E{"how many sectors"}
E -- "under 256" --> F1["RegionBitmap.allocate takes the first free run"]
F1 --> F2["the compressed chunk is written to those new sectors"]
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 or more" --> G1["one sector is allocated for a stub"]
G1 --> G2["the payload goes to a temp file in the same folder, and a five-byte stub with EXTERNAL_STREAM_FLAG is written to that sector"]
G2 --> G3["offsets and timestamps updated, then RegionFile.writeHeader"]
G3 --> G4["the temp file is moved onto c.X.Z.mcc, over the previous copy"]
G4 --> Z
```
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. 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, and it changes hands four times.
`ChunkMap.scheduleChunkLoad` starts with `IOWorker.loadAsync` on the IO
lane; `ChunkMap.readChunk` then hops to `Util.backgroundExecutor` under the
name *upgradeChunk* for `SimpleRegionStorage.upgradeChunkTag`, which is
where datafixing happens; `SerializableChunkData.parse` runs on the same
pool under *parseChunk*; 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 each
section's points of interest from its blocks. 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](chunk-generation-pipeline.md) takes over.
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.
## Questions players ask
**Does the game stall when it saves?** Only on a flush.
`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 is the only place where waiting for the disk is the point rather than
an accident, and it is what `/save-all flush` and `/stop` do.
**Why does lowering the tick rate not push out my autosave?** Because the
interval is wall clock. `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 — the typo is Mojang's); the very first interval is
`MinecraftServer.AUTOSAVE_INTERVAL` (6000 ticks).
`MinecraftServer.onTickRateChanged` recomputes it on every `/tick rate`, but
assigns the result only when it is **smaller** than the pending countdown,
so changing the rate can bring the next autosave forward and can never push
it back. [The server tick](../server/server-tick.md) has the rest of that
loop.
**Can a half-generated chunk overwrite my base?** The guard is best effort.
`ChunkMap.save` refuses to write a non-full chunk over a full one on disk —
but `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. Worse, `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. A proto
chunk still at `ChunkStatus.EMPTY` with no valid structure start is dropped
by the same block, and an `ImposterProtoChunk` never reaches any of it:
`ImposterProtoChunk.tryMarkSaved` and `ImposterProtoChunk.canBeSerialized`
both answer false — not because the wrapper defers to the `LevelChunk` it
wraps, which only `ImposterProtoChunk.markUnsaved` does, but because it
refuses to be serialised at all.
**Why is my *entities/* folder full of files with nothing in them?** It is
not — but 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.
**Do the file timestamps mean anything?** Not to the game.
`RegionFile.write` stamps each entry with epoch seconds
from `RegionFile.getTimestamp`, which reads `Util.getEpochMillis`, and
nothing ever reads the table back; the save cooldown in
`ChunkMap.nextChunkSaveTime` is monotonic `Util.getMillis`. Two clocks, and neither of them is game time.
## 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](tickets-and-loading.md) raises the level,
[chunk anatomy](chunk-anatomy.md) owns what `LevelChunkSection.copy` copies,
[lighting](lighting.md) owns the layers the unload throws away, [points of
interest](points-of-interest.md) owns the *poi/* store, [the server
tick](../server/server-tick.md) owns the budget every method here is handed,
[how a server dies](../server/how-a-server-dies.md) is the save that does
not happen, and [entity lifecycle](../entities/entity-lifecycle.md) 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`.*
---
# Scheduled ticks
> Verified against **Minecraft 26.2** · Part IV · A repeater's input goes high, and the two ticks before its output follows are one entry in a queue.
A repeater set to its shortest delay is not counting anything. When the wire
behind it changes, `DiodeBlock.neighborChanged` runs on the server thread,
notices that `DiodeBlock.POWERED` no longer matches the input, and books an
appointment — a `ScheduledTick` naming this `Block` at this `BlockPos`, due
two game ticks from now, at `TickPriority.HIGH` — and then forgets. Two ticks
later a scheduler the repeater has never heard of hands the position back to
`ServerLevel.tickBlock`, which checks that a repeater is still there and
calls the block again. Nearly everything the world does *later* is one of
these: a fluid flowing, a sapling sprouting after bonemeal, a pressure plate
releasing, a piece of amethyst budding. They all share two queues per chunk
and one rule that surprises everybody: **the queue dedups on type and
position alone, so a second tick for the same block — even a much sooner
one — is silently dropped.**
"Rescheduling moves the tick" is folklore. `ScheduledTick.UNIQUE_TICK_HASH`
hashes the position and compares the type by object identity — the trigger
tick, the priority and the sub-order are no part of it — and
`LevelChunkTicks.schedule` queues a tick only if its dedup set,
`LevelChunkTicks.ticksPerPosition`, did not already hold that pair. The
booking that loses is not queued, not merged and not logged, which is why so
many blocks ask before they book: `DiodeBlock`, `ComparatorBlock` and
`RedstoneTorchBlock` consult `LevelTickAccess.willTickThisTick`, and seven
blocks — `ObserverBlock`, `TargetBlock`, `LightningRodBlock`, `TripWireBlock`,
`SculkSensorBlock`, `DriedGhastBlock` and `SpeleothemBlock` — consult
`TickAccess.hasScheduledTick`.
## The cast
| class | what it decides | thread |
|---|---|---|
| `ScheduledTick` | the appointment itself — type, position, trigger tick, `TickPriority`, sub-order — and the comparisons the system sorts and dedups by | a record, no thread |
| `LevelChunkTicks` | one chunk's queue and its dedup set: whether a booking is new, and which of its ticks is next | Server |
| `LevelTicks` | the per-level scheduler: which chunks are due, which ticks run this level tick, and where the budget falls | Server |
| `ScheduledTickAccess` | the write side every block sees, so no block knows which container it is booking into | any — worldgen workers book through it |
| `ServerLevel` | when the drain runs, the per-chunk gate it runs under, and the type re-check that makes a tick cancellable | Server |
| `SavedTick` | the disk form: a *relative* delay in place of an absolute time | Server, written by the IO worker |
| `ProtoChunkTicks` | a generating chunk's bookings, all at delay zero | worldgen workers |
| `BlackholeTickAccess` | accept every booking and run nothing — the client's answer to the whole system, and also an `ImposterProtoChunk`'s | Render, and the server thread |
Those containers implement one small interface stack — `TickAccess`
(schedule, ask, count), `TickContainerAccess` per chunk, `LevelTickAccess` per
level, which also answers `LevelTickAccess.willTickThisTick`, and
`SerializableTickContainer` for one that can `SerializableTickContainer.pack`
itself for disk — which is why `LiquidBlock` and `RepeaterBlock` run unchanged
during generation, on a client that will never tick them, and on a server that
will.
## The pipeline, end to end
```mermaid
flowchart TD
B["a block books: ScheduledTickAccess.scheduleTick"] --> C["LevelAccessor.createTick — game time plus delay, a TickPriority, the next sub-order"]
C --> S["LevelTicks.schedule finds the chunk's container"]
S -- "no container registered for that chunk" --> DROP["Util.logAndPauseIfInIde — logged and dropped, never deferred"]
S --> D["LevelChunkTicks.schedule — queued only if ticksPerPosition did not hold this type and position"]
D --> I["onTickAdded: if the new tick is now the head, nextTickForContainer learns the earlier time"]
I --> W["waiting — one priority queue per chunk, in ScheduledTick.DRAIN_ORDER"]
W --> SC["LevelTicks.sortContainersToTick walks the index for containers due this tick"]
SC -- "chunk fails ServerLevel.isPositionTickingWithEntitiesLoaded" --> W
SC --> DR["LevelTicks.drainContainers polls the best container, LevelChunkTicks.poll frees the dedup slot"]
DR --> RL["rescheduleLeftoverContainers, which always runs and has work only when the budget MAX_SCHEDULED_TICKS_PER_TICK cut the drain short: the rest go back to the index, still due next tick"]
DR --> RUN["LevelTicks.runCollectedTicks hands each position and type to ServerLevel.tickBlock or ServerLevel.tickFluid"]
RUN -- "the block books again from inside its own run" --> D
RUN --> CL["LevelTicks.cleanupAfterTick empties toRunThisTick, containersToTick, alreadyRunThisTick"]
```
Everything below is one stage of that figure.
## Booking: a type, a position, a time and a tie-breaker
A block calls one of the `ScheduledTickAccess.scheduleTick` defaults with a
delay in ticks and, optionally, a `TickPriority`. The default asks the level
for `LevelAccessor.createTick`, which stamps the appointment with
`LevelAccessor.getGameTime` plus the delay, the priority (`TickPriority.NORMAL` if
none was given) and a fresh sub-order from `LevelAccessor.nextSubTickCount`,
then hands it to `ScheduledTickAccess.getBlockTicks` or
`ScheduledTickAccess.getFluidTicks`. Two type parameters, two parallel
worlds: a `Block` tick and a `Fluid` tick never meet, and `ServerLevel` owns
one `LevelTicks` of each.
The sub-order is the FIFO tie-breaker for two ticks at the same time and
priority, and it carries the one threading fact of this page.
`Level.subTickCount` is a plain counter incremented by
`Level.nextSubTickCount`, because a level's scheduler is touched only from the
server thread; `WorldGenRegion.subTickCount` is an atomic one, because
generation books ticks from the worker pool. **The drain is server-thread
only. Booking is not.**
`TickPriority` runs `TickPriority.EXTREMELY_HIGH` (−3) through
`TickPriority.NORMAL` (0) to `TickPriority.EXTREMELY_LOW` (3), lower first,
and it is not a queue-jump across time: a `TickPriority.LOW` tick due now
still beats a `TickPriority.EXTREMELY_HIGH` tick due next tick. Priority only
settles ties between ticks already due together.
Fluids are the scheduler's largest customer by a distance, and they exploit
the dedup key: `Fluids.WATER` and `Fluids.FLOWING_WATER` are different
registry objects, so one tick of each can be pending at one position. What
those ticks then *do* is [fluids](fluids.md).
> **For a 1.21-era reader.** `BlockBehaviour.updateShape` no longer takes a
> `LevelAccessor`. It takes a `LevelReader` and a separate
> `ScheduledTickAccess` — a small interface whose whole job is booking:
> `ScheduledTickAccess.createTick`, `ScheduledTickAccess.getBlockTicks`,
> `ScheduledTickAccess.getFluidTicks` and four `ScheduledTickAccess.scheduleTick`
> overloads that compose them.
## Where an appointment waits
Every `LevelChunk` owns exactly two containers, `LevelChunk.blockTicks` and
`LevelChunk.fluidTicks`, and they are the only place a pending tick ever lives
([chunk anatomy](chunk-anatomy.md)): a priority queue,
`LevelChunkTicks.tickQueue`, in `ScheduledTick.DRAIN_ORDER`, beside the dedup
set that decides what gets into it.
`LevelTicks` never scans those queues looking for work. It keeps
`LevelTicks.allContainers`, chunk key to container, and
`LevelTicks.nextTickForContainer`, chunk key to the earliest trigger time that
chunk holds, defaulting for an unknown chunk to the largest possible long. The
index is maintained by `LevelTicks.chunkScheduleUpdater`, the callback every
container is handed through `LevelChunkTicks.setOnTickAdded` when it
registers, and it fires only when the tick just added *is* the container's new
head. **A chunk with nothing due costs one map entry and one comparison per
level tick, and a chunk with an empty queue costs nothing at all** — which is
what makes tens of thousands of loaded chunks affordable.
Registration follows the chunk's life exactly: `ChunkStatusTasks.full` calls
`LevelChunk.registerTickContainerInLevel` (`LevelTicks.addContainer`, both
types), and `ServerLevel.unload` calls
`LevelChunk.unregisterTickContainerFromLevel` → `LevelTicks.removeContainer`,
which also drops the callback so an orphaned container can no longer touch the
index. In between, a tick aimed at a chunk with no registered container is
neither deferred nor queued: `LevelTicks.schedule` finds nothing, calls
`Util.logAndPauseIfInIde`, and the appointment ceases to exist. `ClientLevel`
short-circuits all of it — `ClientLevel.getBlockTicks` and
`ClientLevel.getFluidTicks` return `BlackholeTickAccess.emptyLevelList`, which
accepts every booking, answers false to every question and runs nothing, so
everything a client sees of a flowing fluid or a firing repeater arrives as
block-update packets. (`ContainerSingleItem` also lives in `world/ticks`, a
one-slot inventory interface with nothing to do with ticks — a packaging
accident, noted only so it does not confuse you.)
## What one drain actually does
`ServerLevel.tick` calls `LevelTicks.tick` twice in its *tickPending*
section, blocks first and then fluids, each with the current game time and a
budget of `ServerLevel.MAX_SCHEDULED_TICKS_PER_TICK`, 65536 — a budget per
call, so 65536 block ticks *and* 65536 fluid ticks ([the level
tick](../server/server-level-tick.md)). The whole section is skipped in a
debug world and whenever `TickRateManager.runsNormally` is false. Each call
is three phases.
**Collect.** `LevelTicks.sortContainersToTick` walks the index, not the
containers: it leaves a future entry alone, deletes one whose container has
vanished or emptied, corrects one whose head is later than the index claimed,
and tests a genuinely due one against `LevelTicks.tickCheck`. That predicate
is `ServerLevel.isPositionTickingWithEntitiesLoaded`, asked about the
**chunk**, not the position, and true only when the chunk is in
`DistanceManager.inBlockTickingRange`, its
`ChunkHolder.getTickingChunkFuture` has already succeeded, and
`PersistentEntitySectionManager.areEntitiesLoaded` holds ([tickets and
loading](tickets-and-loading.md)). A chunk that fails keeps its index entry
untouched and is asked again next tick — its ticks are late, never lost — and
one that passes moves into `LevelTicks.containersToTick`, a priority queue of
*containers* ordered by `LevelTicks.CONTAINER_DRAIN_ORDER`, on their heads.
`LevelTicks.drainContainers` polls the best container, takes one tick and
hands to `LevelTicks.drainFromCurrentContainer`, which keeps pulling from that
same container while its next tick is still due and still beats the next-best
container's head — containers are re-heaped only when the winner stops
winning. A container that is overtaken, or that still has something due when the
budget is spent, goes back into the container queue; one merely overdue goes
back to the index; one drained empty goes to neither. And
`LevelTicks.rescheduleLeftoverContainers` returns whatever the budget cut off
to the index at its head's trigger time, already in the past — which gets it
*collected* next tick but buys it no place in the order, because
`LevelTicks.CONTAINER_DRAIN_ORDER` compares priority and sub-order and has no
time term at all.
**Run.** `LevelTicks.runCollectedTicks` drains `LevelTicks.toRunThisTick` in
order, moving each entry to `LevelTicks.alreadyRunThisTick` and handing its
position and type to `ServerLevel.tickBlock` or `ServerLevel.tickFluid`. Both
re-read the world there and run `BlockBehaviour.BlockStateBase.tick` or
`FluidState.tick` only if the block or fluid is still the one the appointment
named. **A tick is a promise to a type**, and that check is the whole of
cancellation for anything a block does: break the block and its pending ticks
evaporate with no cancellation code anywhere. The only code that removes a
pending tick outright is bulk — `LevelChunkTicks.removeIf`, through
`LevelTicks.clearArea`, forty lines below. **Clean up** is
`LevelTicks.cleanupAfterTick`, emptying all four working collections including
`LevelTicks.toRunThisTickSet`, which is built lazily and only if somebody
actually asks `LevelTicks.willTickThisTick`.
### The comparisons, and which is used where
| comparison | compares | used by |
|---|---|---|
| `ScheduledTick.DRAIN_ORDER` | trigger tick, then priority, then sub-order | the priority queue inside every `LevelChunkTicks` |
| `ScheduledTick.INTRA_TICK_DRAIN_ORDER` | priority, then sub-order — **no time term** | `LevelTicks.drainFromCurrentContainer`, comparing two already-due ticks |
| `LevelTicks.CONTAINER_DRAIN_ORDER` | the same, applied to two containers' heads | `LevelTicks.containersToTick` |
| `LevelChunkTicks.SUB_TICK_ORDERING` | sub-order alone | `LevelChunkTicks.pack`, on save only |
| `ScheduledTick.UNIQUE_TICK_HASH` | not an ordering — identity on type and position | `LevelChunkTicks.ticksPerPosition` and `LevelTicks.toRunThisTickSet` |
Time drops out of the second comparison because a container reaches the
collect queue only once its head is already due, and once everything in play
is due, time no longer discriminates.
Separating the phases has two consequences. A tick booked *during* the run
phase lands in its container after collect has finished, so it waits for a
later drain even at delay zero — the one exception being a block tick that
books a **fluid** tick at delay zero, which the fluid drain, running
afterwards in the same `ServerLevel.tick`, still catches. And the dedup slot
is released by `LevelChunkTicks.poll` during *collect*, not at run, so a tick
may book its own successor from inside its own run: exactly how a fluid keeps
flowing and how a repeater arms its turn-off.
That the run list outlives the run is what makes bulk edits correct across the
phase boundary. `LevelTicks.copyAreaFrom`, called by `/clone` in
`CloneCommands`, harvests matching ticks from `LevelTicks.alreadyRunThisTick`,
from `LevelTicks.toRunThisTick` *and* from the containers in the area, then
re-bases every sub-order above the highest it found so copies keep their
relative order without colliding with the originals. `LevelTicks.clearArea`
does the mirror image for the gametest framework
(`StructureUtils.clearSpaceForStructure`, `GameTestInfo`). Both touch block
ticks only — nothing in the game copies or clears fluid ticks by area.
## A repeater, appointment by appointment
A repeater with `RepeaterBlock.DELAY` 1 — `RepeaterBlock.getDelay` doubles
it, so two game ticks — with a redstone wire behind it that goes to 15 and
then back to 0 one tick later.
```mermaid
sequenceDiagram
participant SL as ServerLevel
participant RB as RepeaterBlock
participant LTs as LevelTicks
participant LCTs as LevelChunkTicks
participant LC as LevelChunk
SL->>RB: neighborChanged — the wire behind went to 15
RB->>RB: DiodeBlock.checkTickOnNeighbor — not locked, POWERED false, shouldTurnOn true
RB->>LTs: willTickThisTick at this position? no
RB->>SL: scheduleTick — delay 2, TickPriority.HIGH
SL->>SL: createTick — gameTime plus 2, HIGH, nextSubTickCount
SL->>LTs: schedule
LTs->>LCTs: schedule — ticksPerPosition accepts, tickQueue takes it
LCTs-->>LTs: onTickAdded — nextTickForContainer learns gameTime plus 2
Note over SL,RB: next tick, the wire drops to 0. checkTickOnNeighbor finds POWERED false and shouldTurnOn false, so it books nothing and cancels nothing
Note over SL,LC: two ticks after the booking, ServerLevel.tick, tickPending, blockTicks
LTs->>LCTs: poll — the tick leaves the queue and the dedup set
LTs->>SL: tickBlock at this position, for Blocks.REPEATER
SL->>RB: still a repeater here, so BlockBehaviour.BlockStateBase.tick
RB->>SL: setBlock POWERED true, update flags 2
SL->>LC: setBlockState, then DiodeBlock.onPlace
RB->>SL: updateNeighborsInFront — the block it powers, and that block's other neighbours
RB->>LTs: shouldTurnOn is false now, so book the turn-off at TickPriority.VERY_HIGH
```
**A repeater almost never books at `TickPriority.NORMAL`.**
`DiodeBlock.checkTickOnNeighbor` picks `TickPriority.HIGH` to turn on,
`TickPriority.VERY_HIGH` to turn off and `TickPriority.EXTREMELY_HIGH` when
`DiodeBlock.shouldPrioritize` holds — when the block it powers is itself a
diode that is not pointing straight back at it. So a diode's turn-off beats
another's turn-on due on the same tick, and a diode feeding a diode beats
both. The single `TickPriority.NORMAL` booking a repeater makes is
`DiodeBlock.setPlacedBy`, delay 1, when you place it into a powered spot.
**The pending appointment is immune to the input changing.**
`DiodeBlock.checkTickOnNeighbor` books only when the *current*
`DiodeBlock.POWERED` disagrees with the *current* input, and nothing anywhere
removes a booked tick. A pulse shorter than the delay therefore does not
cancel the repeater: `DiodeBlock.tick` finds `DiodeBlock.POWERED` false, turns
it on anyway, and — because the input is already gone — books its own turn-off
one delay later. Pulse extension is two entries in this queue.
**Nothing here uses `Block.UPDATE_NEIGHBORS`.** `DiodeBlock.tick` writes with
`Block.UPDATE_CLIENTS` alone, and the signal leaves through
`DiodeBlock.onPlace` — which `LevelChunk.setBlockState` runs on the server for
any write without `Block.UPDATE_SKIP_ON_PLACE` — calling
`DiodeBlock.updateNeighborsInFront`. The rest is [diodes and the
observer](../blocks/diodes-and-observers.md).
## The other kind of turn: random ticks
The appointment book is one of two ways a block gets a turn, and the contrast
is what defines it: a random tick is booked by nobody, aimed at no block, and
carries no promise.
It also reaches a different set of chunks. `ServerChunkCache.tickChunks` reads
`GameRules.RANDOM_TICK_SPEED` — default 3, minimum 0 — **once per level
tick**, then walks `ChunkMap.forEachBlockTickingChunk`, which despite its name
is `DistanceManager.forEachEntityTickingChunk` filtered to chunks with a live
`ChunkHolder.getTickingChunk`. The scheduled-tick gate,
`ServerLevel.isPositionTickingWithEntitiesLoaded`, reads the wider
block-ticking radius instead, so random ticks stop one ring sooner than
scheduled ticks do.
Inside such a chunk, `ServerLevel.tickChunk` skips every section where
`LevelChunkSection.isRandomlyTicking` is false — a pair of counters,
`LevelChunkSection.tickingBlockCount` and
`LevelChunkSection.tickingFluidCount`, maintained on every block write, so
solid stone is skipped without one position being generated. In each surviving
section it picks that many positions and rolls
`BlockBehaviour.BlockStateBase.randomTick` where
`BlockBehaviour.BlockStateBase.isRandomlyTicking` — a flag baked into the
state at `BlockBehaviour.BlockStateBase.initCache` from
`BlockBehaviour.Properties.randomTicks` — and then, separately,
`FluidState.randomTick` where `FluidState.isRandomlyTicking`.
**Lava gets its random tick twice.** `LiquidBlock.isRandomlyTicking` and
`LiquidBlock.randomTick` both delegate straight to the fluid, so a chosen lava
position runs `LavaFluid.randomTick` once through the block branch of that
loop and again through the fluid branch. It is the only fluid it happens to:
`Fluid.isRandomlyTicking` is false by default and `LavaFluid.isRandomlyTicking`
is the sole override. Water is never randomly ticked at all — every inch of
its motion is a scheduled tick. The same number also drives the ice-and-snow
pass, so zeroing the rule freezes crops, fire, leaf decay and precipitation
together ([the level tick](../server/server-level-tick.md)).
## Appointments that survive a restart
A `SavedTick` stores a **relative** delay rather than an absolute time, so a
world closed and reopened a month later still fires its ticks on schedule:
`ScheduledTick.toSavedTick` subtracts the current game time on the way out and
`SavedTick.unpack` adds the new one on the way back. `LevelChunkTicks.pack`
writes the pending list first and then the live queue sorted by
`LevelChunkTicks.SUB_TICK_ORDERING`, and `SerializableChunkData` stores the two
lists under *block_ticks* and *fluid_ticks* through `SavedTick.codec` ([chunk
storage](chunk-storage.md)). On the way in,
`SavedTick.filterTickListForChunk` discards any saved tick whose position is
not in the chunk being loaded.
Coming back is two-stage. `SerializableChunkData` builds a `LevelChunkTicks`
for a chunk at `ChunkStatus.FULL` and a `ProtoChunkTicks` for one below it, and
the `LevelChunkTicks` constructor holds the saved list as
`LevelChunkTicks.pendingTicks` — *not* in the queue — while pre-seeding the
dedup set from it, so a fresh booking cannot double up a saved one that is not
unpacked yet. The queue fills only when `ChunkMap.prepareTickingChunk` reaches
`ServerLevel.startTickingChunk` → `LevelChunk.unpackTicks` →
`LevelChunkTicks.unpack`, which counts sub-orders up from minus the list's
length. **Every unpacked tick gets a negative sub-order**, and
`Level.subTickCount` starts each session at zero and only rises, so a loaded
tick always sorts before anything this session booked at the same time and
priority.
Generation takes the same road. A `ProtoChunk` holds `ProtoChunkTicks`, which
records everything at delay **zero** and dedups under
`SavedTick.UNIQUE_TICK_HASH`, and `WorldGenRegion` exposes both of its
containers as a `WorldGenTickAccess` — a router that finds the right chunk per
position and answers `LevelTickAccess.willTickThisTick` with a flat false. At
promotion `ProtoChunk.unpackBlockTicks` turns that list into a
`LevelChunkTicks` in the pending state, and it becomes real at the same
`ServerLevel.startTickingChunk` a loaded chunk goes through — due immediately,
at the game time the chunk started ticking.
## Questions players ask
**I rescheduled the tick for sooner and nothing changed. Why?** Because
`LevelChunkTicks.schedule` dedups on type and position only, and the first
booking wins. No block moves or cancels a pending tick — only `/clone` and the gametest
framework do, in bulk, through `LevelTicks.copyAreaFrom` and
`LevelTicks.clearArea`. So ask
`TickAccess.hasScheduledTick` whether one is already booked, or
`LevelTickAccess.willTickThisTick` whether one is about to run in this very
level tick, which reads the already-collected list that
`LevelTicks.hasScheduledTick` can no longer see.
**Why did breaking one block stop a machine that was two ticks from firing?**
A tick names a type. The appointment stays in the queue and still runs, but
`ServerLevel.tickBlock` re-reads the position, finds a different block, and
runs it to nothing.
**Does `/tick freeze` stop scheduled ticks?** Yes, and among the tick
commands it is the only one that does — a debug world skips the section too.
`TickRateManager.runsNormally` returns `TickRateManager.runGameElements`,
recomputed every tick as *not frozen, or stepping* — so `/tick step` runs the
*tickPending* section normally for the ticks it steps, and `/tick sprint`
clears the freeze flag outright for the length of the sprint
(`ServerTickRateManager.requestGameToSprint`) and puts back whatever it found
when the sprint ends.
**Where do my ticks go when a chunk stops ticking?** Nowhere. They sit in the
chunk's own queue, the index entry is left untouched, and the moment the chunk
is block-ticking again they are all collected in one drain. If the chunk
unloads first they are written to disk with it. The only appointment actually
lost is one booked into a chunk with no registered container.
**Why does lava set things alight faster than the number of random ticks
suggests?** Because a chosen lava position runs `LavaFluid.randomTick` twice
per selection, once as a block and once as a fluid.
## Where to look
`ScheduledTick` · `ScheduledTick.UNIQUE_TICK_HASH` ·
`ScheduledTick.DRAIN_ORDER` · `TickPriority` · `ScheduledTickAccess.scheduleTick` ·
`LevelAccessor.createTick` · `LevelTicks.schedule` · `LevelChunkTicks.schedule` ·
`LevelChunkTicks.poll` · `LevelTicks.tick` · `LevelTicks.sortContainersToTick` ·
`LevelTicks.drainContainers` · `LevelTicks.runCollectedTicks` ·
`ServerLevel.tickBlock` · `ServerLevel.isPositionTickingWithEntitiesLoaded` ·
`LevelChunk.registerTickContainerInLevel` · `LevelChunkTicks.unpack` ·
`SavedTick` · `ProtoChunkTicks` · `WorldGenTickAccess` · `BlackholeTickAccess` ·
`ServerLevel.tickChunk` · `LevelChunkSection.isRandomlyTicking` ·
`DiodeBlock.checkTickOnNeighbor` · `DiodeBlock.tick` · `LevelTicks.copyAreaFrom`
---
*Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes `tools/verify_names.py`.*
---
# Fluids
> Verified against **Minecraft 26.2** · Part IV · A bucket of water is emptied on flat stone and spreads one step.
A player holding a water bucket clicks the top of a stone block. Their own
client places the source at once, on a prediction, and it will sit there doing
nothing for the rest of the session. The server places the same block and then
also does nothing — for five ticks. When the appointment falls due, the source
looks down, finds stone, and hands the decision to four independent searches,
one per horizontal direction, each walking out through the surrounding blocks
looking for somewhere the water could fall. On flat stone all four fail in
exactly the same way, so all four tie and the water goes out evenly. Break the
symmetry and it does not, because **water turns toward a hole four steps past
the block it is about to fill, every side running its own depth-first search —
and a side the water is not allowed to replace still votes on where the rest of it
goes.**
This page is the fluid model: the two registry objects behind each fluid, the
block that carries them, what one fluid tick decides and where it sends the
result. The queue that appointment sits in — booked, deduped, drained, saved and
reloaded — is [scheduled ticks](scheduled-ticks.md), which this page borrows
whole and never explains.
## The cast
| class | what it decides | thread |
|---|---|---|
| `Fluid` | the registry object: its `Fluid.stateDefinition`, and every per-fluid number as an overridable method | built at bootstrap, read anywhere |
| `FluidState` | one interned combination of `FlowingFluid.FALLING` and `FlowingFluid.LEVEL` — what a block reports and what a tick names | immutable |
| `FlowingFluid` | the whole algorithm: what a position should hold, whether to go down or sideways, and which sides win | Server |
| `WaterFluid` | water's numbers, and whether two sources may make a third | Server |
| `LavaFluid` | lava's numbers, and the one override that turns liquid into rock | Server |
| `LiquidBlock` | the block form of a fluid, and where a fluid tick is booked from when the fluid is the block | Server |
| `SimpleWaterloggedBlock` | that a stair can be full of water without being a water block | Server |
| `BucketItem` | where a source comes from, and the one attribute that stops it arriving | Server, with a client prediction |
## Two registry objects, one substance
`Fluid` is to `FluidState` what `Block` is to `BlockState`, and it is literally
the same machinery: `StateHolder`, a `StateDefinition` built in the constructor,
and one interned instance per combination of properties, so two `FluidState`s
can be compared by identity. `FlowingFluid` puts `FlowingFluid.FALLING` on every
state it defines, and the flowing subclasses — `WaterFluid.Flowing`,
`LavaFluid.Flowing` — add `FlowingFluid.LEVEL`
(`BlockStateProperties.LEVEL_FLOWING`, 1 to 8) on top of it. The static
initialiser in `Fluids` then walks `BuiltInRegistries.FLUID` and pours every
state of every fluid into `Fluid.FLUID_STATE_REGISTRY`, the one global id table.
**Thirty-seven** — every fluid state in the game: one for `Fluids.EMPTY`, and
eighteen each for water and lava (two from the source object, sixteen from its
flowing twin).
Each fluid being *two* registry objects is not bookkeeping. `Fluids.WATER` is a
`WaterFluid.Source` whose `WaterFluid.Source.getAmount` returns 8 and whose
`WaterFluid.Source.isSource` returns true without consulting any property;
`Fluids.FLOWING_WATER` is a `WaterFluid.Flowing` whose
`WaterFluid.Flowing.getAmount` reads `FlowingFluid.LEVEL` and whose
`WaterFluid.Flowing.isSource` returns false without consulting anything either. `WaterFluid.isSame` answers true for either, which is how
the algorithm treats water as one substance no matter which object it is
holding. The scheduler does not: a scheduled tick is keyed on the fluid object,
so `Fluids.WATER` and `Fluids.FLOWING_WATER` are two different appointments and
one of each can be pending at a single position ([scheduled
ticks](scheduled-ticks.md)).
The amount is also the height. `FluidState.getOwnHeight` delegates to
`FlowingFluid.getOwnHeight`, which divides the amount by *nine* — so a full
source stands 8/9 of a block tall, and `FlowingFluid.getHeight` has to
special-case a same-fluid block overhead and return a flat 1 so that a submerged
column is not full of seams.
## The block underneath the water
Nothing in the world stores a `FluidState`. A chunk section stores `BlockState`s
([chunk anatomy](chunk-anatomy.md)), and
`BlockBehaviour.BlockStateBase.getFluidState` returns a field —
`BlockBehaviour.BlockStateBase.fluidState`, filled once by
`BlockBehaviour.BlockStateBase.initCache` at bootstrap by asking the block.
Reading the fluid at a position is a block lookup and a field read, which is why
the flow algorithm can afford to ask hundreds of times a tick.
For water and lava the block is `LiquidBlock`, whose single property is
`LiquidBlock.LEVEL` (`BlockStateProperties.LEVEL`, 0 to 15). Its constructor
builds `LiquidBlock.stateCache`, nine `FluidState`s: index 0 is the source, 1
through 7 are flowing at amount 8 minus the index, and index 8 is flowing at
amount 8 with `FlowingFluid.FALLING` set. `LiquidBlock.getFluidState` clamps the
level to 8, so block levels 8 through 15 all read back as *falling and full*,
and `FlowingFluid.getLegacyLevel`, the encoder going the other way, is
correspondingly lossy for falling flows — which costs nothing, because
`FlowingFluid.getNewLiquid` only ever produces a falling state at amount 8.
Waterlogging runs the other way round: a block that implements
`SimpleWaterloggedBlock` reports water of its own accord.
`SimpleWaterloggedBlock.canPlaceLiquid` accepts `Fluids.WATER` and nothing else,
and `SimpleWaterloggedBlock.placeLiquid` sets `BlockStateProperties.WATERLOGGED`
and books the water tick itself rather than leaving it to `LiquidBlock.onPlace`.
The state it reports is a *source*, which is why a waterlogged stair is never
drained by a fluid tick: the first half of `FlowingFluid.tick` is skipped
entirely for a source. Exactly one block in the game reports a source that is
also falling — `WaterloggedTransparentBlock`, the copper grate — and
`FlowingFluid.FALLING` is the flag that lets `FlowingFluid.getFlow` add a
downward component to the current it pushes entities with.
## A bucket, five ticks, four neighbours
```mermaid
sequenceDiagram
participant BI as BucketItem
participant SL as ServerLevel
participant LC as LevelChunk
participant LB as LiquidBlock
participant LTs as LevelTicks
participant FF as FlowingFluid
participant CPL as ClientPacketListener
BI->>SL: emptyContents, then setBlock of the source with the flag word 11
SL->>LC: setBlockState, the section write and the counters
LC->>LB: onPlace, server side and without UPDATE_SKIP_ON_PLACE
LB->>SL: scheduleTick for Fluids.WATER, five ticks out
SL->>LTs: schedule into this chunk's fluid container
SL->>CPL: one ClientboundBlockUpdatePacket at broadcast time
Note over BI,CPL: five ticks later, inside ServerLevel.tick
LTs->>SL: the drain hands the position back to tickFluid
SL->>FF: FluidState.tick, and a source skips getNewLiquid
FF->>FF: spread tries down first, stone below refuses
FF->>FF: spreadToSides, then getSpread scores the four sides
FF->>SL: spreadTo, setBlock of flowing water at amount 7, four times
SL->>LB: onPlace on each new block
LB->>LTs: each books its own tick, because spreadTo schedules nothing
SL->>LB: the shape pass reaches the source, updateShape books it again
SL->>CPL: four more block updates, and nothing else
```
`BucketItem.use` picks the position, then `BucketItem.emptyContents` asks two
questions before it places anything. Is the block there a `LiquidBlockContainer`
that will take water — that is the waterlogging path — and does
`EnvironmentAttributes.WATER_EVAPORATES` hold at this position, read through
`EnvironmentAttributeReader.getValue`? The Nether's dimension type sets that
attribute, so there the bucket plays a hiss, throws eight smoke particles and
returns success having placed nothing: this page's whole trace never happens in
the Nether ([environment attributes](environment-attributes-and-timelines.md)).
The first question only decides whether the position is a legal target; the
evaporation branch returns before the waterlogging is actually done, so a
bucket on a Nether stair hisses too.
On overworld stone it destroys and drops whatever was there and calls
`Level.setBlock` with the source's `FluidState.createLegacyBlock` and the flag
word 11 — `Block.UPDATE_NEIGHBORS`, `Block.UPDATE_CLIENTS`,
`Block.UPDATE_IMMEDIATE` ([blocks and states](../blocks/blocks-and-states.md),
[items and stacks](../items/items-and-stacks.md)).
The appointment is the *block's* doing, not the fluid's.
`LevelChunk.setBlockState` writes the section and then, server-side and unless
`Block.UPDATE_SKIP_ON_PLACE` is set, calls `LiquidBlock.onPlace`, which asks
`LiquidBlock.shouldSpreadLiquid` (always true for water) and schedules a tick
for `Fluids.WATER` at `WaterFluid.getTickDelay`, five ticks out. `LiquidBlock.neighborChanged` and
`LiquidBlock.updateShape` book the same appointment when something next door
moves, and so does `SimpleWaterloggedBlock.placeLiquid` — but the habit is not
`LiquidBlock`'s alone. Sixty-three call sites across fifty-six classes schedule a
fluid tick, because every waterloggable block books water's tick from its own
override of `BlockBehaviour.updateShape`, `WaterloggedTransparentBlock`
included. Nothing in `FlowingFluid`
books its own future except the single line in `FlowingFluid.tick` that follows
a state change.
The client is told none of this, because there is nothing to tell. The placing
player's client ran `BucketItem.use` itself inside
`MultiPlayerGameMode.startPrediction`, so the source appears locally with no
round trip, and the blocks the water later touches arrive the way any
run of block changes does: one `ClientboundBlockUpdatePacket` when a section
changed exactly one block that tick, and a single
`ClientboundSectionBlocksUpdatePacket` for the rest — which, for a spreading
flow, is the ordinary case. `LevelChunk.setBlockState` skips
`LiquidBlock.onPlace` off the server, and `ClientLevel.getFluidTicks` hands out
a `BlackholeTickAccess` that accepts every appointment and keeps none. No client ever runs the
spread. What it does run is `FluidState.animateTick` from
`ClientLevel.doAnimateTick` — ambient sound and particles, with
`Fluid.getDripParticle` fetched separately just after — and
`FluidState.getFlow`, which both the fluid mesher and shared entity physics
need to know which way the surface leans. Flowing water on a client is a stream
of block updates and a direction.
Five ticks on, `LevelTicks` drains the fluid queue and hands the position back
through `ServerLevel.tickFluid`, which re-reads the block and fires
`FluidState.tick` only if the fluid still there is the one the tick named. It
is, and because it is a source, `FlowingFluid.tick` goes straight to
`FlowingFluid.spread`.
## What one fluid tick decides
For anything that is *not* a source, the first half of `FlowingFluid.tick`
answers a question with nothing to do with spreading: what should this position
hold, given its surroundings? `FlowingFluid.getNewLiquid` answers it, and the
order of its three branches is most of a fluid's character.
```mermaid
flowchart TD
T["FlowingFluid.tick on a state that is not a source"] --> SCAN["one pass over the four horizontal neighbours reachable through canPassThroughWall, keeping the highest same-fluid amount and counting the sources"]
SCAN --> B1{"two or more sources, canConvertToSource allows it, and the block below is solid or a source of this fluid"}
B1 -- yes --> SRC["a source, not falling"]
B1 -- no --> B2{"the same fluid directly above, through the same wall test"}
B2 -- yes --> FALL["flowing at amount 8, falling"]
B2 -- no --> B3["the highest neighbour amount minus getDropOff"]
B3 --> Z{"zero or less"}
Z -- yes --> EMPTY["Fluids.EMPTY"]
Z -- no --> FLOW["flowing at that amount, not falling"]
SRC --> CMP{"the same state that is already here"}
FALL --> CMP
FLOW --> CMP
EMPTY --> AIR["set plain air with flags 3 and schedule nothing, which is how a flow dies"]
AIR --> SPREAD
CMP -- yes --> KEEP["write nothing, schedule nothing"]
CMP -- no --> SET["set the new state with flags 3 and book a tick getSpreadDelay out"]
KEEP --> SPREAD["then FlowingFluid.spread, which returns at once on an empty state"]
SET --> SPREAD
```
The scan that feeds the branches counts a neighbour only if
`FlowingFluid.canPassThroughWall` says the face between the two positions is
open, so a pane of glass between two source blocks is enough to stop them making
a third.
The first branch is source conversion, and it is where infinite water lives: two
source neighbours, a yes from `WaterFluid.canConvertToSource` — which reads
`GameRules.WATER_SOURCE_CONVERSION`, true by default ([game
rules](../../reference/gamerules.md)) — and something solid
(`BlockBehaviour.BlockStateBase.isSolid`) or another source of the same fluid
*directly below the position being filled*. `LavaFluid.canConvertToSource` reads
`GameRules.LAVA_SOURCE_CONVERSION`, false by default, so infinite lava is one
rule away rather than a property of lava. The second branch is the same fluid
overhead, which always produces a falling state at full amount, however little
is actually falling past. The fallback is the highest same-fluid neighbour minus
`FlowingFluid.getDropOff` — one for water, two for lava outside the Nether — and
zero or less is empty.
Two outcomes at the bottom of the figure matter more than the branches above
them. **Empty means the block becomes plain air and nothing is rescheduled**: a
fluid tick that decides a position should be dry books no follow-up, and the
appointment book simply forgets it. And when the answer equals the state already
there — the ordinary case for a settled flow — neither the write nor the booking
happens, so a stable pool that is ticked one last time falls silently off the
books.
## Down first, then sideways
`FlowingFluid.spread` tries below before anything else. If the block there can
hold the fluid, its existing `FluidState.canBeReplacedWith` allows the swap and
the floor between them is open, the fluid falls: `FlowingFluid.spreadTo` fills
the position below and `FlowingFluid.spread` returns — *unless*
`FlowingFluid.sourceNeighborCount` finds three or more source neighbours around
the position that is pouring, in which case it spreads sideways as well. That is
the lip of a large pool draining into a hole: it pours down and keeps flooding
outward in the same tick.
Under our bucket the block below is stone, so nothing passes. Because the state
is a source, `FlowingFluid.spreadToSides` runs anyway (the other way in is a
position whose floor is not a `FlowingFluid.isWaterHole`). Its first act is a
gate, not a value: the amount minus the drop-off, or a flat 7 for a falling
state, and if that is zero or less nothing spreads at all. What each side
actually *receives* is whatever `FlowingFluid.getNewLiquid` computes for that
side from scratch.
### Four searches, and the losers still vote
`FlowingFluid.getSpread` returns a map from direction to the state that
direction would get, and building it is the most expensive thing a fluid does. A
direction is a candidate if `FlowingFluid.canMaybePassThrough` — not already a
source of this fluid, a block that can hold fluid at all, and not walled off —
and if the state it would receive can be held there. Each candidate is then
scored by its distance to the nearest *hole*, where a hole is
`FlowingFluid.isWaterHole`: a position whose floor will let the fluid through
and whose lower neighbour will take it.
A candidate that is itself a hole scores 0 with no search at all. Otherwise
`FlowingFluid.getSlopeDistance` searches outward from it: a depth-first walk
that tries the three horizontal directions other than the one it arrived from,
returns the pass number the moment it finds a hole, and recurses only while the
pass is still below `WaterFluid.getSlopeFindDistance` — 4 for water, 2 for lava,
4 for lava in the Nether. A search that finds nothing returns 1000. So a side's
score is 0 to 4, or 1000 for *nowhere to fall from here*, and a score of 4 means
the hole is four steps past the block the water is about to fill. Three
branches, four deep, is up to a hundred and twenty positions per side;
`FlowingFluid.SpreadContext`, built once per `FlowingFluid.getSpread` call, caches block
states and hole answers in two maps keyed by the candidate's x and z offset from
the origin packed into a short, so the overlap between the four searches is paid
for once.
The winners are the sides holding the minimum score. A better score clears
everything collected so far and a tie is kept, which is why on flat stone all
four directions score 1000, all four survive, and the water goes out evenly. And
here is the part nobody expects: **the running minimum is updated by every
scored candidate, but only a candidate whose existing fluid passes
`FluidState.canBeReplacedWith` is put in the map.**
`WaterFluid.canBeReplacedWith` allows replacement only from directly above and
only by something that is not water, so a neighbour that is *already* water
refuses every sideways spread — and still clears the map, and still lowers the
minimum. One unreplaceable near neighbour can therefore suppress every other
direction. It is also why a pool that has finished spreading is quiet: the
source's four neighbours all refuse, the map comes back empty, and
`FlowingFluid.spreadToSides` places nothing.
`FlowingFluid.spreadTo` does the placing and is deliberately dumb. A
`LiquidBlockContainer` gets `LiquidBlockContainer.placeLiquid`; anything else
that is not air — air, the usual target, is skipped — has
`FlowingFluid.beforeDestroyingBlock` run over it —
`WaterFluid.beforeDestroyingBlock` drops the block's items through
`Block.dropResources`, `LavaFluid` plays a fizz — and then
`LevelWriter.setBlock` with flags 3. It schedules nothing at all. Every new
flowing block books its own tick from its own `LiquidBlock.onPlace`, and the
shape-update pass at the tail of `Level.setBlock` reaches back to the source,
whose `LiquidBlock.updateShape` books the source again.
## How a flow stops
Two ways, and only one of them is dramatic. The quiet one is the gate. Water
leaves a source at amount 7 and loses one per block, so the seventh block out
holds amount 1, its `FlowingFluid.spreadToSides` gate computes zero, and the front simply
stops. Behind it every block's tick keeps getting the same answer from
`FlowingFluid.getNewLiquid`, writes nothing and books nothing, and the pool
falls off the books until a `LiquidBlock.neighborChanged` or a
`LiquidBlock.updateShape` wakes it.
The loud one is what happens when the source is taken back.
`LiquidBlock.pickupBlock` swaps it for air — level 0 only, which is why you
cannot fill a bucket from flowing water — the flag word runs
`Level.updateNeighborsAt`, each neighbouring `LiquidBlock.neighborChanged` books
a fluid tick, and five ticks later those blocks compute
`FlowingFluid.getNewLiquid` with no source in reach. Only the outermost block
of the flow comes back empty and turns to air; every ring behind it comes back
one level *lower* than the ring beyond, writes that, and books itself again. So
the pool re-levels repeatedly on its way out, one ring per tick delay, and each
ring's last act is to schedule nothing.
## Lava is water with worse numbers, and three exceptions
| | water | lava | lava under `EnvironmentAttributes.FAST_LAVA` |
|---|---|---|---|
| `Fluid.getTickDelay` | 5 | 30 | 10 |
| `FlowingFluid.getDropOff` | 1 | 2 | 1 |
| `FlowingFluid.getSlopeFindDistance` | 4 | 2 | 4 |
| how far a flow reaches | 7 blocks | 3 blocks | 7 blocks |
| source conversion | `GameRules.WATER_SOURCE_CONVERSION`, true | `GameRules.LAVA_SOURCE_CONVERSION`, false | unchanged |
| `Fluid.isRandomlyTicking` | no | yes | yes |
`LavaFluid.isFastLava` reads `EnvironmentAttributes.FAST_LAVA` through
`EnvironmentAttributeReader.getDimensionValue`. The attribute is declared
`EnvironmentAttribute.Builder.notPositional`, so it is a property of the whole
dimension rather than of a place in it, and the Nether's dimension type sets it
— along with `EnvironmentAttributes.WATER_EVAPORATES`. Nether lava is not
special-cased anywhere in `LavaFluid`; it is the same three methods reading one
boolean.
On top of the numbers, `LavaFluid.getSpreadDelay` multiplies the delay by four,
three times in four, whenever a non-falling flow is about to get deeper. Lava
does not creep — it creeps unevenly, and the unevenness is rolled fresh on each
tick.
The three exceptions are where lava stops behaving like a fluid.
`LavaFluid.spreadTo` intercepts a downward spread onto water: the fizz plays
and nothing spreads, whatever the water is in, and the target becomes
`Blocks.STONE` when — and only when — it was a `LiquidBlock`. So a lavafall into
a pool builds a plug rather than replacing the water, while a lavafall onto a
waterlogged stair is merely stopped. The other two are in `LiquidBlock.shouldSpreadLiquid`, called from
`LiquidBlock.onPlace` and `LiquidBlock.neighborChanged`: for lava it walks
`LiquidBlock.POSSIBLE_FLOW_DIRECTIONS` and tests each direction's *opposite*, so
the faces it inspects are the top and the four sides and never the bottom. Water
at any of them turns *this* block into `Blocks.OBSIDIAN` if its own fluid is a
source and `Blocks.COBBLESTONE` if it is not, and returns false so no tick is
booked at all. The `Blocks.BASALT` case is the exception to the exception and
the only place the block below is read: `Blocks.SOUL_SOIL` underneath and
`Blocks.BLUE_ICE` at one of those five opposites.
Lava's *random* tick is fire rather than flow, and a selected position gets it
twice — once as a block and once as a fluid — for reasons that belong to
[scheduled ticks](scheduled-ticks.md).
## Questions players ask
**Why does a water block have a block tick at all?** `LiquidBlock.tick` spreads
nothing. It calls `BubbleColumnBlock.updateColumn`, and only when the fluid
there is a full source in `FluidTags.BUBBLE_COLUMN_CAN_OCCUPY`; the tick was
booked twenty ticks out by `LiquidBlock.tryScheduleBubbleBlockColumn` because
soul sand or magma is underneath. Flow is entirely a *fluid* tick, in the other
queue with its own budget.
**Why is my infinite pool not infinite?** Because
`GameRules.WATER_SOURCE_CONVERSION` can be turned off, and because the first
branch of `FlowingFluid.getNewLiquid` also demands something solid or another
source directly below the position being filled. Two sources over a hole make
nothing.
**Why does water refuse to run the way that looks downhill?** Because a water
block already sitting on one side scores in the slope vote and then refuses to
be replaced, so it can drag the minimum down to its own distance and empty the
winners' map on the way past.
**Why is the wall test worth caching?** Because it runs constantly.
`FlowingFluid.canPassThroughWall` short-circuits the easy cases — either side a
full cube is a no, both sides empty is a yes — and otherwise merges the two
collision shapes with `Shapes.mergedFaceOccludes` and memoises the answer in
`FlowingFluid.OCCLUSION_CACHE`, a thread-local 200-entry map keyed by
`FlowingFluid.BlockStatePairKey`, which hashes both states by identity and is
skipped entirely when either block has a dynamic shape
(`Block.hasDynamicShape`).
## Where to look
`BucketItem.emptyContents` · `LiquidBlock.onPlace` ·
`LiquidBlock.shouldSpreadLiquid` · `ServerLevel.tickFluid` · `FlowingFluid.tick`
· `FlowingFluid.getNewLiquid` · `FlowingFluid.spread` ·
`FlowingFluid.spreadToSides` · `FlowingFluid.getSpread` ·
`FlowingFluid.getSlopeDistance` · `FlowingFluid.SpreadContext` ·
`FlowingFluid.spreadTo` · `FlowingFluid.canPassThroughWall` ·
`LiquidBlock.stateCache` · `SimpleWaterloggedBlock.placeLiquid` · `WaterFluid` ·
`LavaFluid.spreadTo`
---
*Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes `tools/verify_names.py`.*
---
# Game events and vibrations
> Verified against **Minecraft 26.2** · Part IV · A footstep reaches a sculk sensor.
You walk across a stone floor and, eight blocks away, a sculk sensor's
tendrils flick up and it pushes redstone power out of its side. Nothing
scanned for you: the step itself posted a `GameEvent.STEP` into
`GameEventDispatcher.post`, which walked the loaded chunk sections around
you and called every listener inside its own radius *inline*, before
`Entity.move` had finished. What a player believes about sculk lives in the
gates that footstep then has to pass, and their shape is the surprise.
**The sensor always hears you at least one tick late by design,
because `VibrationSelector.chosenCandidate` hands over a candidate only if
it was recorded on an *earlier* tick. The wool box works only if all six
rays of `VibrationSystem.Listener.isOccluded` hit wool, not just the one on
the straight line. And standing on the sensor skips the whole cascade —
`SculkSensorBlock.stepOn` calls `VibrationSystem.Listener.forceScheduleVibration`
with no dispatcher, no occlusion test and no
`VibrationSystem.User.isValidVibration`, so sneaking does not save you.**
Only a warden gets out of that one.
## The cast
| class | what it decides | thread |
|---|---|---|
| `GameEvent` | how far the news travels — one field, `GameEvent.notificationRadius` | — (a record) |
| `GameEventDispatcher` | which sections are walked, and who is told inline rather than sorted | Server |
| `EuclideanGameEventListenerRegistry` | which listeners in one section Y are close enough to be visited | Server |
| `DynamicGameEventListener` | which section a *moving* listener is registered in | Server |
| `VibrationSystem.Listener` | the gates between an event and a candidate | Server |
| `VibrationSelector` | which of a tick's candidates survives, and that none is used before the next tick | Server |
| `VibrationSystem.Ticker` | the travel countdown, the particle, and the moment of arrival | Server, from the host's own tick |
| `SculkSensorBlockEntity.VibrationUser` | whether this sensor takes a vibration, and what it does with one | Server |
## A game event is one number
`GameEvent` is a record of a single field, `GameEvent.notificationRadius`.
That is the whole type. Sixty-one constants live in
`BuiltInRegistries.GAME_EVENT`, fifteen of them `GameEvent.RESONATE_1`
through `GameEvent.RESONATE_15`, one per vibration frequency, so a
resonating block can re-emit the frequency it heard. All but three take
`GameEvent.DEFAULT_NOTIFICATION_RADIUS`, 16 blocks: `GameEvent.JUKEBOX_PLAY`
and `GameEvent.JUKEBOX_STOP_PLAY` are 10, `GameEvent.SHRIEK` is 32. The
radius is the *dispatcher's* number, and each listener then applies its
own. Everything else rides beside the event in a `GameEvent.Context` — the
source entity and the affected block state, either of which may be absent.
The registry is a `DefaultedMappedRegistry` on *step*, which is narrower
than it sounds. `DefaultedMappedRegistry.getValue` and
`DefaultedMappedRegistry.byId` substitute `GameEvent.STEP` for an unknown
key, so a raw lookup that misses becomes a footstep — but `GameEvent.CODEC`
is a `RegistryFixedCodec`, which errors on an unknown name. Bad data in a
pack fails loudly; only the raw lookup silently becomes a step.
## Listeners live in the chunk, one registry per section
A `GameEventListener` is four methods: a `PositionSource`
(`BlockPositionSource` for a block, `EntityPositionSource` for a mob, the
latter resolving a stored UUID against the level the first time it is
asked), a `GameEventListener.getListenerRadius`,
`GameEventListener.handleGameEvent` and a
`GameEventListener.getDeliveryMode`. They are stored per chunk section in
`LevelChunk.gameEventListenerRegistrySections`
([chunk anatomy](chunk-anatomy.md)), and `LevelChunk.getListenerRegistry`
creates an `EuclideanGameEventListenerRegistry` for a section Y the first
time anyone asks — including the dispatcher, merely walking past. One is
dropped again through `LevelChunk.removeGameEventListenerRegistry`, but
only when an `EuclideanGameEventListenerRegistry.unregister` empties it.
`ChunkAccess.getListenerRegistry` answers
`GameEventListenerRegistry.NOOP`: proto chunks and the client have none.
Block entities join on the chunk's terms: `LevelChunk.addGameEventListener`
runs from `LevelChunk.addAndRegisterBlockEntity` on placement and from
`LevelChunk.registerAllBlockEntitiesAfterLevelLoad` when the chunk comes
back, asking `EntityBlock.getListener` — whose default returns the listener
of any block entity implementing `GameEventListener.Provider` ([block
entities](../blocks/block-entities.md)). A sensor's section is fixed for
the life of the block. Entities move, so they carry a
`DynamicGameEventListener` instead: a listener plus the last `SectionPos`
it was filed under. `Entity.updateDynamicGameEventListener` is empty on
`Entity` and overridden by `Warden` and by `Allay`, which hands over two.
`ServerLevel.EntityCallbacks` drives it — `DynamicGameEventListener.add`,
`DynamicGameEventListener.remove`, and `DynamicGameEventListener.move` on
every section change. Its two halves are guarded separately and its record of
where it was advances either way, so a move whose *old* chunk is not loaded to
`ChunkStatus.FULL` leaves a stale registration behind, and one whose *new*
chunk is not takes the listener out of the world entirely.
## The dispatcher never queues
`ServerLevel.gameEvent` is one line into `GameEventDispatcher.post`, and
`ServerLevel.gameEventDispatcher` owns nothing between calls. `GameEventDispatcher.post` turns
the radius into a range of sections — 3 by 3 by 3 for the default 16, 5 by
5 by 5 for a shriek — fetches each chunk column once with
`ServerChunkCache.getChunkNow`, and visits each section's registry.
`EuclideanGameEventListenerRegistry.visitInRangeListeners` resolves each
listener's position, compares block-position distance *squared* against the
listener's own radius squared — a sphere inside the dispatcher's cube — and
calls `GameEventListener.handleGameEvent` there and then. No queue, no
ordering, no next-tick delivery: the broadcast is a nested loop inside
`Entity.move`, and the emitter's method has not returned yet.
Two things follow. `ServerChunkCache.getChunkNow` returns null for a column
that is not already loaded and `GameEventDispatcher.post` skips it, so an
event at the edge of the loaded world is never delivered over the border.
And because a listener can be told mid-walk, a registry sets
`EuclideanGameEventListenerRegistry.processing` while it iterates and
defers every `EuclideanGameEventListenerRegistry.register` and
`EuclideanGameEventListenerRegistry.unregister` to the end of the visit.
One listener does wait. `GameEventListener.getDeliveryMode` is
`GameEventListener.DeliveryMode.UNSPECIFIED` everywhere except
`SculkCatalystBlockEntity.CatalystListener`, which answers
`GameEventListener.DeliveryMode.BY_DISTANCE`. Those are collected as
`GameEvent.ListenerInfo`s, sorted by squared distance and delivered by
`GameEventDispatcher.handleGameEventMessagesInQueue` after the walk,
because a catalyst *consumes* the dead mob's experience
(`LivingEntity.skipDropExperience`) and `LivingEntity.wasExperienceConsumed`
means only the first told gets any. Sorting is how the nearest one wins.
## The gates between a footstep and a candidate
```mermaid
flowchart TD
A["Entity.move, moveDist has passed nextStep"] --> B{"on ground, climbing, crouching without vertical movement or on rails, not swimming, and MovementEmission emits events"}
B -->|"no"| X1["no event is posted at all"]
B -->|"yes"| C["ServerLevel.gameEvent, GameEvent.STEP at the entity, context is the entity plus the block walked on"]
C --> D["GameEventDispatcher.post, radius 16 becomes 3 by 3 by 3 sections"]
D --> E{"ServerChunkCache.getChunkNow, is the column loaded"}
E -->|"no"| X2["skipped in silence, nothing is loaded and nothing is retried"]
E -->|"yes"| F{"EuclideanGameEventListenerRegistry.visitInRangeListeners, inside the listener's own radius"}
F -->|"outside"| X3["not visited"]
F -->|"inside"| G{"GameEventListener.getDeliveryMode"}
G -->|"BY_DISTANCE"| Q["collected, sorted by distance, delivered after the walk. The sculk catalyst alone"]
G -->|"UNSPECIFIED"| H{"VibrationSystem.Listener.handleGameEvent, is a vibration already in flight"}
H -->|"yes"| X4["dropped, this listener is busy"]
H -->|"no"| I{"VibrationSystem.User.isValidVibration"}
I -->|"outside GameEventTags.VIBRATIONS, a spectator, sneaking on an event in GameEventTags.IGNORE_VIBRATIONS_SNEAKING, Entity.dampensVibrations, or the walked block is in BlockTags.DAMPENS_VIBRATIONS"| X5["dropped"]
I -->|"passes"| P{"does the listener's own PositionSource resolve"}
P -->|"no"| X6["dropped"]
P -->|"yes"| J{"SculkSensorBlockEntity.VibrationUser.canReceiveVibration"}
J -->|"a break or place at the sensor's own position, frequency 0, or the sensor is not inactive"| X7["dropped"]
J -->|"passes"| K{"VibrationSystem.Listener.isOccluded, six rays nudged off the source block centre"}
K -->|"all six hit BlockTags.OCCLUDES_VIBRATION_SIGNALS"| X8["dropped"]
K -->|"any one ray gets through"| L["VibrationSelector.addCandidate"]
S["SculkSensorBlock.stepOn, from Entity.applyEffectsFromBlocks while standing on the block"] --> S2{"not a warden, SculkSensorBlock.canActivate, and canReceiveVibration"}
S2 -->|"yes"| L
```
The order is not the one a player would guess. The busy check comes first,
so a sensor already carrying a vibration ignores everything without
evaluating a single tag, and the occlusion raycast — much the most
expensive gate — comes last, after every cheap refusal has had its chance.
Before any of it, `Entity.applyMovementEmissionAndPlaySound` decides
whether there is an event to post, by accumulating `Entity.moveDist` and firing
only when it passes `Entity.nextStep` — which is why walking emits a footstep
per stride rather than per tick. Two gates ask the *user* rather than the
system: `VibrationSystem.User.getListenableEvents` supplies the tag
`VibrationSystem.User.isValidVibration` tests first, and
`SculkSensorBlockEntity.VibrationUser.canReceiveVibration` refuses
`GameEvent.BLOCK_DESTROY` and `GameEvent.BLOCK_PLACE` at the sensor's *own*
position — which is why placing a sensor does not set it off — refuses a
frequency of `VibrationSystem.NO_VIBRATION_FREQUENCY`, and otherwise defers
to `SculkSensorBlock.canActivate`: inactive only.
The occlusion test is worth reading slowly.
`VibrationSystem.Listener.isOccluded` takes the source block's centre,
nudges it a hundred-thousandth of a block along each of the six `Direction`
values in turn, and runs `BlockGetter.isBlockInLine` with a
`ClipBlockStateContext` looking for `BlockTags.OCCLUDES_VIBRATION_SIGNALS`.
It reports *occluded* only if all six rays are stopped, and returns the
moment one is not — so a single block of wool on the straight line is
almost never enough, and a wool box is a box because a box is what makes
all six fail.
## The trace: one footstep, several ticks
```mermaid
sequenceDiagram
participant Entity as Entity
participant SL as ServerLevel
participant GED as GameEventDispatcher
participant VSL as VibrationSystem.Listener
participant VSel as VibrationSelector
participant VST as VibrationSystem.Ticker
participant SSB as SculkSensorBlock
Note over Entity,SSB: tick T, the entity ticks and moves
Entity->>SL: gameEvent, GameEvent.STEP at the entity's feet
SL->>GED: post, every loaded section within 16 blocks
GED->>VSL: handleGameEvent, inline, before Entity.move returns
VSL->>VSel: addCandidate, a VibrationInfo stamped with game time T
Note over VST,SSB: still tick T, whenever the sensor's block entity ticks
VST->>VSel: chosenCandidate
VSel-->>VST: nothing, the candidate is not from an earlier tick
Note over Entity,SSB: tick T plus 1
VST->>VSel: chosenCandidate
VSel-->>VST: the VibrationInfo, then startOver clears the slot
VST->>SL: sendParticles, one VibrationParticleOption with the destination and the tick count
Note over VST,SSB: the countdown starts in this same tick, one block per tick
VST->>SSB: onReceiveVibration on SculkSensorBlockEntity's VibrationSystem.User — the event, the entities and the arrival distance
SSB->>SL: setBlock PHASE active with POWER, scheduleTick 30, gameEvent SCULK_SENSOR_TENDRILS_CLICKING
Note over Entity,SSB: 30 ticks later deactivate, then 10 more before inactive
```
`VibrationSystem.Ticker.tick` is the whole of the wait, and runs from
whoever hosts the listener: `SculkSensorBlock.getTicker` and
`SculkShriekerBlock.getTicker` for the blocks — server-side only, and
already gated by `Level.shouldTickBlocksAt` on their own chunk — and
`Warden.tick` and `Allay.tick` for the mobs. One call does three things in
order: select, if nothing is in flight; send or re-send the particle; then
decrement the travel time and arrive if it has reached zero.
That particle is the only thing the client is told.
`VibrationParticleOption` carries the destination `PositionSource` and the
remaining tick count, and the client animates the flight from that alone:
`ClientLevel.gameEvent` is an empty method and there is no vibration
packet. When the block entity was loaded from disk,
`VibrationSystem.Data.shouldReloadVibrationParticle` is set and the ticker
re-sends the particle from a point interpolated along the path covered.
## One tick, structurally
`VibrationSelector` holds at most one candidate, stamped with the game time
it arrived. `VibrationSelector.addCandidate` takes an empty slot
unconditionally; against a candidate from the *same* tick it takes the
closer one, breaking a distance tie in favour of the higher frequency; and
against one held over from an *earlier* tick it does nothing at all,
because that one is already waiting to be consumed. That is the whole of
"the nearest event wins" — per listener, per tick, one slot.
The latency falls out of the read side. `VibrationSelector.chosenCandidate`
returns the candidate only if its stamp is strictly *less* than the current
game time, so one added during tick T is invisible for the rest of tick T
however the ordering falls, and the earliest it can be selected is the tick
after. Travel is measured from there:
`VibrationSystem.User.calculateTravelTimeInTicks` is the floor of the
distance — one block per tick — and the countdown's first decrement happens
inside the same call that selected the vibration, so a source *n* whole
blocks away arrives *n* minus 1 ticks after selection, and anything closer
than two blocks arrives on the selecting tick itself.
Arrival can be refused. `VibrationSystem.User.requiresAdjacentChunksToBeTicking`
is true for both sculk blocks, and `VibrationSystem.Ticker` will not deliver
unless all nine columns of the 3 by 3 around the listener are loaded and
pass `Level.shouldTickBlocksAt` — [tickets and
loading](tickets-and-loading.md) is what "ticking" means. It does not drop
the vibration: the travel time is floored at zero, so the ticker asks again
every tick until the neighbourhood ticks. And the whole of
`VibrationSystem.Data` is written to disk under *listener* through
`VibrationSystem.Data.CODEC`, so a vibration survives a reload with its
countdown intact.
## What arrival costs the block
`VibrationSystem.VIBRATION_FREQUENCY_FOR_EVENT` maps game events to
frequencies 1 to 15 and returns 0 for anything unmapped: a step, a swim and
a flap are 1, a death or an explosion 15. That number is kept by
`SculkSensorBlockEntity.setLastVibrationFrequency` and is what a comparator
reads out of an *active* sensor — `SculkSensorBlock.getAnalogOutputSignal`
answers 0 in any other phase. The redstone power is a different number:
`VibrationSystem.getRedstoneStrengthForDistance` is the larger of 1 and 15
minus the floor of 15 times the distance over the listener's radius, using
a distance recomputed from the two *block* positions at arrival, not the
float stored when the candidate was made.
`SculkSensorBlock.activate` sets `SculkSensorBlock.PHASE` to
`SculkSensorPhase.ACTIVE` with that power, schedules a block tick
`SculkSensorBlock.getActiveTicks` out — 30 for a plain sensor, 10 for a
calibrated one, and never the constant `SculkSensorBlock.ACTIVE_TICKS`, which
nothing reads — runs
`SculkSensorBlock.tryResonateVibration` — which, for each of the six
neighbours in `BlockTags.VIBRATION_RESONATORS`, posts the matching
`GameEvent.RESONATE_1` … `GameEvent.RESONATE_15` at the *neighbour's*
position, so an amethyst block beside a sensor rebroadcasts the frequency
it heard — and then emits `GameEvent.SCULK_SENSOR_TENDRILS_CLICKING`, the
single entry in `GameEventTags.SHRIEKER_CAN_LISTEN`. Shriekers do not hear
you. They hear sensors hearing you.
Coming down takes two scheduled ticks. At 30, `SculkSensorBlock.tick` calls
`SculkSensorBlock.deactivate`, which drops the power to zero, moves the
phase to `SculkSensorPhase.COOLDOWN` and schedules another tick
`SculkSensorBlock.COOLDOWN_TICKS` (10) out; only *that* tick returns the
block to `SculkSensorPhase.INACTIVE`. In between the sensor is dark and
still refuses every vibration, because `SculkSensorBlock.canActivate` tests
for inactive.
## The other listeners
| listener | radius | what it listens to | what a vibration does |
|---|---:|---|---|
| `SculkSensorBlockEntity` | 8 | `GameEventTags.VIBRATIONS` | activates for 30 ticks, power by distance, frequency on the comparator |
| `CalibratedSculkSensorBlockEntity` | 16 | `GameEventTags.VIBRATIONS` | the same, but active for 10 ticks rather than 30, and when the block behind `CalibratedSculkSensorBlock.FACING` gives a redstone signal, only that exact frequency is accepted |
| `SculkShriekerBlockEntity` | 8 | `GameEventTags.SHRIEKER_CAN_LISTEN` | needs a player behind the event, then `SculkShriekerBlockEntity.tryShriek` — warning level, darkness, and a warden at level 4 |
| `Warden` | 16 | `GameEventTags.WARDEN_CAN_LISTEN` | anger through `Warden.increaseAngerAt`, a 40-tick `MemoryModuleType.VIBRATION_COOLDOWN`, and a disturbance location for `WardenAi` |
| `Allay` | 16 | `GameEventTags.ALLAY_CAN_LISTEN`, note blocks only | `AllayAi.hearNoteblock` stores `MemoryModuleType.LIKED_NOTEBLOCK_POSITION`, after which it accepts that block and no other |
| `Allay.JukeboxListener` | 10 | not a vibration at all | a plain `GameEventListener` for `GameEvent.JUKEBOX_PLAY` and `GameEvent.JUKEBOX_STOP_PLAY` — it makes the allay dance |
| `SculkCatalystBlockEntity.CatalystListener` | 8 | not a vibration at all | on `GameEvent.ENTITY_DIE`, takes the mob's experience as sculk cursors and blooms |
The tags are where the personalities live, and they are data
([tags](../foundations/tags.md)): `GameEventTags.WARDEN_CAN_LISTEN` covers
shrieks and tendril clicks that `GameEventTags.VIBRATIONS` leaves out, and
leaves out the flap that sensors hear. The warden is also the one entity
whose `Entity.dampensVibrations` is *unconditionally* true — a dropped item
answers true too, but only while it holds something in
`ItemTags.DAMPENS_VIBRATIONS` — so the warden is invisible to every other
listener while being the most sensitive one on the list, and the one entity
`SculkSensorBlock.stepOn` refuses by name. Its brain and the allay's are
[Part VI](../entities/ai-goals-and-brains.md).
## Questions players ask
**Does sneaking make me silent?** It makes six events silent.
`GameEventTags.IGNORE_VIBRATIONS_SNEAKING` holds `GameEvent.STEP`,
`GameEvent.SWIM`, `GameEvent.HIT_GROUND`, `GameEvent.PROJECTILE_SHOOT`,
`GameEvent.ITEM_INTERACT_START` and `GameEvent.ITEM_INTERACT_FINISH`, and
the test is `Entity.isSteppingCarefully` — whether the sneak key is down.
Open a chest while crouched and the sensor hears it. Sneak past one as a
player and `CriteriaTriggers.AVOID_VIBRATION` notes the advancement,
because sensors and wardens answer `VibrationSystem.User.canTriggerAvoidVibration`.
**Then why does the sensor I am crouching on still fire?** That path is not
the dispatcher's. `SculkSensorBlock.stepOn` runs from
`Entity.applyEffectsFromBlocks` every tick an entity stands on the block
and calls `VibrationSystem.Listener.forceScheduleVibration` directly: no
section walk, no radius test, no occlusion, and no
`VibrationSystem.User.isValidVibration`, which is where the sneaking tag
lives. It still asks `VibrationSystem.User.canReceiveVibration`, so an
active sensor stays quiet, and it still refuses a warden. The tick of
latency remains, since the shortcut ends at `VibrationSelector.addCandidate`
like everything else.
**Why did my wool floor not stop it?** Wool underfoot and wool in the way
are different tags doing different jobs. `BlockTags.DAMPENS_VIBRATIONS` on
the block being walked on kills the event at
`VibrationSystem.User.isValidVibration`, and `ItemTags.DAMPENS_VIBRATIONS`
does the same for a dropped item through `ItemEntity.dampensVibrations`.
`BlockTags.OCCLUDES_VIBRATION_SIGNALS` is the six-ray test, and it has to
stop all six.
**Why does a sensor near the edge of the world miss?** Two reasons, both
silent: `GameEventDispatcher.post` skips any column
`ServerChunkCache.getChunkNow` does not already have, and a sensor whose
3 by 3 neighbourhood is not ticking holds a finished vibration until it is.
The second is visible on the debug channel; the first is not, because the
broadcast happens only where a listener was actually visited —
`DebugSubscriptions.GAME_EVENTS`
and `DebugSubscriptions.GAME_EVENT_LISTENERS`, broadcast through
`ServerLevel.debugSynchronizers`
([debugging the running game](../client/debugging-the-running-game.md)).
## Where to look
`GameEvent` · `GameEventDispatcher.post` ·
`EuclideanGameEventListenerRegistry.visitInRangeListeners` ·
`LevelChunk.getListenerRegistry` · `LevelChunk.addGameEventListener` ·
`DynamicGameEventListener.move` · `ServerLevel.EntityCallbacks` ·
`Entity.vibrationAndSoundEffectsFromBlock` ·
`VibrationSystem.Listener.handleGameEvent` ·
`VibrationSystem.User.isValidVibration` ·
`VibrationSystem.Listener.isOccluded` · `VibrationSelector.addCandidate` ·
`VibrationSelector.chosenCandidate` · `VibrationSystem.Ticker.tick` ·
`SculkSensorBlockEntity.VibrationUser` · `SculkSensorBlock.activate` ·
`SculkSensorBlock.stepOn` · `SculkShriekerBlockEntity.tryShriek` ·
`Warden.VibrationUser` · `SculkCatalystBlockEntity.CatalystListener` —
then [entity anatomy](../entities/entity-anatomy.md) for
`Entity.updateDynamicGameEventListener` and
[registries](../../reference/registries.md) for
`BuiltInRegistries.GAME_EVENT`. The other index the world keeps about
itself — where things worth walking to are, rather than what just happened
— is [points of interest](points-of-interest.md).
---
*Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes `tools/verify_names.py`.*
---
# Points of interest
> Verified against **Minecraft 26.2** · Part IV · A villager claims a bed.
A villager standing in the middle of a field at noon decides which bed is
his. He is not near it, he is not looking at it, and he will not walk to it
for another six thousand ticks. `AcquirePoi` asked the index for the beds
with a free ticket within 48 blocks, took the five nearest, asked `PathNavigation` for a path to all five at once, and the
moment `Path.canReach` came back true it called `PoiManager.take` and
decremented that bed's ticket. Night has nothing to do with it. Hours later
`SleepInBed` will finally put him in the bed and set `BedBlock.OCCUPIED`,
and because both occupied variants of a bed head are in `PoiTypes.BEDS` and
map to the same `PoiTypes.HOME`, that block change does not touch the record
at all: **the claim and the *occupied* flag speak in one direction only. Going
to sleep tells the index nothing; the single behaviour that reads the flag
back can only take a claim away.** Neither is what a player would call
ownership.
## The cast
| class | what it decides | thread |
|---|---|---|
| `PoiType` | how many tickets a kind of block hands out and how close is close enough — a record of `PoiType.matchingStates`, `PoiType.maxTickets`, `PoiType.validRange` | static, built at bootstrap |
| `PoiTypes` | the catalogue, and the block-state → type map `PoiTypes.TYPE_BY_STATE` that every lookup goes through | static |
| `PoiRecord` | one position's type and its `PoiRecord.freeTickets` counter | Server |
| `PoiSection` | the records in one 16³ section, by section-relative position and by type, plus the *Valid* flag that decides whether the section is rebuilt on load | Server |
| `PoiManager` | the index: a `SectionStorage` over the *poi/* region files, the whole query family, and the village distance graph | Server |
| `ServerLevel` | that a block change happened at all — `ServerLevel.updatePOIOnBlockStateChange` is the door every ordinary write goes through | Server, or a worldgen worker |
| `AcquirePoi` | which POI a mob claims, and when to stop asking about one it cannot reach | Server |
| `ValidateNearbyPoi` · `SleepInBed` | whether a remembered POI is still real, and what to do on arrival | Server |
## A ticket is a claim nothing enforces
A point of interest is a block state the game has decided is worth going to.
There are twenty-one kinds, none data-pack-driven: `PoiTypes.bootstrap`
registers them into `BuiltInRegistries.POINT_OF_INTEREST_TYPE` and files every
one of their block states into `PoiTypes.TYPE_BY_STATE`, throwing at startup
if two types claim one state. `PoiTypes.forState` and `PoiTypes.hasPoi` are
the only questions anyone asks of that map.
A ticket is the claim. A `PoiRecord` starts with `PoiType.maxTickets` free
and hands them out one at a time: `PoiRecord.acquireTicket` decrements,
`PoiRecord.releaseTicket` increments, both refuse to run past their end of the
range, and both mark the section dirty. `PoiRecord.hasSpace` asks whether any
are left, `PoiRecord.isOccupied` whether any are gone — the second question
being the one the village graph reads. Neither says anything about *who*: a
record does not know its holder, a holder is not told when its record
disappears, and no state on the block corresponds to a claim.
```mermaid
stateDiagram-v2
[*] --> Free : block placed, PoiManager.add, freeTickets = PoiType.maxTickets
Free --> Held : PoiManager.take then PoiRecord.acquireTicket
Held --> Free : ValidateNearbyPoi, the bed is OCCUPIED and no villager is asleep in it
Held --> Free : Villager.releaseAllPois, on death or on witch conversion
Held --> Free : SetWalkTargetFromBlockMemory, unreachable for 1200 ticks
Held --> Gone : the block changes, PoiManager.remove drops the whole record
Free --> Gone : the block changes, PoiManager.remove drops the whole record
Gone --> [*]
note right of Held : occupied, so its section is a village centre if the type is in PoiTypeTags.VILLAGE
note right of Gone : nothing is released, and the claimant is not told
```
The asymmetry in that figure is deliberate on the release side and merely
survivable on the removal side. `PoiManager.release` **throws** when the
section is not there and `PoiSection.release` throws when the record is not,
which is why three of the four releasers check `PoiManager.getType` or
`PoiManager.exists` first — `Villager.releasePoi` checks the type and then
tests it against `Villager.POI_MEMORIES` before it dares. The fourth,
`VillagerMakeLove`, checks nothing, and gets away with it because the
position it releases is one `PoiManager.take` handed back a moment earlier. `PoiSection.remove` on
a missing record only logs an error, so the removal path is allowed to be
wrong and the release path is not.
Erasing a memory is not a release, which is what makes the job-site behaviours
noisy and harmless: `PoiCompetitorScan` awards a contested site to whichever
villager has the higher `Villager.getVillagerXp` and makes the loser erase
`MemoryModuleType.JOB_SITE`, `YieldJobSite` hands a
`MemoryModuleType.POTENTIAL_JOB_SITE` over to an unemployed neighbour, and
`AssignProfessionFromJobSite` promotes the potential site to the real one.
None of those three touches a ticket — though `GoToPotentialJobSite` does,
`GoToPotentialJobSite.stop` giving back the ticket `AcquirePoi` took on the
potential site.
## The catalogue
| types | the block | `PoiType.maxTickets` | `PoiType.validRange` |
|---|---|---:|---:|
| `PoiTypes.ARMORER` `PoiTypes.BUTCHER` `PoiTypes.CARTOGRAPHER` `PoiTypes.CLERIC` `PoiTypes.FARMER` `PoiTypes.FISHERMAN` `PoiTypes.FLETCHER` `PoiTypes.LEATHERWORKER` `PoiTypes.LIBRARIAN` `PoiTypes.MASON` `PoiTypes.SHEPHERD` `PoiTypes.TOOLSMITH` `PoiTypes.WEAPONSMITH` | one work block each — blast furnace, smoker, cartography table, brewing stand, composter, barrel, fletching table, lectern, stonecutter, loom, smithing table, grindstone, and for the leatherworker all four cauldrons | 1 | 1 |
| `PoiTypes.HOME` | the bed's **head** half only — `PoiTypes.BEDS` filters `BedBlock.PART` to `BedPart.HEAD` | 1 | 1 |
| `PoiTypes.MEETING` | the bell | 32 | 6 |
| `PoiTypes.BEEHIVE` `PoiTypes.BEE_NEST` | hive and nest | 0 | 1 |
| `PoiTypes.NETHER_PORTAL` | the portal block | 0 | 1 |
| `PoiTypes.LODESTONE` | the lodestone | 0 | 1 |
| `PoiTypes.LIGHTNING_ROD` | the rod, every facing | 0 | 1 |
| `PoiTypes.TEST_INSTANCE` | the test instance block | 0 | 1 |
Six of the twenty-one types are locatable but unclaimable.
`PoiRecord.hasSpace` is false for them forever, so nothing can
`PoiManager.take` one, and `PoiRecord.isOccupied` — which asks whether the free
count has moved off `PoiType.maxTickets` — is false forever too. Not that it
would matter: none of the six is in `PoiTypeTags.VILLAGE`, so none was ever a
candidate for a village centre. They are indexed purely so something can find
the nearest one fast: the bee's hive search asks for `PoiTypeTags.BEE_HOME`
within 20 blocks and then filters through `Bee.doesHiveHaveSpace`, which asks
the `BeehiveBlockEntity` whether it is full. The index answers *where* and
something else answers *whether*. Three tags cut across the catalogue
([tags](../foundations/tags.md)): `PoiTypeTags.ACQUIRABLE_JOB_SITE` for the
thirteen professions, `PoiTypeTags.BEE_HOME` for the two hives, and
`PoiTypeTags.VILLAGE` for those thirteen plus `PoiTypes.HOME` and
`PoiTypes.MEETING` — fifteen types whose occupied records are what a village
*is*.
## Where the index lives, and how it repairs itself
`PoiManager` extends `SectionStorage` ([chunk storage](chunk-storage.md)), so
the unit of storage is a chunk section and the unit of file a region:
`ChunkMap` builds it on the dimension's *poi/* folder with
`DataFixTypes.POI_CHUNK`, and `PoiSection.Packed` is the on-disk shape — a
*Valid* boolean and a list of *Records*, each a position, a type and a
*free_tickets* count. Tickets survive a restart, and so do the villagers'
memories of them, and nothing on load reconciles the two.
`ChunkMap.tick` runs `PoiManager.tick` under the profiler's *poi*, which
writes dirty chunks for as long as the tick has time and then settles the
village graph ([the level tick](../server/server-level-tick.md) owns the
budget). Reads are the interesting half. The query family is a dozen shapes of
the same walk — `PoiManager.getInSquare` over a chunk range, `PoiManager.getInRange`
narrowing it to a sphere, and `PoiManager.findClosest`, `PoiManager.getRandom`,
`PoiManager.getCountInRange`, `PoiManager.exists` and `PoiManager.getType`
above them — and every one bottoms out in `SectionStorage.getOrLoad`, where a
section that was never prefetched is read from disk **synchronously, on the
Server thread, blocking**. That is why `ChunkMap.scheduleChunkLoad` fires
`SectionStorage.prefetch` beside the chunk's own parse and joins the two
before either is used.
The repair runs on every chunk read. `SerializableChunkData.read` calls
`PoiManager.checkConsistencyWithBlocks` once per section with block data. If
the section is in storage, `PoiSection.refresh` rebuilds it — but only if its
*Valid* flag is false, and the rebuild reuses the existing `PoiRecord` objects
for positions that still have a POI, so **a repair does not reset anybody's
tickets**. If it is not in storage, one is created and scanned. Both scans are
short-circuited by `PoiManager.mayHavePoi`, which asks
`LevelChunkSection.maybeHas` whether the palette holds any state
`PoiTypes.hasPoi` recognises ([chunk anatomy](chunk-anatomy.md) has the
palette), so a section of plain stone is dismissed without one block read. The
*Valid* flag's codec defaults to **false**: anything not explicitly written as
validated gets rescanned.
## A record appears when a block changes, sometimes a task late
`Level.setBlock` calls `Level.updatePOIOnBlockStateChange` last, after the
neighbour updates, and on a `ServerLevel` that override compares
`PoiTypes.forState` of the old and the new state. Equal types mean nothing
happens — exactly the bed case, since a bed head that gains
`BedBlock.OCCUPIED` is still `PoiTypes.HOME`. Different types mean a
`PoiManager.remove` for the old and a `PoiManager.add` for the new, each
wrapped in a `BlockableEventLoop.execute` on the server.
That wrapper exists because `WorldGenRegion.setBlock` calls the same hook from
worldgen workers and the index is Server-thread-only. What it does *not* do is
defer the ordinary case: `MinecraftServer.scheduleExecutables` is false when
the caller is already the Server thread and not inside a task the loop is
running (`ReentrantBlockableEventLoop.runningTask`), so a block placed by a
player, a command or a mob during the tick body gets its record synchronously.
Deferral is the worldgen and nested-task case — there the record appears a
task later than the block it describes, and a read in between gets the old
answer.
## The trace: a villager claims a bed
```mermaid
sequenceDiagram
participant SL as ServerLevel
participant PM as PoiManager
participant Brain as Brain
participant AP as AcquirePoi
participant PN as PathNavigation
participant VNP as ValidateNearbyPoi
participant SIB as SleepInBed
Note over SL,PM: any tick, Server thread
SL->>SL: setBlock puts the bed head down, forState of old and new differ
SL->>PM: add, a PoiRecord with one free ticket, section marked dirty
Note over Brain,SIB: Activity.CORE, any hour of the day, HOME absent
Brain->>AP: priority 10, and this evaluation is due
AP->>PM: findAllClosestFirstWithType HOME, HAS_SPACE, 48 blocks
PM-->>AP: the nearest five past the retry cache, then validateBedPoi
AP->>PN: createPath to all five at once, reach range 1
PN-->>AP: a Path whose canReach is true, getTarget is one bed
AP->>PM: take at that position, acquireTicket, one free becomes zero
AP->>Brain: MemoryModuleType.HOME set to a GlobalPos, entity event 14
Note over Brain,SIB: thousands of ticks later, tick 12000, Activity.REST
Brain->>Brain: SetWalkTargetFromBlockMemory writes WALK_TARGET, MoveToTargetSink walks
Brain->>VNP: within 16 blocks, is the record still HOME
Brain->>SIB: within 2 blocks and the bed not OCCUPIED
SIB->>SL: startSleeping, setBlock with BedBlock.OCCUPIED true
SL->>SL: forState is HOME either way, so nothing is queued and the record is untouched
Note over Brain,SIB: morning, REST leaves the brain, WakeUp clears the flag, the ticket stays
```
The scan is cheap and the path is not. `PoiManager.findAllClosestFirstWithType`
turns a 48-block radius into a chunk radius of four, walks every section of
those chunks, filters by `PoiManager.Occupancy.HAS_SPACE` and sorts by
distance; `AcquirePoi` takes the first five and only then runs
`VillagerGoalPackages.validateBedPoi`, which re-reads each block to confirm it
is in `BlockTags.BEDS` and not already `BedBlock.OCCUPIED`. Then
`AcquirePoi.findPathToPois` hands all five positions to
`PathNavigation.createPath` as one target set, at the reach range from
`PoiType.validRange` — one, for a bed. A villager's constructor raises
`PathNavigation.setRequiredPathLength` to 48 so that this search can span the
scan range ([goals and brains](../entities/ai-goals-and-brains.md) owns the
pathfinder). Beds that failed before are held off by
`AcquirePoi.JitteredLinearRetry`, whose delay is **cumulative**: each attempt
adds another 40 to 79 ticks to that position's own counter, capped at
`AcquirePoi.JitteredLinearRetry.MAX_RETRY_PATHFINDING_INTERVAL`, 400. A bed
behind a wall is checked ever more rarely — but never as rarely as once every
twenty seconds, because a marker untouched for 400 ticks is dropped on the
very tick the cap would first apply, so the interval saws back to the
beginning. A successful claim clears the whole cache.
The claim itself is five statements. `PoiManager.take` — alone among the
radius searches in having no `PoiManager.Occupancy` parameter, because it
always means *HAS_SPACE* — is called with radius 1 around the path's target and a filter
accepting only that exact position, and calls `PoiRecord.acquireTicket` on
what it finds. Then `MemoryModuleType.HOME` is set to a `GlobalPos`, then
`ServerLevel.broadcastEntityEvent` sends event 14 — the green particle burst,
and the only thing an ordinary client learns of any of this. The last two
statements clear the retry cache and tell the debug channel. The memory is set
inside `PoiManager.getType`'s *ifPresent*, not inside `PoiManager.take`'s —
the take's result is never consulted.
## After the claim: the night shift
`Brain.updateActivityFromSchedule`, run from `UpdateActivityFromSchedule` at
priority 99 and only when more than twenty ticks have passed since it last did
anything, reads the villager's schedule *attribute* —
`EnvironmentAttributes.VILLAGER_ACTIVITY` for an adult,
`EnvironmentAttributes.BABY_VILLAGER_ACTIVITY` for a child — at the villager's
own position, and `Timelines.VILLAGER_SCHEDULE` puts the `Activity.REST`
keyframe at tick 12000 of a 24000-tick period
([environment attributes](environment-attributes-and-timelines.md) owns the
mechanism; the old *Schedule* class is gone). Only then does the bed half of
the brain exist at all: `VillagerGoalPackages.getRestPackage` is where
`SetWalkTargetFromBlockMemory`, `ValidateNearbyPoi` for `PoiTypes.HOME` and
`SleepInBed` live, while the core package validates the *job site* and not the
bed. So between dawn and dusk a villager whose bed was mined keeps pointing at
a position with no record, and nothing tells it otherwise.
At night the three run in priority order. `SetWalkTargetFromBlockMemory` at
priority 2 writes `MemoryModuleType.WALK_TARGET` whenever the bed is more than
one block away in Manhattan distance — straight at it when it is nearer than
150, and at a random intermediate position when it is further — and gives up,
releasing the ticket and erasing the memory, once `MemoryModuleType.CANT_REACH_WALK_TARGET_SINCE` has stood for
more than 1200 ticks. `ValidateNearbyPoi` at priority 3 does
nothing at all unless the bed is within 16 blocks and in this dimension: then
it erases the memory if `PoiManager.exists` no longer agrees on the type, and
if the bed is `BedBlock.OCCUPIED` and this villager is not itself the
sleeper, it erases the memory and releases the ticket — unless some villager
is asleep in that block, in which case the memory goes and the ticket stays,
the sleeper being presumed to hold it. `SleepInBed`, also priority 3, needs the villager within 2 blocks, the bed
unoccupied, and `SleepInBed.COOLDOWN_AFTER_BEING_WOKEN` ticks since
`MemoryModuleType.LAST_WOKEN`. It calls `LivingEntity.startSleeping`, which is
what actually sets the flag, and then records `MemoryModuleType.LAST_SLEPT`
and erases the walk target itself.
Morning ends it twice over: `WakeUp`, at priority 0 in the core package, calls
`LivingEntity.stopSleeping` the instant `Activity.REST` goes inactive, and
`SleepInBed.stop` does the same when the behaviour ends. Either way the flag
clears and the ticket does not.
## What makes a village
`PoiManager.DistanceTracker` is a `SectionTracker` — the same
`DynamicGraphMinFixedPoint` flood the ticket system's two graphs use
([tickets and loading](tickets-and-loading.md)), and the only one of the five
outside `server/level` — over chunk sections instead of chunks. Its sources are the sections where
`PoiManager.isVillageCenter` holds: at least one record whose type is in
`PoiTypeTags.VILLAGE` and whose `PoiManager.Occupancy` is *IS_OCCUPIED*. They
sit at level 0 and the flood runs out to `PoiManager.MAX_VILLAGE_DISTANCE`,
six sections, past which the level is simply absent from the map.
**An unclaimed bed makes no village.** A hundred empty beds are a hundred
records and zero sources; one villager taking one ticket lights the section up.
`PoiManager.setDirty` and `PoiManager.onSectionLoad` re-seed the source,
`PoiManager.tick` settles the flood every tick, and
`PoiManager.sectionsToVillage` settles it again before answering.
`ServerLevel.isVillage` is that distance being one section or less;
`ServerLevel.isCloseToVillage` takes the distance as an argument and refuses
anything past six.
That boolean is load-bearing far outside this system. `BadOmenMobEffect`
starts a raid only where `ServerLevel.isVillage` is true, `VillageSiege` needs
one to put zombies in, `PatrolSpawner` refuses to spawn a patrol within two
sections of one and `CatSpawner` insists on being within two. `Raid` re-checks
it as the raid runs, and `Raids` sets the raid's centre to the average
position of the occupied `PoiTypeTags.VILLAGE` records within 64 blocks.
## Everyone else who reads the index
| who | what it asks for | radius | `PoiManager.Occupancy` |
|---|---|---|---|
| `PortalForcer.findClosestPortalPosition` | `PoiTypes.NETHER_PORTAL`, after `PoiManager.ensureLoadedAndValid` drags the sections in | 16 going to the Nether, 128 coming back | *ANY* |
| `Bee` | `PoiTypeTags.BEE_HOME`, then `Bee.doesHiveHaveSpace` for the real occupancy | 20 | *ANY* |
| `ServerLevel.findLightningRod` | `PoiTypes.LIGHTNING_ROD` standing at the surface height | 128 | *ANY* |
| `LodestoneTracker` | one position — is `PoiTypes.LODESTONE` still there | — | — |
| `LocateCommand` | `PoiManager.findClosestWithType` for a type or a tag | 256 | *ANY* |
| `Raids` | the records it averages into a raid's centre | 64 | *IS_OCCUPIED* |
| `CatSpawner` | more than four claimed `PoiTypes.HOME` nearby | 48 | *IS_OCCUPIED* |
| `WanderingTraderSpawner` | a `PoiTypes.MEETING` near a player, to arrive at | 48 | *ANY* |
| `NearestBedSensor` | `PoiTypes.HOME` for `MemoryModuleType.NEAREST_BED`, babies only, no ticket taken | 48 | *ANY* |
`PoiManager.ensureLoadedAndValid` in the first row is the only caller that
forces loading rather than tolerating what is in memory: for any nearby
section missing or invalid it pulls the chunk in at `ChunkStatus.EMPTY`, once
per chunk per server run since `PoiManager.loadedChunks` never forgets. The
index is on the debug channel too — `LevelDebugSynchronizers.registerPoi`,
`LevelDebugSynchronizers.updatePoi` and `LevelDebugSynchronizers.dropPoi` feed
`DebugSubscriptions.POIS` and `DebugSubscriptions.VILLAGE_SECTIONS`.
## Questions players ask
**Why is a villager sleeping in the bed I built for someone else?** Because
the claim is a number in a file and the bed is a block, and neither knows the
other. `PoiManager.take` decremented a counter 48 blocks away hours before
anyone walked anywhere, and `SleepInBed` only ever checks that the bed is *not
currently occupied* — never who holds the ticket.
**Why do villagers stop breeding when I take a bed away?**
`VillagerMakeLove.tryToGiveBirth` calls `VillagerMakeLove.takeVacantBed`
first — a `PoiManager.take` for `PoiTypes.HOME` within 48 blocks, filtered by
reachability. No free ticket, no baby, and the pair get entity event 13
instead. If the birth then fails the ticket is released; if it succeeds,
`VillagerMakeLove.giveBedToChild` writes the baby's `MemoryModuleType.HOME`
directly, with no `AcquirePoi` involved. This is the raw `PoiManager.take`,
which finds the first reachable match in section order rather than the
nearest, so the baby's bed need not be the closest.
**Why does the same bed get claimed twice after I break and replace it?**
Breaking it runs `PoiManager.remove` and the record ceases to exist with its
ticket count — no release, no notification. The villager keeps its `GlobalPos`
until `ValidateNearbyPoi` next runs, which needs `Activity.REST` active *and*
the villager within 16 blocks. Replace the bed first and there is a brand-new
record with a full ticket, claimable by anyone — including the villager that
thought it already had one.
## Where to look
`PoiTypes.bootstrap` · `PoiRecord.acquireTicket` · `PoiSection.refresh` ·
`PoiManager.add` · `PoiManager.take` · `PoiManager.release` ·
`PoiManager.getInRange` · `PoiManager.checkConsistencyWithBlocks` ·
`ServerLevel.updatePOIOnBlockStateChange` · `AcquirePoi.create` ·
`VillagerGoalPackages.getCorePackage` · `VillagerGoalPackages.getRestPackage` ·
`ValidateNearbyPoi.create` · `SleepInBed.start` · `Villager.releasePoi` ·
`PoiManager.DistanceTracker` · `ServerLevel.isVillage`
The other index in this corner of the tree — the fire-and-forget broadcast
sculk sensors listen to — is
[game events and vibrations](game-events-and-vibrations.md). The brain belongs
to [goals and brains](../entities/ai-goals-and-brains.md), death and
conversion to [the entity lifecycle](../entities/entity-lifecycle.md), the bed
block to [blocks and states](../blocks/blocks-and-states.md).
---
*Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes `tools/verify_names.py`.*
---
# V · Blocks
> Verified against **Minecraft 26.2** · Part V · Everything that happens at the moment one block state replaces another: the click that asks for it, the write that performs it, and the four kinds of block that answer back.
Part IV built the container. This part is about what is *in* it, and it has
one moment at its centre: a position in a chunk section stops holding one
block state and starts holding another. Every page here is about choosing
that state, about the write itself, about a block that answers a write near
it, or — once — about the state a position cannot hold at all. A player
recognises the part by how it *feels* — the block that appears under the
crosshair before the server has heard about it, the door whose top half
swings with the bottom, the redstone lamp that does not light until the
server says so. The first of those is a prediction and Part X
owns it. The other two come from one distinction, and it is this part's:
**there are two entirely different ways a block hears that its neighbour
changed, and the client runs only one of them.**
## The shape of the part
Part V is a hub and six spokes. The hub is `blocks-and-states`, and what the
spokes reach back into it for is not the state table — it is the tail of a
write, drawn there once as a flowchart and linked to from everywhere else.
```mermaid
flowchart TD
BS["Blocks and states — the table, and what a write actually does"]
BI["Block interaction — the right click"]
BB["Block breaking — the left click"]
BE["Block entities — when a state is not enough"]
SD["Signal and dust — reading power, and the cascade"]
PE["Pistons and block events — a change that waits for a phase"]
DO["Diodes and the observer — a change that books a turn"]
BS -- "the shape channel, which the client runs too" --> BI
BS -- "what a flags-3 write does after the section is written" --> BB
BS -- "where a block entity is created, kept, replaced, removed" --> BE
BS -- "the neighbour channel, which is the server's alone" --> SD
BS -- "the flag word, and which bits the placeholders leave out" --> PE
BS -- "a flag-2 write, and the onPlace that runs inside it anyway" --> DO
BI -- "one lecture in two halves, one prediction ledger" --> BB
SD -- "what powers a piston, and how the wire connects to it" --> PE
SD -- "what a diode reads, and what reads a diode" --> DO
BE -- "the one int redstone keeps outside a block state" --> DO
BE -- "the placeholder's entity carries a whole state" --> PE
```
## Before you start
[Chunk anatomy](../world/chunk-anatomy.md), because the first half of every
write in this part is a section write with four heightmaps and a light check
behind it, and [the level tick](../server/server-level-tick.md), because half
of this part's surprises are really claims about which phase of a tick
something ran in — with [the server tick](../server/server-tick.md) behind
it, for the two claims this part makes about what happens *outside* the level
tick: that a packet handler runs before the levels do, and that a connection
is flushed after they have. Two more Part IV pages are load-bearing here
rather than merely adjacent: [scheduled
ticks](../world/scheduled-ticks.md) is how a block gets a turn *later*, which
is the whole of the diode lecture, and [fluids](../world/fluids.md) owns the
`FluidState` that shares a `StateHolder` with every block state, and the
waterlogging this part keeps writing around.
One dependency runs the other way. [Prediction and
acknowledgement](../client/prediction-and-acks.md) is Part X, and the two
click lectures here use three of its six windows between them — but its own
scenario is a block placed against a wall, which needs
this part's vocabulary. So watch
Part V first: both click pages open with the same four-sentence statement of
the contract, which is all either lecture needs, and the machinery keeps
until Part X.
## Watch in this order
1. [Blocks and states](blocks-and-states.md) — a right-click on stone puts one
of oak stairs' eighty states into the world. Every state the game will ever
have was built before the world was, and the world stores an index into
that table. The second half — what a write does after the section has been
written — is the figure the other six lectures point back at.
2. [Block interaction](block-interaction.md) — the right click, in full: a
door opened by hand. It fires no neighbour update at all, and the top half
follows anyway.
3. [Block breaking](block-breaking.md) — the same lecture's other half: two
clocks that agree without exchanging a packet. Let go too early and the
block comes back, then vanishes again, and nothing short of the block
itself going away stops it.
4. [Block entities](block-entities.md) — a furnace smelts while nobody is
looking. It tells nobody anything: the fire is a block state, the arrow is
four ints from a menu, and both are a tick late by construction.
5. [Signal and dust](signal-and-dust.md) — a lever, two dust, and the
cascade. A line turning off is visited once for every intermediate value it
passes through, none of which is ever sent to anybody, and the game ships a
second implementation behind a flag that does not do it at all.
6. [Pistons and block events](pistons-and-block-events.md) — the part's
deferral with no delay in it: the work waits for one named phase of the
level tick rather than for a number of ticks, and usually gets it in the
same tick. Also the one place the client is handed a re-simulation instead
of a result: no block update is ever sent for the moving blocks.
7. [Diodes and the observer](diodes-and-observers.md) — the part's closer.
Three blocks that read their neighbours three different ways, and the one
whose entire job is noticing change turns out not to be listening on the
channel that carries it.
## Reference this part uses
[Block update flags](../../reference/block-update-flags.md) — the ten bits of
`Level.setBlock`'s flag word and what reads each. [Registries](../../reference/registries.md) — `Registries.BLOCK` and
`Registries.BLOCK_ENTITY_TYPE` are two of its rows.
[Packets](../../reference/packets.md) — every block update, block event and
acknowledgement in one table. [Data
components](../../reference/components.md) — `DataComponents.TOOL`, which
decides how fast a stack mines and whether the block drops. [Game
rules](../../reference/gamerules.md). [Math and
primitives](../../reference/math-and-primitives.md) — `BlockPos`,
`Direction` and the packings every page here assumes. [Diagram
lanes](../../reference/lanes.md).
---
*Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes `tools/verify_names.py`.*
---
# Blocks and states
> Verified against **Minecraft 26.2** · Part V · A player right-clicks the top of a stone block holding oak stairs, and one of the stair's eighty pre-built states goes into the world.
You are standing on stone with a stack of oak stairs, and you right-click the
top of the block. A moment later a stair is up there, facing away from you,
sitting on the bottom half of its cube. Nothing was constructed to make that
happen. Oak stairs have four properties — *facing*, *half*, *shape*,
*waterlogged* — and all eighty combinations of them were built before any
world existed, in the class initialiser of `Blocks`, and numbered into one
flat table, `Block.BLOCK_STATE_REGISTRY`. What a chunk stores is an index
into that table. Both of the surprises on this page fall out of that single
decision. Choosing a property allocates nothing: `StateHolder.setValue`
reads one cell out of a table of neighbours computed at startup and hands
back a state that already existed. And the index is not always checked:
`Block.getId` answers **0** for a state its table has never seen, and
`Block.stateById` answers `Blocks.AIR`'s default state for a number it does
not know — so wherever the game reaches the table through that pair, a state
the two sides disagree about raises nothing at all. It quietly becomes air.
## The cast
| class | what it decides | thread |
|---|---|---|
| `Block` | one kind of thing: which properties it has, what its default state is, and — through fifty-eight statics — the drops, the particles and the shape-update helpers the rest of the game calls | built at class-initialisation, read from every thread after |
| `BlockBehaviour` | every hook a block may override, from `BlockBehaviour.onPlace` to `BlockBehaviour.updateShape`. `Block` extends it and adds registration | as above |
| `BlockBehaviour.Properties` | hardness, sound, map colour, whether it ticks — and the `ResourceKey` without which no block can be built at all | kept by the `BlockBehaviour` constructor and read from thereafter; hardness and map colour are never copied out |
| `StateDefinition` | the table: which properties this block has, in which order, and the full product of their values | built in the `Block` constructor |
| `Property` | one axis — a name, a value type, and where a value sits in that axis | immutable, shared between blocks |
| `StateHolder` | one state's property values, and the table that answers *what state am I if this property becomes that value* | filled once by `StateDefinition`, read-only after |
| `BlockBehaviour.BlockStateBase` | everything a state can answer without going to the block, and the caches that make collision and occlusion cheap | half-built in its constructor, finished by `BlockBehaviour.BlockStateBase.initCache` |
| `Block.BLOCK_STATE_REGISTRY` | the integer a state is on the wire and in a section's global palette — never on disk, where a state is its name and its properties | appended once per state, in the `Blocks` class initialiser |
## Twelve classes and one Cartesian product
```mermaid
flowchart TB
PROPS["BlockBehaviour.Properties: the builder. Useless until setId hands it a ResourceKey"]
BB["BlockBehaviour: 1,357 lines of overridable hooks"]
BLOCK["Block: 643 lines, mostly statics, plus one state table and one default state"]
PROP["Property: a name, a value type, and getInternalIndex"]
BOOL["BooleanProperty: exactly two values, true at index 0"]
INT["IntegerProperty: min to max, min never below zero"]
ENUM["EnumProperty: any StringRepresentable enum, ordinalToIndex for the lookup"]
SD["StateDefinition: propertiesByName sorted by name, states the full Cartesian product"]
SH["StateHolder: propertyKeys, propertyValues, and the neighbors table"]
BSB["BlockBehaviour.BlockStateBase: every hook a state answers, and the caches"]
BS["BlockState: twenty lines. A constructor, asState, and CODEC"]
REG["Block.BLOCK_STATE_REGISTRY: an IdMapper over every state of every block"]
PROPS -- "kept by the BlockBehaviour constructor and read from thereafter" --> BB
BB -- "extended by" --> BLOCK
PROP -- "extended by, and only by these three" --> BOOL
PROP --> INT
PROP --> ENUM
PROP -- "collected by StateDefinition.Builder.add" --> SD
BLOCK -- "builds exactly one, in its own constructor" --> SD
SD -- "one object per cell of the product, built once and never again" --> BS
SH -- "extended by" --> BSB
BSB -- "extended by" --> BS
SD -. "fillNeighborsForState fills each state's neighbors, property index by value index" .-> SH
BS -- "added in registry order by the Blocks class initialiser, then initCache" --> REG
```
### The kind, three classes deep
A *block* is a kind of thing — oak stairs, stone, water. A *block state* is
one exact configuration of that kind, and it is a block state, never a block,
that a chunk section stores, that a packet carries, that a model is chosen
for. The kind is spread over three classes. `BlockBehaviour` is the base and
holds the hooks; `Block` extends it and adds the registry holder, the state
table and the statics everything else in the game reaches for. Both are
constructed from a `BlockBehaviour.Properties`, a builder that must first be
given an identity: `BlockBehaviour.Properties.setId` supplies the
`ResourceKey`, the loot table and the translation key are derived from it,
and the `BlockBehaviour` constructor throws *Block id not set* without one.
So a block cannot be built from `BlockBehaviour.Properties.of` outside
`Blocks.register`, which takes the id from `BlockItemIds` or `BlockIds` and
hands it to the builder on the way past. None of that is data:
`BlockBehaviour.Properties.CODEC` is a unit codec, so `Block.CODEC` is a
constructor dispatch and hardness, sound and map colour never serialise.
### The table, sorted by name
Each `Block` constructor calls its own `Block.createBlockStateDefinition`,
collecting properties through `StateDefinition.Builder.add` — which rejects a
name outside lower-case, digits and underscore, a *value* name that breaks the
same pattern, a property with fewer than two values, and a duplicate name — and
then `StateDefinition.Builder.create`
builds every state the block will ever have. Zero properties gives one
singleton state, one property gives a row, and two or more gives the full
Cartesian product of every property's values, each cell constructed through
a `StateDefinition.Factory` which for blocks is the `BlockState` constructor.
**Eighty** — the states of oak stairs: four facings, two halves, five shapes,
two waterlogged values, every one of them a distinct object built before any
world existed.
`StateDefinition.propertiesByName` is a sorted map, so the axes are ordered
by property *name*, not by the order the block added them — for stairs the two
orders happen to coincide, at *facing, half, shape, waterlogged*. Two things
follow. The order of the global state ids follows it, because the product is
built by walking that map. And so does the field order of `BlockState.CODEC`
and `StateDefinition.propertiesCodec` — which is not the same as saying a
state is written alphabetically anywhere: NBT is a hash map on disk, and the
one alphabetical form is the *command* text, which `BlockStateParser` builds
without going near the codec. And
`StateDefinition.any` is the first cell of the product, which the `Block`
constructor installs as the default state unless the block calls
`Block.registerDefaultState` itself. Since `BooleanProperty.VALUES` lists
*true* before *false*, a block that does not override its default gets *true*
for every boolean it has. That is why `StairBlock` sets
`StairBlock.WATERLOGGED` to false explicitly: the alternative is stairs that
are born full of water.
There are exactly three concrete kinds of `Property` and no
*DirectionProperty* — facing is an `EnumProperty` over `Direction`.
`BlockStateProperties` is the shared pool of **124** of them, and several
share a serialised name while being different objects:
`BlockStateProperties.FACING`, `BlockStateProperties.FACING_HOPPER` and
`BlockStateProperties.HORIZONTAL_FACING` are all *facing* on disk.
### The state, a twenty-line leaf
`StateHolder` is the generic state, shared with `FluidState`
([fluids](../world/fluids.md)). It holds its owner, two parallel arrays of
property keys and values, and `StateHolder.neighbors` — a two-dimensional
table, property index by value index, answering *what state am I if this
property becomes that value*. `StateHolder.setValue` walks the key array
comparing references to find the row, asks `Property.getInternalIndex` for
the column, and returns the object already sitting in that cell. It
allocates nothing and it never constructs. The table is installed once by
`StateHolder.initializeNeighbors`, and a second call throws. Because every
state is built once, `StateHolder.equals` is final and identity-based: two
states are the same only if they are the same object.
`BlockBehaviour.BlockStateBase` extends it and is the state-to-block hop —
`BlockBehaviour.BlockStateBase.getShape`,
`BlockBehaviour.BlockStateBase.canSurvive` and the rest each forward to the
owning block with the state as the first argument. It is also where the
caches live, and they arrive in two waves. Its constructor copies the flat
values out of the block's `BlockBehaviour.Properties`. Everything that has to
ask a *virtual* question — the fluid state, whether it random-ticks, the
occlusion shape and its six faces, sky-light propagation, light dampening,
and the `BlockBehaviour.BlockStateBase.Cache` of collision shape and sturdy
faces built for every block without a dynamic shape — is filled later, by
`BlockBehaviour.BlockStateBase.initCache`, because those questions may look
at other blocks and so cannot be answered until every block exists.
`BlockState` itself is **twenty lines**: a constructor, a `BlockState.asState`
that returns *this*, and `BlockState.CODEC`. It exists so the generic
plumbing has a concrete type to name. `BlockBehaviour.BlockStateBase` is the
class people mean when they say *block state*.
The `Blocks` class initialiser is that second wave and the only caller of
`BlockBehaviour.BlockStateBase.initCache`: it walks
`BuiltInRegistries.BLOCK`, adds each state to `Block.BLOCK_STATE_REGISTRY`
and finishes it. Note what makes the result safe to share between the server
thread, the client thread, the chunk workers and the meshing pool — it is
**not** immutability, because those cached fields are non-final and written
long after the constructor. It is that the writes happen inside a class
initialiser, and every thread that later reaches a `BlockState` reaches it
through `Blocks`.
## Four decisions, four lookups
`BlockItem.getPlacementState` asks the block for a state and refuses if it
cannot have one. The default `Block.getStateForPlacement` returns the block's
default state; `StairBlock` overrides it and makes four decisions, each of
them one `StateHolder.setValue` into the table above.
`StairBlock.FACING` is `UseOnContext.getHorizontalDirection`, which is
`Entity.getDirection` — the way the player is *facing*, so the tall side ends
up away from them. `StairBlock.HALF` is `Half.BOTTOM` when the clicked face
is the top, `Half.TOP` when it is the bottom, and otherwise decided by
whether the hit point is in the upper or lower half of the clicked block.
`StairBlock.WATERLOGGED` is whether the fluid already at the target position
is `Fluids.WATER`. Then `StairBlock.SHAPE` is computed by
`StairBlock.getStairsShape` from the *partly built* state: it looks at the
neighbour in the direction the stair faces, and a stair there of the same
half with a perpendicular facing gives `StairsShape.OUTER_LEFT` or
`StairsShape.OUTER_RIGHT`; failing that it looks at the neighbour in the
opposite direction for `StairsShape.INNER_LEFT` or
`StairsShape.INNER_RIGHT`; failing both, `StairsShape.STRAIGHT`. In each case
`StairBlock.canTakeShape` vetoes the corner if the stair on the far side is
already aligned with this one. The same routine runs again in
`StairBlock.updateShape` every time a horizontal neighbour changes, which is
how a straight stair turns into a corner when you build next to it.
Everything in front of that — the click, the reach check, the block-then-item
ordering, the packet and the ack — belongs to
[block interaction](block-interaction.md) and
[prediction and acks](../client/prediction-and-acks.md). One sentence of it
matters here: the client runs the identical `BlockItem.place` under a
prediction, so the write below happens twice, once on each side, from the
same code. Almost everything that differs is inside the write; what
`BlockItem.place` itself does differently afterwards is to skip the
block-entity tag and the advancement trigger on the client, and the state that
lands is not affected by either.
`BlockItem.placeBlock` calls `Level.setBlock` with flags **11**,
`Block.UPDATE_ALL_IMMEDIATE`.
## The two update channels
This is the shape the rest of Part V refers back to. A write is two
half-writes with a re-read between them: `LevelChunk.setBlockState` changes
the world and runs the side effects that belong to the *position*, then
`Level.setBlock`'s tail runs the side effects that belong to the
*neighbourhood* — and only if the state it reads back is the one it asked
for.
```mermaid
flowchart TB
IN["Level.setBlock. Refuses a position out of bounds, and refuses everything on the server side of a debug world"]
IN --> SEC
subgraph CHUNK["inside LevelChunk.setBlockState"]
SEC["write the section"]
NOOP{"was the section all air and the state air, or is that exact state already there"}
HM["update the four live heightmaps: MOTION_BLOCKING, MOTION_BLOCKING_NO_LEAVES, OCEAN_FLOOR, WORLD_SURFACE. The two worldgen ones are not touched"]
LIGHT["if the section's emptiness flipped, tell the light engine and the chunk source. If the light properties differ, update the sky-light sources and queue LevelLightEngine.checkBlock"]
PRE["server only, flag 256 clear, and only when the block changed and the new state does not keep the old block entity: BlockEntity.preRemoveSideEffects. The removal itself runs on both sides"]
AFT["server only, flag 1 set or flag 64 set, and only when the block changed or the new block is a rail: affectNeighborsAfterRemoval"]
GUARD{"is the block at that position still the one just written"}
ONP["server only, flag 512 clear: BlockBehaviour.BlockStateBase.onPlace"]
BE["create, keep or replace the block entity, then ChunkAccess.markUnsaved"]
NOTHING["return nothing"]
SEC --> NOOP
NOOP -- "yes" --> NOTHING
NOOP -- "no" --> HM
HM --> LIGHT --> PRE --> AFT --> GUARD
GUARD -- "no" --> NOTHING
GUARD -- "yes" --> ONP --> BE
end
NOTHING --> FALSE["Level.setBlock returns false"]
BE --> READ{"re-read the position: is it the state we wrote"}
READ -- "no" --> TRUE["Level.setBlock returns true, having skipped its entire tail"]
READ -- "yes" --> DIRTY
subgraph TAIL["back in Level.setBlock"]
DIRTY["Level.setBlocksDirty. Empty on Level, on the client a re-mesh through LevelExtractor.setBlockDirty"]
SEND["flag 2, plus flag 4 clear on the client, plus a chunk at FullChunkStatus.BLOCK_TICKING or better on the server: Level.sendBlockUpdated"]
NB["flag 1: Level.updateNeighborsAt, and on the server also updateNeighbourForOutputSignal when the new state has an analog output"]
SHAPE["flag 16 clear and updateLimit still positive, with flags 1 and 32 masked out of what it passes on: three shape passes, indirect for the old state, direct for the new, indirect for the new"]
POI["Level.updatePOIOnBlockStateChange"]
DIRTY --> SEND --> NB --> SHAPE --> POI
end
POI --> TRUE
```
### Inside the chunk write
The section write, the four heightmaps and the light checks are the same on
both sides — that is [chunk anatomy](../world/chunk-anatomy.md)'s territory.
Three things after them are not.
`BlockEntity.preRemoveSideEffects` is the block entity's last word before it
is unregistered — the chest scattering its contents, say. It needs the
server, and it needs `Block.UPDATE_SKIP_BLOCK_ENTITY_SIDEEFFECTS` clear; the
removal that follows it happens either way, on both sides
([block entities](block-entities.md)).
`BlockBehaviour.BlockStateBase.affectNeighborsAfterRemoval` is how the
outgoing block tells its neighbours it is gone — a piston head taking its base,
a broken lever telling the block it powered. It is *not* how a door drops its
other half:
`DoorBlock` does not override it, and the top half goes down the shape channel
instead ([block interaction](block-interaction.md)). It is easy to put in the wrong place:
it runs **inside** the chunk write, before the new state is even confirmed,
not in `Level.setBlock`'s tail with the other neighbour work. It needs the
server, it needs `Block.UPDATE_NEIGHBORS` set *or* the moved-by-piston bit,
and it also needs the *block* to have changed — a write that only changes a
property does not fire it, unless the new block is a `BaseRailBlock`.
Then the chunk re-reads its own section. If a side effect has already
replaced what was just written, `LevelChunk.setBlockState` returns nothing at
all and `Level.setBlock` reports false. Otherwise
`BlockBehaviour.BlockStateBase.onPlace` runs — server-side, with
`Block.UPDATE_SKIP_ON_PLACE` clear — and the block entity is created, kept
or replaced. A block entity that disagrees with the new state is logged as
*mismatched* and thrown away.
### Back in Level.setBlock's tail
The first two steps of the tail are how the change becomes visible.
`Level.setBlocksDirty` is empty on `Level` itself; on the client it reaches
`LevelExtractor.setBlockDirty`, which re-meshes only if
`ModelManager.requiresRender` says the two states look different. The
broadcast that follows is gated on `Block.UPDATE_CLIENTS`, and then on
opposite conditions per side: the client also needs
`Block.UPDATE_INVISIBLE` clear, the server also needs the chunk to be at
`FullChunkStatus.BLOCK_TICKING` or better, so a write into a chunk that is
loaded but not yet simulating tells nobody. Worldgen is silent for a different
reason again: it never reaches `Level.setBlock` at all, writing through
`WorldGenRegion` instead.
The last three are the two update channels proper, and the difference
between them is the fact the rest of this part rests on:
**Neighbour updates are server-only.** `Level.updateNeighborsAt` and
`Level.neighborChanged` are empty methods on `Level`, overridden only by
`ServerLevel`. Gated on `Block.UPDATE_NEIGHBORS`, the server hands the
position to its `CollectingNeighborUpdater`, which visits the six neighbours
in `NeighborUpdater.UPDATE_ORDER` — west, east, down, up, north, south —
calling each one's `BlockBehaviour.neighborChanged`. Beside it,
`Level.updateNeighbourForOutputSignal` reaches the comparators in the four
horizontal directions, directly or through one redstone conductor
([signal and dust](signal-and-dust.md)).
**Shape updates run on both sides.** `Level.neighborShapeChanged` is
implemented on `Level`, and both a `ServerLevel` and a `ClientLevel` own a
`CollectingNeighborUpdater`. Unless `Block.UPDATE_KNOWN_SHAPE` is set,
`Level.setBlock` runs three passes with a decremented limit and with
`Block.UPDATE_NEIGHBORS` and `Block.UPDATE_SUPPRESS_DROPS` masked out of the
flags it propagates: `BlockBehaviour.BlockStateBase.updateIndirectNeighbourShapes`
for the *old* state, then
`BlockBehaviour.BlockStateBase.updateNeighbourShapes` for the new, then the
indirect pass again for the new. The middle one is the familiar one: six
neighbours in `BlockBehaviour.UPDATE_SHAPE_ORDER` — west, east, north, south,
down, up, a *different* order from the neighbour channel — each asked for a
new state through `BlockBehaviour.BlockStateBase.updateShape` and then
handed to `Block.updateOrDestroy`. The indirect passes are the hook a block
uses to reach past its six neighbours. `Block.UPDATE_LIMIT`, 512, is the
budget that stops the cascade.
And there is the catch. `Block.updateOrDestroy` writes the new state on
either side — but when the new state is air its destroy branch is
server-gated, going through `Level.destroyBlock`. So a shape update that
turns a block into nothing deletes it on the server and does nothing at all
on the client, which then waits to be told.
### The flag word
The flag word is `Level.setBlock`'s third argument — its last, except on the
four-argument overload that takes an update limit after it. It is a bit set, tagged in signatures by
`Block.UpdateFlags`, an annotation that carries no values of its own. The
flowchart above names its bits by number; the ten bits, what reads each one
and the four named combinations are in
[block update flags](../../reference/block-update-flags.md). Placement's
**11** is `Block.UPDATE_ALL_IMMEDIATE`, and `Block.UPDATE_LIMIT` is also 512
without being a bit at all — it is the default recursion budget for the
shape cascade.
## Questions players ask
**Why did `Level.setBlock` say false when the block is right there?** It
returns true whenever the chunk accepted the write, even if the state was
changed again immediately afterwards and the whole tail was skipped. It
returns false from three statements: a position out of bounds, the server side
of a debug world, and the chunk write coming back with nothing. That last one
has three causes of its own, and only the third is a real failure — writing air
into a section that holds only air, writing the state that is already there
(states being interned, that is an identity comparison), or a side effect
inside the chunk write having replaced the block before it could be confirmed.
The first two are the common ones.
**Why does my property lookup throw when the property looks identical?**
Because states match properties by *identity* and properties match each other
by *value*. `StateHolder.setValue` compares `Property` references with `==`,
while `Property.equals` compares the value class and the name — refined by
`IntegerProperty` and `EnumProperty` to compare the value list too. So two
separately constructed properties can be equal to one another and still make
`StateHolder.setValue` throw *Cannot set property … as it does not exist*.
Use the `BlockStateProperties` constant, not a look-alike.
**Does an unknown block state really become air?** For `Block.getId` and
`Block.stateById`, yes, and that is the pair behind block-break particles,
the falling-block spawn packet and `EntityDataSerializers.OPTIONAL_BLOCK_STATE`.
It is not universal. `ClientboundBlockUpdatePacket.STREAM_CODEC` reads the
same table through `ByteBufCodecs.idMapper`, which uses `IdMap.byIdOrThrow`
and fails the connection instead, and
`ClientboundSectionBlocksUpdatePacket` decodes with `IdMapper.byId`, which
answers null. The tolerant lookup is a property of the two static methods,
not of the id.
## Where to look
`Blocks.register` · `BlockBehaviour.Properties.setId` ·
`StateDefinition.Builder.create` ·
`StateDefinition.StateCollection.fillNeighborsForState` ·
`StateHolder.setValue` · `StateHolder.neighbors` ·
`BlockBehaviour.BlockStateBase.initCache` · `Block.BLOCK_STATE_REGISTRY` ·
`StairBlock.getStateForPlacement` · `StairBlock.getStairsShape` ·
`BlockItem.placeBlock` · `LevelChunk.setBlockState` · `Level.setBlock` ·
`Block.updateOrDestroy` · `NeighborUpdater.executeShapeUpdate`
---
*Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes `tools/verify_names.py`.*
---
# Block interaction
> Verified against **Minecraft 26.2** · Part V · A player right-clicks the bottom half of an oak door, and the top half opens without a single neighbour update.
You are standing in front of a closed oak door, crosshair on its bottom half,
and you press the use key. Before the tick is over the door is open on your
screen, both halves of it, and a packet is on its way to a server that has
not yet been asked. The obvious guess about how the top half found out is
wrong. Opening a door fires **no neighbour updates at all** — `DoorBlock`
writes with flags 10, and the neighbour bit is not among them — and the top
half follows anyway, down the *shape* channel, which is the half of the
update machinery the client also runs. That is why a door feels instant on a
laggy server and a redstone lamp does not.
> **The contract both halves run under.** The client acts at once and remembers the state it overwrote, under a sequence number it sends with the action. The server's `ClientboundBlockChangedAckPacket` is a receipt for that number and *not* a verdict — it is sent for actions the server refused exactly as for actions it allowed — and correctness comes from ordering instead: any correction the server means to send travels in the same tick and earlier in the stream than the receipt. A correction *replaces* what the client remembered rather than being weighed against it, so when the receipt arrives the client writes back whatever the entry now holds — and only where that differs from what is on screen. [Prediction and acknowledgement](../client/prediction-and-acks.md) owns that machinery; this page and [block breaking](block-breaking.md) are its two applications.
## The cast
| class | what it decides | thread |
|---|---|---|
| `Minecraft` | that a use-key press becomes one `Minecraft.startUseItem`, and that the main hand is tried before the off hand | Render |
| `MultiPlayerGameMode` | the client's copy of the whole decision, wrapped in one prediction | Render |
| `InteractionResult` | whether the caller stops here, who swings, and what the hand ends up holding | a record, no thread |
| `BlockBehaviour.BlockStateBase` | which of the two block hooks a state answers with, and when its six neighbours are asked to re-fit | either side |
| `DoorBlock` | whether this door opens by hand, what it writes, and what the other half becomes | either side |
| `CollectingNeighborUpdater` | the order queued updates run in, and where a runaway cascade is cut | whichever thread wrote the block |
| `ServerGamePacketListenerImpl` | the gate list the packet must pass, and what each refusal answers with | Server |
| `ServerPlayerGameMode` | the same inner order as the client, plus the advancement triggers | Server |
## The whole click, both sides
```mermaid
sequenceDiagram
participant MC as Minecraft
participant MPGM as MultiPlayerGameMode
participant CL as ClientLevel
participant DB as DoorBlock
participant CNU as CollectingNeighborUpdater
participant SGPL as ServerGamePacketListenerImpl
participant SL as ServerLevel
Note over MC,CNU: one client tick, all of it before the packet leaves
MC->>MPGM: startUseItem, main hand first, useItemOn with the BlockHitResult
MPGM->>MPGM: startPrediction opens sequence n
MPGM->>DB: useItemOn returns TRY_WITH_EMPTY_HAND, so useWithoutItem runs
DB->>CL: canOpenByHand, cycle OPEN, setBlock on the lower half with flags 10
CL->>CNU: updateNeighbourShapes, six directions, limit 511
CNU->>DB: updateShape on the upper half, direction DOWN
CNU->>CL: updateOrDestroy writes the upper half, flags 10, limit 511
DB->>CL: playSound with the clicker as except, so only they hear it
MPGM->>SGPL: ServerboundUseItemOnPacket carrying hand, hit and n
MC->>SGPL: ServerboundSwingPacket, because SUCCESS swings on the client
Note over SGPL,SL: server tick, packets drained before the levels tick
SGPL->>SGPL: ackBlockChangesUpTo n, then reach, hit box, height, spawn protection
SGPL->>DB: ServerPlayerGameMode.useItemOn, the same inner order
DB->>SL: setBlock lower half, then the shape pass writes the upper half
DB->>SL: playSound to everyone but the clicker, gameEvent BLOCK OPEN
SGPL-->>CL: ClientboundBlockUpdatePacket, clicked position and face neighbour
Note over SGPL,SL: still this tick, levels tick, then connections tick
SL-->>CL: ClientboundSectionBlocksUpdatePacket, both halves in one section
SGPL-->>CL: ClientboundBlockChangedAckPacket for n
CL->>CL: endPredictionsUpTo n, both halves already agree, nothing is written
```
## One press, one hand at a time
`Minecraft.handleKeybinds` runs from the client tick and only when no screen
and no overlay is open. Every queued press of the use key becomes its own
`Minecraft.startUseItem`, unthrottled; `Minecraft.rightClickDelay` gates the
held-down auto-repeat alone, and `Minecraft.startUseItem` is what sets it, to
four ticks, and only when `MultiPlayerGameMode.isDestroying` is false. A
player already using an item (drawing a bow, eating) never reaches that
branch at all: the queued presses are drained and discarded.
Inside, after `LocalPlayer.isHandsBusy`, the hands are tried in the order
`InteractionHand.MAIN_HAND` then `InteractionHand.OFF_HAND`. Each hand's
stack must pass `ItemStack.isItemEnabled` — a disabled item aborts the whole
loop, not just its own hand — and then, for a `BlockHitResult`, the hand goes
to `MultiPlayerGameMode.useItemOn`. A `InteractionResult.Success` or an
`InteractionResult.Fail` ends the loop; only an `InteractionResult.Pass`
falls through to `MultiPlayerGameMode.useItem` (right-click air,
`ServerboundUseItemPacket`) and then to the off hand. The door returns
success on the main hand, so the off hand is never asked.
`MultiPlayerGameMode.useItemOn` sends a `ServerboundSetCarriedItemPacket`
first if the hotbar selection has moved
(`MultiPlayerGameMode.ensureHasSentCarriedItem`), refuses outright if the
target is outside the world border, and otherwise opens the prediction with
`MultiPlayerGameMode.startPrediction`, which allocates sequence *n*, runs the
whole client-side interaction inside it, and sends the
`ServerboundUseItemOnPacket` the interaction returned.
## Block, then empty hand, then item
`MultiPlayerGameMode.performUseItemOn` and `ServerPlayerGameMode.useItemOn`
run the same three-step order. First the block is offered the item:
`BlockBehaviour.BlockStateBase.useItemOn`, whose `BlockBehaviour.useItemOn`
default answers `InteractionResult.TRY_WITH_EMPTY_HAND`. That sentinel — and
**only when the hand is `InteractionHand.MAIN_HAND`** — routes to
`BlockBehaviour.BlockStateBase.useWithoutItem`, whose
`BlockBehaviour.useWithoutItem` default is `InteractionResult.PASS`. If the
block consumed nothing, the item gets its turn through `ItemStack.useOn`,
provided the stack is non-empty and not held back by
`ItemCooldowns.isOnCooldown`. Sneaking skips the first two steps, but only
when *some* hand holds something: the guard is
`Player.isSecondaryUseActive` **and** a non-empty main or off hand, so an
empty-handed sneak still opens the door.
Three things differ between the two copies, and none of them is the inner
order. The server tests the block's `BlockBehaviour.requiredFeatures` through
`FeatureElement.isEnabled` as its very first statement, while the client
tests the same thing through `ClientPacketListener.isFeatureEnabled` inside
the not-sneaking branch. A spectator gets a flat
`InteractionResult.CONSUME` on the client, but on the server is routed to
`BlockBehaviour.BlockStateBase.getMenuProvider` and may end up with an open
container. And the advancement triggers exist only on the server:
`CriteriaTriggers.ITEM_USED_ON_BLOCK` when the item did it,
`CriteriaTriggers.DEFAULT_BLOCK_USE` when the empty-hand hook did, and
`CriteriaTriggers.ANY_BLOCK_USE` from the packet handler for anything that
consumed.
The result is the vocabulary the whole pipeline turns on.
`InteractionResult` is a sealed interface of four records —
`InteractionResult.Success`, `InteractionResult.Fail`,
`InteractionResult.Pass` and `InteractionResult.TryEmptyHandInteraction` —
and the swing is part of it, not a separate decision.
`InteractionResult.SUCCESS`, `InteractionResult.SUCCESS_SERVER` and
`InteractionResult.CONSUME` are all `InteractionResult.Success` values
differing only in `InteractionResult.SwingSource`: the client animates and
sends `ServerboundSwingPacket`, the server animates for the trackers, or
nobody does. `InteractionResult.consumesAction` is what the server's branches test — the
client's own loop matches on the record types instead —
but the record carries two more answers besides —
`InteractionResult.Success.wasItemInteraction`, which decides whether
`Stats.ITEM_USED` is awarded, and
`InteractionResult.Success.heldItemTransformedTo`, which both game modes use
to swap the stack the hand ends up holding.
## The door writes ten
`DoorBlock.useWithoutItem` asks `BlockSetType.canOpenByHand` and, if the
answer is no, returns `InteractionResult.PASS` and lets the item try. For oak
it is yes: `StateHolder.cycle` flips `DoorBlock.OPEN`, and `Level.setBlock`
is called with flags **10** — `Block.UPDATE_CLIENTS` and
`Block.UPDATE_IMMEDIATE`, with `Block.UPDATE_NEIGHBORS` **clear**. Then
`DoorBlock.playSound`, `LevelAccessor.gameEvent` with `GameEvent.BLOCK_OPEN` or
`GameEvent.BLOCK_CLOSE` (posting is [game events and
vibrations](../world/game-events-and-vibrations.md)), and
`InteractionResult.SUCCESS`. Exactly this code runs on both sides.
Ten is the whole story of the page. Bit 2 broadcasts, and on the client
`LevelExtractor.blockChanged` reads bit 8 not as *immediate* but as
*a player did this*, which can buy the section a priority remesh — `LevelRenderer`
acts on that mark only when the *Chunk Builder* option is set to prioritise
nearby or player-affected sections, which the fancy graphics preset does and
the default does not. Bit 1 is absent, so `Level.setBlock` never reaches its
neighbour fan-out — and on the client that would be a no-op anyway. What the flags then feed, and the rest of
what a write does, is the flowchart on [blocks and
states](blocks-and-states.md#the-two-update-channels); everything below is
the part of it the door
actually walks.
## The shape channel, which both sides run
With `Block.UPDATE_KNOWN_SHAPE` clear and the update limit still positive,
the tail of `Level.setBlock` calls
`BlockBehaviour.BlockStateBase.updateNeighbourShapes`, which walks all six of
`BlockBehaviour.UPDATE_SHAPE_ORDER` — west, east, north, south, down, up —
and asks each neighbour, one top-level cascade at a time, whether it still
fits. Note which direction travels: for the block above, the level is handed
`Direction.DOWN`, the direction pointing *from that neighbour back at the
door*. Each hop costs one from the limit, so the upper half is written at 511
and asks its own neighbours at 510.
The whole distinction rests on three method bodies. `Level.updateNeighborsAt`
and `Level.neighborChanged` are **empty on `Level`** and overridden only by
`ServerLevel`; `Level.neighborShapeChanged` is implemented on `Level` itself
and therefore runs on both sides. Shape updates are predictable because the
client genuinely runs them; neighbour updates are not because the client's
copy does nothing.
`DoorBlock.updateShape` answers four of its six callers with the state it was
given: the whole method is behind a test for the vertical axis, so the four
horizontals fall through to `BlockBehaviour.updateShape`, which returns the
state unchanged. The other two directions are where the door lives, and there
are three outcomes between them. Asked from the matching vertical direction — up
for a lower half, down for an upper — it returns **the neighbour's own state
with `DoorBlock.HALF` swapped to its own**, so open, facing, hinge and
powered are copied wholesale, which is why the top half is already open by
the time it is written. Asked from that same direction when the neighbour is
*not* the other half, it returns `Blocks.AIR`. And a lower half asked from
`Direction.DOWN` returns air when `DoorBlock.canSurvive` fails — the block
beneath must be face-sturdy upward. `Block.updateOrDestroy` then compares:
a different non-air state becomes a `Level.setBlock` at the inherited limit,
and air becomes a `Level.destroyBlock`, **but only when the level is not the
client's**. That server-gated destroy branch, which writes with flags 3 and
posts `GameEvent.BLOCK_DESTROY`, is the whole of "break the bottom and the
top pops".
## The updater underneath: a stack, drained depth-first
Every `Level` builds one `Level.neighborUpdater`, a
`CollectingNeighborUpdater`, in its constructor — on the client too. Requests
arrive as four small implementations of
`CollectingNeighborUpdater.NeighborUpdates`, three of them records:
`CollectingNeighborUpdater.ShapeUpdate` for the door's case,
`CollectingNeighborUpdater.SimpleNeighborUpdate` and
`CollectingNeighborUpdater.FullNeighborUpdate` for a single neighbour, and
`CollectingNeighborUpdater.MultiNeighborUpdate`, one request that walks up to
six directions in `NeighborUpdater.UPDATE_ORDER` — a different order from the
shape one, west, east, down, up, north, south.
`CollectingNeighborUpdater.addAndRun` decides where a request goes by whether
a cascade is already running. The first one is pushed on
`CollectingNeighborUpdater.stack` and drained immediately by
`CollectingNeighborUpdater.runUpdates`; anything requested from inside a
running hook lands in `CollectingNeighborUpdater.addedThisLayer` and is
pushed on top of the stack before the current record's remaining work, so the
drain is depth-first — a cascade finishes its children before its siblings.
`NeighborUpdater.executeShapeUpdate` and `NeighborUpdater.executeUpdate` do
the actual calls and wrap any throw in a crash report.
The chain limit is coarser than it looks and gentler than folklore says.
`CollectingNeighborUpdater.count` increments once per **request**, not per
depth level and not per block touched — a
`CollectingNeighborUpdater.MultiNeighborUpdate` is one
request that can expand to six calls — and past
`CollectingNeighborUpdater.maxChainedNeighborUpdates` further requests are
silently dropped after a single logged error, never a crash. The count is
reset when the outermost cascade unwinds, so the budget is per top-level
cascade. It is **not** a game rule: it is the *max-chained-neighbor-updates*
line in *server.properties*
(`DedicatedServerProperties.maxChainedNeighborUpdates`, default one million,
read through `DedicatedServer.getMaxChainedNeighborUpdates`), with
`MinecraftServer.getMaxChainedNeighborUpdates` hard-coding the same number
for the integrated server and `ClientLevel` passing the literal. Keep it
distinct from `Block.UPDATE_LIMIT`, the 512 that bounds nested writes: that
one counts *recursion depth* and is what the door's 511 and 510 come from.
## The gate list, and what each refusal answers with
`ServerGamePacketListenerImpl.handleUseItemOn` tests in this order, and the
interesting column is the second one — the refusals do not answer alike, and
one of them lies.
| the gate | what the client gets when it fails |
|---|---|
| `ServerGamePacketListenerImpl.hasClientLoaded` | nothing, not even the receipt |
| `ItemStack.isItemEnabled` on the held stack | nothing |
| `Player.isWithinBlockInteractionRange`, with 1.0 of slack | nothing |
| the hit location lying within one block of the clicked block's centre on every axis — a 2×2×2 box, not the block | nothing, plus a server-side log line naming the player |
| above `LevelHeightAccessor.getMaxY` or below `LevelHeightAccessor.getMinY` | `ServerPlayer.sendBuildLimitMessage` — an action-bar line, and **no block update** |
| `MinecraftServer.isUnderSpawnProtection` | `ServerPlayer.sendSpawnProtectionMessage`, plus both block updates |
| a pending teleport, or `ServerLevel.mayInteract` refusing for the world border | `ServerPlayer.sendBuildLimitMessage` — you are told you are building too high, whatever the real reason |
| everything passed | `ServerPlayerGameMode.useItemOn` runs, plus both block updates |
The two block updates are a `ClientboundBlockUpdatePacket` for the clicked
position and one for the block on its clicked face, sent whatever the
interaction returned — and they sit inside the branch below the build-height
test, which is why a click that was out of reach or off the block is answered
with silence while a click into spawn protection is answered with the
truth. The door's *other* half is in neither: it reaches the client with
everyone else's copy, through `ChunkHolder.broadcastChanges`, which turns two
changed positions in one section into a single
`ClientboundSectionBlocksUpdatePacket` (and into two
`ClientboundBlockUpdatePacket`s when the halves straddle a section boundary).
`ServerGamePacketListenerImpl.ackBlockChangesUpTo` was called before any of
the gates, and `ServerGamePacketListenerImpl.tick` emits the receipt when
`MinecraftServer.tickChildren` reaches connections — after the levels have
already broadcast ([the server tick](../server/server-tick.md)).
## Questions players ask
**Why can't I open an iron door by hand?** Because
`DoorBlock.useWithoutItem`'s first question is `BlockSetType.canOpenByHand`,
false on `BlockSetType.IRON` and `BlockSetType.GOLD` and true on
`BlockSetType.COPPER`. Nothing on this path reads
`BlockTags.WOODEN_DOORS` — the copper door proves it, since it opens by hand
and is not in that tag. The tag is for mining and fuel. Mobs that open doors ask
somewhere else again: `InteractWithDoor` reads
`BlockTags.MOB_INTERACTABLE_DOORS`, while the older goals read
`DoorBlock.isWoodenDoor`, which is `BlockSetType.canOpenByHand` under
another name.
**Why does the door sound different to me than to everyone else?**
`DoorBlock.playSound` passes the clicking player as the *except* entity, and
the two sides read that word oppositely: `ClientLevel.playSeededSound` plays
the sound **only** when the except entity is the local player, while
`ServerLevel.playSeededSound` broadcasts a `ClientboundSoundPacket` to
everyone in range **but** them. So you hear your own prediction and never the
server's copy — and since each side draws its own pitch from its own
`Level.getRandom` and its own seed from `Level.soundSeedGenerator`, your door
is genuinely a different sound from the one your friend heard.
**Why does breaking the bottom of a door remove the top on the server but
not on my screen?** Because that removal is a *shape* update whose destroy
half is server-only. Both sides run `DoorBlock.updateShape` on the upper
half, both get `Blocks.AIR` back, and both hand it to
`Block.updateOrDestroy` — where the air branch is wrapped in a not-client
check. Your client leaves the top half standing until the section packet
arrives; the server has already dropped it, with the flags-3 neighbour
updates that `Level.destroyBlock` implies.
**Why does opening a door lag a busy server when nothing is powered?**
Because the write still reaches `ServerLevel.sendBlockUpdated`, which
compares the old and new collision shapes and, when they differ, walks
`ServerLevel.navigatingMobs` — every tracked mob in the level, in full — asking
each whether the position is near enough to its remaining path to be worth
`PathNavigation.recomputePath`. A door changes shape every time it moves.
None of that exists on the client, which is one more reason your half of the
click is the fast half.
Left-click is the same contract with a different pipeline:
`Minecraft.startAttack` opens its own prediction and sends a
`ServerboundPlayerActionPacket` instead, and the block hook is
`BlockBehaviour.BlockStateBase.attack`. [Block breaking](block-breaking.md)
takes it from there.
## Where to look
`Minecraft.handleKeybinds` · `Minecraft.startUseItem` ·
`MultiPlayerGameMode.useItemOn` · `MultiPlayerGameMode.performUseItemOn` ·
`InteractionResult` · `BlockBehaviour.BlockStateBase.useItemOn` ·
`BlockBehaviour.BlockStateBase.useWithoutItem` · `DoorBlock.useWithoutItem` ·
`Level.setBlock` · `BlockBehaviour.BlockStateBase.updateNeighbourShapes` ·
`Level.neighborShapeChanged` · `CollectingNeighborUpdater.addAndRun` ·
`CollectingNeighborUpdater.runUpdates` ·
`NeighborUpdater.executeShapeUpdate` · `DoorBlock.updateShape` ·
`Block.updateOrDestroy` · `ServerGamePacketListenerImpl.handleUseItemOn` ·
`ServerPlayerGameMode.useItemOn` · `ChunkHolder.broadcastChanges`
---
*Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes `tools/verify_names.py`.*
---
# Block breaking
> Verified against **Minecraft 26.2** · Part V · A survival player holds left-click on stone with an iron pickaxe for eight ticks: two clocks that agree without talking, one loot roll, one cobblestone.
Hold the button on a stone block and two programs start counting. The client
adds a fraction to `MultiPlayerGameMode.destroyProgress` every client tick and
paints the crack; the server sets `ServerPlayerGameMode.destroyProgressStart`
to the tick the dig began and recomputes, from scratch, how far along it ought
to be. Between the first packet and the last, **neither clock is ever
mentioned on the wire** — no progress reports, no heartbeat, nothing but the
swing animation going up — and on the eighth tick the two answers are the same
number. That agreement is what the whole design rests on, and it is
also why the failure mode is so strange: **releasing the button does not
cancel a break.** A client that stops too early gets a deferral, not a
rejection. The receipt for the STOP goes out in the same tick, the client
dutifully puts the stone back — and then watches it vanish a second time when
the server's own clock finishes the job, with nothing the player can do in
between.
> **The contract both halves run under.** The client acts at once and remembers the state it overwrote, under a sequence number it sends with the action. The server's `ClientboundBlockChangedAckPacket` is a receipt for that number and *not* a verdict — it is sent for actions the server refused exactly as for actions it allowed — and correctness comes from ordering instead: any correction the server means to send travels in the same tick and earlier in the stream than the receipt. A correction *replaces* what the client remembered rather than being weighed against it, so when the receipt arrives the client writes back whatever the entry now holds — and only where that differs from what is on screen. [Prediction and acknowledgement](../client/prediction-and-acks.md) owns that machinery; [block interaction](block-interaction.md) and this page are its two applications.
## The cast
| class | what it decides | thread |
|---|---|---|
| `Minecraft` | that the button is down and the crosshair is on a block, and whether this frame's tick starts a dig or continues one | Render |
| `MultiPlayerGameMode` | the client's clock: accumulated progress, the five-tick pause after a break, when to predict the removal and send STOP | Render |
| `ClientLevel` | the predicted air, and the crack overlays — every breaker's, including the local player's | Render |
| `ServerGamePacketListenerImpl` | which of the eight `ServerboundPlayerActionPacket.Action`s this is, and when the receipt for its sequence is flushed | Server |
| `ServerPlayerGameMode` | the server's clock, the reach and permission gates, the 0.7 verdict, and the deferral | Server |
| `BlockBehaviour.BlockStateBase` | hardness, whether the block needs the right tool for drops, and the per-tick fraction | either |
| `Tool` | how fast this stack mines this block, and whether it drops — two separate answers from one rule list | either |
| `Block` | the removal, the particles and sound event, the stat, the exhaustion and the loot roll | Server |
## One dig, end to end
```mermaid
sequenceDiagram
participant MC as Minecraft
participant MPGM as MultiPlayerGameMode
participant CL as ClientLevel
participant SGPL as ServerGamePacketListenerImpl
participant SPGM as ServerPlayerGameMode
participant SL as ServerLevel
participant Block as Block
Note over MC,Block: client tick 1, the button goes down
MC->>MPGM: startAttack sees a non-air block, startDestroyBlock
MPGM->>CL: startPrediction opens sequence N, attack runs, getDestroyProgress is 0.133
MPGM->>CL: destroyBlockProgress with stage -1, which clears my own crack
MPGM->>SGPL: ServerboundPlayerActionPacket START, sequence N
SGPL->>SPGM: handleBlockBreakAction, destroyProgressStart = gameTicks
SPGM->>SL: destroyBlockProgress broadcasts the first crack stage to everyone else within 32
SGPL-->>CL: ClientboundBlockChangedAckPacket N, nothing to reconcile
loop client ticks 1-7 beside server ticks, with a swing packet up and nothing about progress either way
MC->>MPGM: continueAttack, continueDestroyBlock adds 0.133
MPGM->>CL: my own stage, plus a hit sound every fourth tick
SPGM->>SPGM: tick, incrementDestroyProgress recomputes 0.133 x (elapsed + 1)
SPGM->>SL: a ClientboundBlockDestructionPacket only when the tenth changes
end
Note over MC,Block: client tick 8, and the client is the only side that acts on 1.0
MPGM->>CL: prediction M, playerWillDestroy plays event 2001 locally, setBlock to air with flags 11
MPGM->>SGPL: ServerboundPlayerActionPacket STOP, sequence M
Note over SGPL,Block: a server tick, STOP handled off the task queue
SGPL->>SPGM: handleBlockBreakAction STOP, own progress 1.07 clears the 0.7 bar
SPGM->>Block: destroyAndAck then destroyBlock, playerWillDestroy sends 2001 to all but the breaker
SPGM->>SL: removeBlock writes the fluid-or-air state under flags 3
SPGM->>Block: mineBlock spends one durability point, then playerDestroy
Block->>SL: the blocks/stone table rolls, popResource adds the ItemEntity
Note over SGPL,Block: same tick, later: the levels broadcast, then the connections flush
SL-->>CL: ClientboundBlockUpdatePacket air, absorbed by the ledger
SGPL-->>CL: ClientboundBlockChangedAckPacket M, syncBlockState finds air already
```
## Two clocks, and the plus one that makes them agree
`BlockBehaviour.getDestroyProgress` is the shared formula, and both sides call
it through `BlockBehaviour.BlockStateBase.getDestroyProgress` with the same
arguments. It answers the fraction of the block broken *per tick*: the
player's speed, divided by the block's hardness, divided by **30** when
`Player.hasCorrectToolForDrops` says yes — which it does for every block that
does not require a tool at all — and by **100** when it says no. Hardness −1
returns zero forever. Hardness *zero* is not special-cased, so an instabreak
block divides by zero and returns infinity: that, and not a branch on
hardness, is what sends the START handler down its insta-mine path on the
first tick.
`Player.getDestroySpeed` builds the numerator from `Inventory.getSelectedItem`
in one pass, and the surprising parts are all constants rather than data. The
stack's `ItemStack.getDestroySpeed` gives the base. If that is above 1.0 —
only then — `Attributes.MINING_EFFICIENCY` is *added*, which is where
`Enchantments.EFFICIENCY` lands, at level² + 1. Haste and conduit power are
read together through `MobEffectUtil.hasDigSpeed` and
`MobEffectUtil.getDigSpeedAmplification`, which returns the **greater** of the
two amplifiers — a beacon and a conduit are interchangeable and do not stack —
and multiply by 1 + 0.2 × (amplifier + 1). Mining fatigue is
not an attribute at all but four literal factors switched on the amplifier —
0.3, 0.09, 0.0027, and 0.00081 for anything higher. Then
`Attributes.BLOCK_BREAK_SPEED`, then `Attributes.SUBMERGED_MINING_SPEED` (0.2
by default) if the eyes are in `FluidTags.WATER`, and finally **divide by five
if the player is not on the ground**.
For stone at hardness 1.5 and an iron pickaxe at 6.0, that is 6 ÷ 1.5 ÷ 30 =
0.133 per tick, so the eighth tick is the one that passes 1.0.
### Why the two answers match
The two clocks count differently and still land on the same number. The client
accumulates: `MultiPlayerGameMode.continueDestroyBlock` adds one tick's
fraction each time it runs. The server keeps no accumulator, and its number is
in fact a tick *ahead* of the client's all the way down — it simply never acts
on it, because the live branch of `ServerPlayerGameMode.tick` throws the value
away and only the delayed branch compares it with 1.0.
`ServerPlayerGameMode.incrementDestroyProgress` multiplies the per-tick
fraction by *elapsed ticks plus one*, and that plus one is exactly the client's
first `Minecraft.continueAttack`, which happens in the same client tick as the
`Minecraft.startAttack` that opened the dig. Recomputing rather than
accumulating has a second consequence: swap tools or lose haste mid-dig and the
server rescales the *whole* dig retroactively, while the client keeps the
progress it already banked.
They agree without talking because every input is either static data both
sides loaded — hardness, the block tags, the `Tool` component travelling with
the stack — or a syncable attribute, or a synced effect. The inputs that could
drift are the ones the client reports rather than shares, and the sharpest of
them is which slot is selected:
`MultiPlayerGameMode.ensureHasSentCarriedItem` runs at the top of every
`MultiPlayerGameMode.continueDestroyBlock` to send a
`ServerboundSetCarriedItemPacket` the moment it changes.
## The button is not the switch
**Seventy per cent** — how much of the server's own clock a STOP must have run
before the block breaks immediately (`ServerPlayerGameMode.handleBlockBreakAction`).
For stone that is about two ticks of slack. Below the bar the STOP is not
refused: the handler sets `ServerPlayerGameMode.hasDelayedDestroy`, copies the
position and the *original* start tick into
`ServerPlayerGameMode.delayedDestroyPos` and
`ServerPlayerGameMode.delayedTickStart`, and lets its own clock run on. The
sequence is acknowledged regardless — `ServerGamePacketListenerImpl.handlePlayerAction`
calls `ServerGamePacketListenerImpl.ackBlockChangesUpTo` for all three break
actions, unconditionally, after the game mode has run. So the receipt arrives
with no correction in front of it, the client settles prediction M against the
stone it recorded, and `ClientLevel.syncBlockState` puts the stone back. The
block is visibly there again. A tick or two later the server's clock crosses
1.0, `ServerPlayerGameMode.destroyBlock` runs, and the air arrives as an
ordinary block update. The prediction was not wrong — it was undone and then
redone.
Letting go changes nothing. The ABORT branch clears
`ServerPlayerGameMode.isDestroyingBlock` and erases the crack, and it never
touches `ServerPlayerGameMode.hasDelayedDestroy` — and
`ServerPlayerGameMode.tick` tests the delayed dig **first**, before the live
one. Starting a dig on a different block does not help either: the START is
processed normally, but the delayed branch keeps winning the tick. The delayed
path re-checks almost nothing on its way through — not reach, not
`MinecraftServer.isUnderSpawnProtection`, not `ServerLevel.mayInteract`, not
that the player is still in the same room. It calls
`ServerPlayerGameMode.destroyBlock` directly rather than
`ServerPlayerGameMode.destroyAndAck`, so a failure there is silent, with no
corrective block update. Its only escape is the block turning to air: that is
the one condition `ServerPlayerGameMode.tick` tests before recomputing
progress.
### What a real refusal looks like
The refusals that *are* refusals differ in what they send back, and the
difference is observable. A failed `Player.isWithinBlockInteractionRange`
check — which allows a full block of slack — sends **nothing at all**, and it
guards ABORT as well as START, so an abort from too far away is dropped on the
floor. Being above `LevelHeightAccessor.getMaxY`, failing
`ServerLevel.mayInteract` or failing `Player.blockActionRestricted` each answer
with a `ClientboundBlockUpdatePacket` carrying the true state. Spawn protection
answers with an overlay message from `ServerPlayer.sendSpawnProtectionMessage`
and no block update whatsoever. Every one of those exits is named in a string
behind `SharedConstants.DEBUG_BLOCK_BREAK`, which is the best map of this state
machine there is.
## Speed and drops are two scans of one list
`DataComponents.TOOL` holds a `Tool`: a list of `Tool.Rule`, a
`Tool.defaultMiningSpeed`, a `Tool.damagePerBlock` and a
`Tool.canDestroyBlocksInCreative`. Each rule names a set of blocks and carries
an *optional* speed and an *optional* drop verdict, so a rule can answer one
question and stay silent on the other. `Tool.getMiningSpeed` and
`Tool.isCorrectForDrops` are two independent walks of the same list, each
taking the first rule that both matches the block *and* carries the field it
came for. `ToolMaterial.applyToolProperties` builds every pickaxe, axe, shovel
and hoe from exactly two rules, in this order:
| the iron pickaxe's rules, in order | what `Tool.getMiningSpeed` does | what `Tool.isCorrectForDrops` does |
|---|---|---|
| deny drops on `BlockTags.INCORRECT_FOR_IRON_TOOL` — no speed field | skips it | obsidian matches, answers **no** |
| mine and drop `BlockTags.MINEABLE_WITH_PICKAXE` at 6.0 | obsidian matches, answers **6.0** | never reached |
| nothing matched | `Tool.defaultMiningSpeed`, 1.0 | false |
That is why an iron pickaxe mines obsidian and drops nothing. The speed scan
falls through the deny rule — it has no speed — and takes the full pickaxe
6.0; the drop scan stops at the deny. The block still takes forever, but for a
different reason: `Player.hasCorrectToolForDrops` is false, so
`BlockBehaviour.getDestroyProgress` divides by 100 instead of 30. No item in
the game uses all three rule shapes: `Tool.Rule.deniesDrops` and
`Tool.Rule.overrideSpeed` never appear in the same `Tool`, because the items
that deny drops on a tag are exactly the ones that name their own speed on
another. The sword is one of exactly three whose
`Tool.canDestroyBlocksInCreative` is false — the other two are the mace and the
trident.
## The cracks belong to everyone but you
`ServerLevel.destroyBlockProgress` sends a `ClientboundBlockDestructionPacket`
to every player in the level within 32 blocks whose entity id is not the
breaker's. You are never sent your own cracks. What you see is
`MultiPlayerGameMode` writing straight into `ClientLevel.destroyBlockProgress`
each tick — the same method the packet handler calls, reached by a different
road. The same asymmetry runs through the break itself: `Block.playerWillDestroy`
posts level event 2001, which `ServerLevel.levelEvent` broadcasts within 64
blocks *excluding* the breaker, because the breaker already played it locally
inside `MultiPlayerGameMode.destroyBlock`.
`BlockDestructionProgress` is a plain holder — id, position, progress, a last
touched tick — and `BlockDestructionProgress.setProgress` clamps only the top,
at 10. The 0–9 window everybody quotes is enforced by the *caller*:
`ClientLevel.destroyBlockProgress` stores a stage only for values in [0, 10),
and reads anything else — including the −1 that
`MultiPlayerGameMode.getDestroyStage` returns at zero progress — as an
instruction to **remove** that breaker's entry. Entries are indexed twice, by
breaker id and by position, the latter into a sorted set so the deepest crack
at a position wins; `LevelExtractor` collects those within 32 blocks of the
camera each frame. Entries untouched for 400 ticks are swept every twentieth
tick, which is what eventually clears the cracks left by someone who
disconnected mid-dig.
## Remove, damage, roll, drop
`ServerPlayerGameMode.destroyBlock` runs a short gauntlet before it writes
anything: `ItemStack.canDestroyBlock`, then `GameMasterBlock` against
`Player.canUseGameMasterBlocks`, then `Player.blockActionRestricted`. It
captures the `BlockEntity` first, because the write is about to destroy it.
Then `Block.playerWillDestroy` — particles and sound to everyone else, piglins
angered for `BlockTags.GUARDED_BY_PIGLINS`, and a `GameEvent.BLOCK_DESTROY`
posted for sculk ([game events](../world/game-events-and-vibrations.md)).
The write itself is `Level.removeBlock`, not `Level.destroyBlock`. It puts the
*fluid* that was in the block back — water for a waterlogged block, air here —
under flags 3, and everything that follows from those flags is the one
flowchart on [blocks and
states](blocks-and-states.md#the-two-update-channels).
Drops come last and in a fixed order. If `Player.preventsBlockDrops` (creative)
the method returns here. Otherwise the tool is copied,
`Player.hasCorrectToolForDrops` is asked *once* and remembered, and
`ItemStack.mineBlock` is called **unconditionally** — though what it does is
not: `Item.mineBlock` awards `Stats.ITEM_USED` and spends
`Tool.damagePerBlock` only on the server, only for a stack that carries a
`DataComponents.TOOL` at all, only when that tool's damage per block is above
zero, and only when the block's hardness is non-zero
([items and stacks](../items/items-and-stacks.md)). Only then, and only if the
write succeeded and the remembered answer was yes, does `Block.playerDestroy`
run: `Stats.BLOCK_MINED`, 0.005 of food exhaustion, and `Block.dropResources`.
The loot side is thin. `Block.getDrops` supplies `LootContextParams.ORIGIN` at
the block centre, `LootContextParams.TOOL` and `LootContextParams.THIS_ENTITY`,
`BlockBehaviour.getDrops` adds `LootContextParams.BLOCK_STATE`, and the set is
`LootContextParamSets.BLOCK`. The table key is not looked up by name at break
time: `BlockBehaviour.Properties.effectiveDrops` resolves the block's id under
*blocks/* once, when the block is constructed. What *blocks/stone* then does is
two lines of JSON — a silk-touch alternative, else cobblestone if it survives
an explosion — rolled from a seeded per-table sequence rather than the level
random ([loot tables](../items/loot-tables.md)). Each surviving stack goes to
`Block.popResource`, which respects `GameRules.BLOCK_DROPS`, jitters the
position ±0.25 on all three axes around the block centre, gives the
`ItemEntity` its small upward kick in the constructor and a ten-tick pickup
delay. Ores add their experience afterwards, in
`BlockBehaviour.BlockStateBase.spawnAfterBreak`.
## Questions players ask
**Why did the block come back, and then break anyway?** You released a tick or
two before the server's clock agreed you were done, so the STOP fell under 0.7
and became a deferral. The receipt for it carried no correction, so your client
rolled the prediction back and restored the stone; the server finished the dig
on its own a tick or two later. See *The button is not the switch*.
**Why does my pickaxe lose durability on obsidian, which drops nothing, but
not on short grass, which does?** Durability is spent by `ItemStack.mineBlock`,
which runs before the drop verdict is consulted and does not care about it —
what it cares about is that the block's hardness is non-zero and the tool has a
damage-per-block above zero. Obsidian is hard and drops nothing: you pay. Short
grass is `Blocks.SHORT_GRASS`, hardness zero: you never pay, whatever it drops.
Shears are the exception at both ends — `ShearsItem` is the only override of
`Item.mineBlock` in the game, and it tests neither hardness nor drops, only
that the block is not in `BlockTags.FIRE`. Shearing grass costs a point.
**Why can't I break blocks with a sword in creative?** Because
`Tool.canDestroyBlocksInCreative` is false on the sword's component and
`ItemStack.canDestroyBlock` checks it on both sides. It is a property of the
item, not a special case in the game mode — which is why the client refuses
first, and why the correcting block update the server sends back is a no-op:
the client predicted nothing to correct.
**Why do other players' cracks lag behind mine?** Yours are written locally
every client tick from your own accumulator. Theirs arrive as packets, sent
only when the server's tenth-of-progress changes, from the server's own clock,
and only if you are within 32 blocks.
## Where to look
`Minecraft.startAttack` · `Minecraft.continueAttack` ·
`MultiPlayerGameMode.startDestroyBlock` ·
`MultiPlayerGameMode.continueDestroyBlock` ·
`MultiPlayerGameMode.destroyBlock` ·
`ServerGamePacketListenerImpl.handlePlayerAction` ·
`ServerPlayerGameMode.handleBlockBreakAction` · `ServerPlayerGameMode.tick` ·
`ServerPlayerGameMode.incrementDestroyProgress` ·
`ServerPlayerGameMode.destroyBlock` · `BlockBehaviour.getDestroyProgress` ·
`Player.getDestroySpeed` · `Tool.getMiningSpeed` · `Tool.isCorrectForDrops` ·
`Block.playerWillDestroy` · `Block.playerDestroy` · `Block.popResource` ·
`ServerLevel.destroyBlockProgress` · `ClientLevel.destroyBlockProgress`
---
*Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes `tools/verify_names.py`.*
---
# Block entities
> Verified against **Minecraft 26.2** · Part V · A furnace smelts raw iron while nobody is watching, and the player who opens it learns the fire from a block state and the arrow from a menu — never from the block entity itself.
You drop raw iron in the top slot of a furnace, coal in the bottom, and walk
away. Two hundred ticks later there is an iron ingot in a box nobody is
looking at. A block state is one of a fixed table, shared by every position
that has it, so the moment a position needs something of its own — an
inventory, a timer, a name — it gets a `BlockEntity`: a plain object owned by
its chunk, keyed by its position, created by the block and destroyed with it.
The surprise is how little that object says for itself. A furnace tells
nobody anything: `BlockEntity.getUpdatePacket` returns null for it,
`BlockEntity.setChanged` sends nothing at all, and the two things a player
*does* see — the fire in the world and the arrow in the GUI — are a block
state and four ints from a menu, both of which arrive on the tick **after**
the smelting step that produced them, because block entities tick in the
level's last content phase, after the broadcast has already gone out.
## The cast
| class | what it decides | thread |
|---|---|---|
| `BlockEntity` | the position, the cached state, the components, and the four defaults every subclass inherits — two of which are *say nothing* | whichever thread owns the level |
| `BlockEntityType` | which blocks the entity is legal on, and what constructs it | immutable once the registry is built |
| `EntityBlock` | whether a block has an entity at all, which one, and which ticker *per level* | — |
| `LevelChunk` | the position-to-entity map, the ticker wrapper per position, and create / keep / replace / remove on every write | the chunk's owning thread |
| `Level` | the flat list of tickers and the two gates over it | server thread, or the client's main thread |
| `AbstractFurnaceBlockEntity` | three slots, four ints, a cached recipe check, and when the block's *lit* state has to change | server thread only — its ticker is null on the client |
| `ChunkHolder` | which positions changed since the last drain, and the single call to `BlockEntity.getUpdatePacket` | server thread, chunk-source phase |
| `FurnaceMenu` | what an open screen is allowed to see of all that: three slots and four ints | server thread, mirrored on the client |
## A furnace tells nobody anything
`BlockEntity` has four hooks a subclass is expected to fill in, and its own
answers to all four are deliberately weak. `BlockEntity.saveAdditional` and
`BlockEntity.loadAdditional` do nothing. `BlockEntity.getUpdateTag` returns
an empty tag. `BlockEntity.getUpdatePacket` returns **null** — the base class
declines to be synced, and a subclass that wants to be must say so.
**Nineteen** classes say so, and every one of them answers with
`ClientboundBlockEntityDataPacket.create` of itself: signs, banners, beacons,
skulls, spawners and trial spawners, conduits, end gateways, structure and
jigsaw blocks, campfires, decorated pots, vaults, shelves, brushable blocks,
creaking hearts, copper golem statues and the two test blocks. Counting those
declarations in the decompile and mapping each class onto the **49**
registrations in `BlockEntityTypes` gives **twenty** synced types out of
forty-nine, because `HangingSignBlockEntity` is a type of its own that
inherits `SignBlockEntity`'s override and adds nothing.
The overriders of the packet and the overriders of the tag are not the same
list, and the two classes that differ are instructive.
`PistonMovingBlockEntity` overrides `BlockEntity.getUpdateTag` but not the
packet, so its state travels only in a chunk send
([pistons and block events](pistons-and-block-events.md)).
`CopperGolemStatueBlockEntity` overrides the packet but not the tag, so what
it broadcasts is the base class's empty tag.
Everything else a client knows about a block entity it knows by consequence:
the block state it can see, a menu it has been given, and a block event — the
third channel, and the one that swings a chest lid without either side saying
what is inside ([pistons and block events](pistons-and-block-events.md)). The
trace below is what the first two cost.
## One save hook, four ways out
Saving is a tree, not a chain, and the branch a caller picks decides how much
metadata rides along. Only `BlockEntity.saveAdditional` belongs to the
subclass; everything else is bookkeeping the base class adds.
| what runs | what it writes | who calls it |
|---|---|---|
| `BlockEntity.saveAdditional` | the subclass's own fields, and nothing else | nobody directly |
| `BlockEntity.saveCustomOnly` | that alone | thirteen of the nineteen `BlockEntity.getUpdateTag` overrides, and the pick-block path, which then strips the keys that are now components |
| `BlockEntity.saveWithoutMetadata` | that plus *components* | the two below, plus the copy-NBT debug key, `BlockInput` and the falling block |
| `BlockEntity.saveWithId` | that plus *id* | two callers that record their own position separately: `AdventureModePredicate` and `StructureTemplate` |
| `BlockEntity.saveWithFullMetadata` | that plus *id*, *x*, *y* and *z* | `LevelChunk.getBlockEntityNbtForSaving`, the chunk-save form, and every command that reads a block's NBT |
Reading back is `BlockEntity.loadWithComponents` (fields plus components) or
`BlockEntity.loadCustomOnly` (fields only) over a `ValueInput`
([codecs, NBT and JSON](../foundations/codecs-nbt-json.md)) — but something
has to decide *which class* to construct first, and that cannot come through
a `ValueInput`, because no entity exists yet to own one. So
`BlockEntity.loadStatic` reads *id* off the raw `CompoundTag` with
`BlockEntity.TYPE_CODEC`, calls `BlockEntityType.create`, and only then wraps
the same tag in a `ValueInput` and loads it. Any of those three steps failing
logs and returns null, and the position ends up with no entity at all.
The network joins that path at the end rather than reusing it whole:
`ClientPacketListener.handleBlockEntityData` never reads *id* or constructs
anything — it finds the existing entity by position *and* type and hands the tag
to `BlockEntity.loadWithComponents`. There is no separate network
deserialiser. Where the chunk's *block_entities* list is
written and read is [chunk storage](../world/chunk-storage.md).
## Create, keep, replace, remove
A block entity that appears because a *block* appeared is created and
destroyed inside one method: `LevelChunk.setBlockState`, after the section
write, the heightmaps and the light checks ([what a write
does](blocks-and-states.md#the-two-update-channels)). That is the lifecycle
path, and it makes two decisions, in this order. It is not the only way one
comes into being: a chunk arriving from disk or from the network builds its
entities from saved tags, and `LevelChunk.getBlockEntity` in its *immediate*
mode constructs a missing one on a plain read — which is the mode every
`Level.getBlockEntity` asks for.
**Removal** happens only when the *block* changed, the old state had an
entity, and the new state does not claim it through
`BlockBehaviour.BlockStateBase.shouldChangedStateKeepBlockEntity` — which
exactly two blocks in 26.2 override, `CopperChestBlock` and
`CopperGolemStatueBlock`, both keeping the entity when the old state was
another block of the same family, so oxidising or waxing a copper chest does
not empty it. Removal is two halves with different gates. The side effects —
`BlockEntity.preRemoveSideEffects`, which for anything implementing
`Container` drops the contents through `Containers.dropContents` — run **only
on the server** and only with `Block.UPDATE_SKIP_BLOCK_ENTITY_SIDEEFFECTS`
clear. The bookkeeping, `LevelChunk.removeBlockEntity`, runs regardless —
though only the last of its four steps is itself unconditional. The map entry
going, the game-event listener being unregistered and the entity being flagged
removed all sit behind *is this chunk in a level*, which a chunk still being
generated is not; the rebind of the ticker to `LevelChunk.NULL_TICKER` happens
either way.
**Creation** happens after `BlockBehaviour.BlockStateBase.onPlace`, and only
if the state actually written still has a block entity. The chunk looks for
an existing one without creating it, and if what it finds does not pass
`BlockEntity.isValidBlockState` for the new state it logs a *mismatched block
entity* warning, removes it and builds a fresh one from
`EntityBlock.newBlockEntity` — the block's own factory, not
`BlockEntityType.create`. Only a surviving match is kept, with its cached
state refreshed and `LevelChunk.updateBlockEntityTicker` re-asking the block
for a ticker. That is why flipping a furnace's *lit* property costs almost
nothing: same block, valid state, same object — a fresh
`LevelChunk.BoundTickingBlockEntity` rebound into the wrapper is the whole of
the expense.
Chunk load and unload use the ends of the same machinery.
`ChunkStatusTasks.full` runs `LevelChunk.runPostLoad` to turn the saved tags
into entities, then `LevelChunk.setLoaded` and
`LevelChunk.registerAllBlockEntitiesAfterLevelLoad`, which attaches listeners
and tickers to entities built before the chunk belonged to a level.
`ServerLevel.unload` calls `LevelChunk.clearAllBlockEntities`: every entity
flagged removed, every ticker pointed at `LevelChunk.NULL_TICKER`.
## Two hundred ticks nobody watches
```mermaid
sequenceDiagram
participant SL as ServerLevel
participant LC as LevelChunk
participant AFBE as AbstractFurnaceBlockEntity
participant CH as ChunkHolder
participant SP as ServerPlayer
participant FM as FurnaceMenu
participant CPL as ClientPacketListener
Note over SL,CPL: tick N, blockEntities phase, the level's last content phase
SL->>LC: tickBlockEntities reaches the wrapper, isTicking and isValid pass
LC->>AFBE: serverTick, quickCheck finds the smelting recipe
AFBE->>AFBE: fuel consumed, lit fields set to 1600, cookingTimer 1
AFBE->>SL: setBlock LIT true with flags 3
SL->>LC: setBlockState, same block, entity kept and ticker rebound
SL->>CH: blockChanged only queues the holder, the drain already ran
Note over SL,CPL: tick N plus 1, chunkSource phase, the broadcast drain
CH->>CPL: ClientboundBlockUpdatePacket, the fire appears
CH-->>CH: broadcastBlockEntity asks getUpdatePacket and gets nothing
Note over SL,CPL: tick N plus 1, entities phase, players tick
SP->>FM: broadcastChanges compares four data slots against remoteDataSlots
FM->>CPL: ClientboundContainerSetDataPacket per changed slot: 0, 1 and 2 on this tick, 0 and 2 from the next
```
The furnace's ticker is handed out by `AbstractFurnaceBlock.createFurnaceTicker`
**only when the level is a `ServerLevel`** — on the client it is null, so no
furnace anywhere ever ticks there. `AbstractFurnaceBlockEntity.serverTick` is
therefore the whole of smelting: burn down
`AbstractFurnaceBlockEntity.litTimeRemaining`, ask
`AbstractFurnaceBlockEntity.quickCheck` (a `RecipeManager.CachedCheck`, which
retries last tick's match before scanning the type) for a recipe on the input
slot, check that the result slot can take the output, and, if the fire is out
but fuel is present, light it: both lit fields take `FuelValues.burnDuration`
for that item — 1600 for coal — and one fuel item is consumed. Then
`AbstractFurnaceBlockEntity.cookingTimer` advances by one. Where the recipe
comes from is [recipes](../items/recipes.md).
Two writes leave the block entity, and neither leaves the server this tick.
The first is the fire: lit-ness is a *block state*, so the ticker calls
`Level.setBlock` on its own position with `AbstractFurnaceBlock.LIT` flipped.
`ServerChunkCache.blockChanged` marks the holder dirty — and the drain that
turns dirty holders into packets, `ServerChunkCache.broadcastChangedChunks`,
lives in the chunk-source phase, which ran before entities and long before
block entities. The second is progress: `BlockEntity.setChanged` marks the
chunk unsaved and pokes comparators through
`Level.updateNeighbourForOutputSignal`, and that is all it does. It sends
nothing.
So a viewer sees both a tick late, by two different routes. Next tick's drain
sends the `ClientboundBlockUpdatePacket` and then — for every broadcast
position whose state has a block entity, including each position inside a
`ClientboundSectionBlocksUpdatePacket` — calls `BlockEntity.getUpdatePacket`,
the only call site in the game, and gets null from the furnace. Next tick's
entity phase runs `ServerPlayer.tick`, which runs
`AbstractContainerMenu.broadcastChanges`, which compares the menu's four data
slots against the values last sent and emits a
`ClientboundContainerSetDataPacket` per difference. Those four ints are the
furnace's entire GUI: the flame is data 0 over data 1 and the arrow data 2
over data 3, read by `AbstractFurnaceMenu.getLitProgress` and
`AbstractFurnaceMenu.getBurnProgress`. While smelting, only 0 and 2 change,
so it is two packets a tick per open screen and none at all with no viewer.
How a menu is opened, synchronised and closed is
[containers and menus](../items/containers-and-menus.md).
At the end, `AbstractFurnaceBlockEntity.burn` moves the ingot into the result
slot and `AbstractFurnaceBlockEntity.setRecipeUsed` adds one to a counter map
— not to a recipe object, which is why
`AbstractFurnaceBlockEntity.getRecipeUsed` returns null. The experience is
paid out on collection: `FurnaceResultSlot.checkTakeAchievements`, reached
from a take *or* a shift-click, calls
`AbstractFurnaceBlockEntity.awardUsedRecipesAndPopExperience`, which pops the
orbs at the player and unlocks the recipes.
## Loaded is not enough to tick
`Level.tickBlockEntities` walks one flat list, `Level.blockEntityTickers`,
under two gates the tickers themselves never see.
`TickRateManager.runsNormally` is the first, so `/tick freeze` stops every
block entity in the game. `Level.shouldTickBlocksAt` is the second, and it is
where the interesting asymmetry lives: on `Level` it is always true, on
`ServerLevel` it is `DistanceManager.inBlockTickingRange` — the
**simulation** chunk tracker, not the loading one. A chunk your view distance
keeps loaded and your simulation distance does not reach holds furnaces that
do not smelt, and nothing about the block entity records this: it is simply
never called. Below the gates,
`LevelChunk.BoundTickingBlockEntity` adds its own — not removed, adopted by a
level, inside the world border, and then, on a `ServerLevel` only, the chunk at
`FullChunkStatus.BLOCK_TICKING` with its entities loaded — before re-reading
the live state and ticking only while `BlockEntityType.isValid` still holds,
logging once and skipping while it does not.
The list is never searched. Removal rebinds the chunk's
`LevelChunk.RebindableTickingBlockEntityWrapper` to `LevelChunk.NULL_TICKER`,
whose `TickingBlockEntity.isRemoved` is permanently true, and the next pass of
`Level.tickBlockEntities` drops it on the way past — no search, on a list that
is walked every tick anyway. Additions made *during* the walk go to
`Level.pendingBlockEntityTickers` and are folded in at the top of the next
pass, so a block entity created by another block entity's tick starts ticking
one tick later. The client runs the same method from `Minecraft.tick`, after
its entity pass and before `ClientLevel.tick`, and only while unpaused.
## Questions players ask
**Why does my furnace stop smelting when I walk away, even though the chunk
is still loaded?** Because loading and simulating are two different
distances, and `Level.shouldTickBlocksAt` asks about the second. The chunk is
in memory, its entity is in the map and its ticker is in the list — and
`Level.tickBlockEntities` walks past it every tick without calling it.
**Why doesn't the client know what is in a chest until I open it?**
`ChestBlockEntity` overrides neither sync hook, so a chunk send carries its
type and position with no tag at all (an empty update tag is stored as null)
and the client builds its chest from the *block state* the packet's sections
already decoded, with an empty container inside. What ticks on the client is
animation only: `ChestBlock.getTicker` hands out
`ChestBlockEntity.lidAnimateTick` on the client and null on the server, the
exact mirror of the furnace.
**Why does a shulker box keep its contents when every other container drops
them?** Because dropping is the *base class's* behaviour, not the block's:
`BlockEntity.preRemoveSideEffects` drops the contents of anything that
implements `Container`. Eight classes override that hook, and
`ShulkerBoxBlockEntity` overrides it to do nothing whatever. The furnace
overrides it too, to pop the experience owed for uncollected smelts at the
block — awarding the recipes to nobody.
**Why does the arrow only move when the screen is open?** Because the arrow
is not a property of the furnace. It is data slot 2 of an
`AbstractContainerMenu` that exists only while a player has that screen open,
reconciled once per tick by the player who owns it. Close the screen and the
menu is gone, and the furnace goes on smelting with no packets at all.
## Where to look
`BlockEntity.getUpdatePacket` · `BlockEntity.setChanged` ·
`BlockEntity.loadStatic` · `BlockEntity.saveWithFullMetadata` ·
`BlockEntity.preRemoveSideEffects` · `BlockEntityType.isValid` ·
`EntityBlock.newBlockEntity` · `EntityBlock.getTicker` ·
`BaseEntityBlock.createTickerHelper` · `LevelChunk.setBlockState` ·
`LevelChunk.updateBlockEntityTicker` · `LevelChunk.BoundTickingBlockEntity` ·
`LevelChunk.NULL_TICKER` · `Level.tickBlockEntities` ·
`Level.shouldTickBlocksAt` · `ChunkHolder.broadcastBlockEntity` ·
`AbstractFurnaceBlockEntity.serverTick` · `AbstractFurnaceMenu.getBurnProgress`
How a block entity is *drawn* — and why a chest's block model is empty — is
[block-entity rendering](../rendering/block-entity-rendering.md), in Part XI.
---
*Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes `tools/verify_names.py`.*
---
# Signal and dust
> Verified against **Minecraft 26.2** · Part V · A lever on the floor is flipped, and two redstone dust to the east of it go to 15 and 14.
You flip a lever, and the dust beside it turns bright. Flip it back and the
line goes dark — in one tick, but not in one pass. Each wire recomputes its
own strength from scratch, writes it, and then **hand-issues seven
`Level.updateNeighborsAt` calls — its own position and all six neighbours —
which is forty-two neighbour updates for one wire that changed**; and because
the recursion stops on *value* rather than on distance, the wire at the far
end is reached once for every intermediate value the near end passes through
on the way down. That staircase is the whole cost of redstone, and **nobody
has ever seen it**: the cascade finishes inside one packet handler, and the
packet a client is sent is built later in the same tick from whatever the
position holds by then. The game ships a second implementation of exactly this
computation, behind a feature flag, which walks the whole connected network in
two ordered phases and does not produce the staircase at all.
**Forty-two** — neighbour updates issued by one wire whose power changed
(`DefaultRedstoneWireEvaluator.updatePowerStrength`).
## The cast
| class | what it decides | thread |
|---|---|---|
| `SignalGetter` | every question about power: what a position emits, what reaches it, and the direction order the answers are gathered in | a `Level` interface, either side |
| `BlockBehaviour.BlockStateBase` | the three answers a state gives — is it a source, what is its weak signal per face, what is its strong signal | either side |
| `LeverBlock` | the trace's source: 15 in every direction, and 15 *strongly* into one block only | server — the client's copy writes nothing |
| `RedStoneWireBlock` | which sides a wire connects to, what it emits through them, and the mutable flag that stops it counting itself | server |
| `RedstoneWireEvaluator` | *minus one per block*: what the neighbouring wires are worth to this one | server |
| `DefaultRedstoneWireEvaluator` | one wire at a time, recursively, with the fan-out issued by hand | server |
| `ExperimentalRedstoneWireEvaluator` | the whole network at once, off in two phases and on in one, allocated fresh per call | server |
| `Orientation` | in the experimental mode only, where an update *came from*, so the fan-out can be ordered relative to it | server |
## What one neighbour update to a wire costs
Redstone has no scheduler and no graph. It is what happens when blocks answer
two questions about their neighbours — *how much signal do you give me* and
*did something near you change* — through the same `BlockBehaviour.neighborChanged`
fan-out every *neighbour* update uses. This is the whole of the default
implementation, from one update arriving to the next batch leaving.
```mermaid
flowchart TB
IN["RedStoneWireBlock.neighborChanged arrives on the server"]
CLIENT{"is this a ClientLevel"}
SURV{"RedStoneWireBlock.canSurvive"}
DROP["dropResources and Level.removeBlock"]
NOTHING["nothing at all. Level.updateNeighborsAt and Level.neighborChanged are empty on Level"]
BLK["RedStoneWireBlock.getBlockSignal: set shouldSignal false, ask SignalGetter.getBestNeighborSignal, set it back"]
WIRE["RedstoneWireEvaluator.getIncomingWireSignal: the best of the four side wires, the wire above a conducting neighbour, the wire below a non-conducting one, minus one"]
TARGET["DefaultRedstoneWireEvaluator.calculateTargetStrength: block signal if it is 15, else the larger of the two"]
SAME{"is the target the POWER already stored"}
STOP["return. No write, no fan-out, and the cascade ends here"]
WRITE["Level.setBlock with flag 2 alone. Clients are told, no neighbour updates come from the write itself, shape updates still run"]
FAN["seven Level.updateNeighborsAt calls: this position and its six neighbours, collected in a hash set"]
OUT["forty-two neighbour updates, queued in CollectingNeighborUpdater.addedThisLayer and run before the caller's remaining directions"]
IN --> CLIENT
CLIENT -- "yes" --> NOTHING
CLIENT -- "no, and not a wire-sourced update under the feature flag" --> SURV
SURV -- "no" --> DROP
SURV -- "yes" --> BLK
BLK -- "block signal under 15" --> WIRE --> TARGET
BLK -- "block signal 15, so the wires are never asked" --> TARGET
TARGET --> SAME
SAME -- "yes" --> STOP
SAME -- "no" --> WRITE --> FAN --> OUT
```
The two facts that make the staircase are both in that figure. The write uses
`Block.UPDATE_CLIENTS` **alone**, so the fan-out is not the one
`Level.setBlock` would have done — it is issued afterwards, by hand, over
seven positions rather than one. And the recursion terminates on *value*, not
on distance: a wire whose recomputed strength equals what it already holds
writes nothing and tells nobody. A line going dark therefore re-enters every
wire once per step of the descent, and each visit is the last one only when
the value has stopped moving. None of those intermediate writes is ever sent.
`Level.sendBlockUpdated` only records the position in a set on the
`ChunkHolder`, and `ChunkHolder.broadcastChanges` builds the packet once per
tick by reading the level again — so a position written five times in a tick
is broadcast once, with the value it ended on.
## What a block answers when it is asked for power
Three questions, all on `BlockBehaviour.BlockStateBase`, all answered by the
block. `BlockBehaviour.BlockStateBase.isSignalSource` is whether the block
emits at all. `BlockBehaviour.BlockStateBase.getSignal` is the **weak** signal
it offers to a given face. `BlockBehaviour.BlockStateBase.getDirectSignal` is
the **strong** one, and the difference between them is entirely a matter of
who is allowed to pass it on. Conduction is
`BlockBehaviour.BlockStateBase.isRedstoneConductor`, from
`BlockBehaviour.Properties.isRedstoneConductor`, which defaults to *is this
state's collision shape a full block*.
`SignalGetter` is where the two meet, and the join is easy to get backwards.
`SignalGetter.getSignal` reads the block's own weak signal and then, **only
if that block is a redstone conductor**, takes the larger of it and
`SignalGetter.getDirectSignalTo` — the strongest signal being pushed into that
position from any of its six neighbours. It is a maximum, not a choice between
two modes: a powered conductor offers the greater of what it emits itself and
what is being forced into it. That single line is what "strongly powered"
means, and it is why a block with a lever on it powers the dust beside it. A
block a wire merely points into is strongly powered too — that is what a
piston beside a line reads — but no *other dust* can see it, for a reason that
has nothing to do with this line and everything to do with
`RedStoneWireBlock.shouldSignal`, below.
The lever shows both halves at once. `LeverBlock.ownSignal` is 15 in every
direction when powered — that is the weak signal, and it is what the dust next
to the lever reads. `LeverBlock.getDirectSignal` is 15 only into the one block
the lever is attached to. So the block behind a lever becomes a source in its
own right, and everything touching *that* block sees 15 too.
### Three direction orders, and only one of them is about reading
Three fixed direction orders run through this page and they are not
interchangeable. Two decide who gets **told** something; the third decides
what a block **reads**, and it is the one this page uses.
| array | order | what it governs |
|---|---|---|
| `SignalGetter.DIRECTIONS` | down, up, north, south, west, east | what a block reads. Only `SignalGetter.getBestNeighborSignal` walks the array; `SignalGetter.getDirectSignalTo` and `SignalGetter.hasNeighborSignal` are written out by hand in the same order |
| `NeighborUpdater.UPDATE_ORDER` | west, east, down, up, north, south | which neighbour is told first about a change, on the neighbour channel |
| `BlockBehaviour.UPDATE_SHAPE_ORDER` | west, east, north, south, down, up | which neighbour is asked first to re-fit, on the shape channel |
All three stop early, and not on the same thing: the two that return a number
stop at a 15, while `SignalGetter.hasNeighborSignal` — which returns a boolean
— stops at the first answer above zero. That is not a micro-optimisation with
no consequences: a position saturated from one side never reads the others at
all. `SignalGetter.DIRECTIONS` is plain `Direction` order, which is the only
reason its first entry is *down*.
## Dust, and how far it reaches
`RedStoneWireBlock.POWER` is the number, 0 to 15. Four `RedstoneSide`
properties — one per horizontal — record how the wire is drawn and, more
importantly, which sides it will actually talk through. What a wire is worth
to its neighbours is `RedstoneWireEvaluator.getIncomingWireSignal`, and the
*minus one per block* everyone knows lives in its last line: it takes the best
of the wires it can see and subtracts one, floored at zero. The wires it can
see are the four beside it, plus the wire on top of a conducting neighbour
when nothing conducts above this position, plus the wire below a
non-conducting neighbour — which is the *power* half of "dust climbs a block
and falls down one". The drawing half is `RedStoneWireBlock.getConnectingSide`
below, which asks `BlockBehaviour.BlockStateBase.isFaceSturdy` where this one asks
about conduction.
Two asymmetries follow from `RedStoneWireBlock.getSignal` and are worth
stating plainly, because they are the two questions every redstone build
eventually asks. A wire returns **zero** when the direction asked about is
`Direction.DOWN` — so dust never powers the block above it. It returns its
full power without any connection test when the direction is `Direction.UP` —
so dust always powers the block below it. In every other direction it answers
only if its connection on the opposite side is made.
Connection itself is two rules and a completion pass.
`RedStoneWireBlock.shouldConnectTo` is the real one: another wire always, a
`Blocks.REPEATER` along its own axis, a `Blocks.OBSERVER` only from its facing
side, and otherwise any block that says it is a signal source — that last
clause only when a direction is supplied, which the vertical rules do not do,
so up and down connect to wire and to nothing else.
`RedStoneWireBlock.getConnectingSide` adds the vertical cases — up over a
face-sturdy neighbour, down past a non-conducting one. And then
`RedStoneWireBlock.getConnectionState` runs a completion pass that produces
most of the confusion: **if a wire has no north or south connection, west and
east are set anyway**, and the same the other way round. That is why a lone
dust is drawn as a cross, and why a wire fed from the west appears to point
firmly into whatever is on its east — a piston, say — which satisfies neither
real rule. The piston is not a source and, by `Blocks.pistonProperties`, not a
conductor. It gets powered anyway, because the wire's east side is *SIDE* by
completion and `RedStoneWireBlock.getSignal` asks about the side, not about
the neighbour.
> **For a 1.21-era reader.** `BlockBehaviour.neighborChanged` now takes a
> nullable `Orientation` rather than a source `BlockPos`, and
> `BlockBehaviour.affectNeighborsAfterRemoval` — which replaced the old
> removal hook — does not take one at all.
`RedStoneWireBlock.shouldSignal` is the oddest thing on the page: a mutable
boolean on the block singleton, flipped false for the duration of
`RedStoneWireBlock.getBlockSignal` so that a wire does not count itself or its
neighbouring wires as sources while it works out its *block* power. It works
because the server thread is the only writer and never re-enters the method.
## The lever, two dust, and a powered piston
```mermaid
sequenceDiagram
participant LevB as LeverBlock
participant SL as ServerLevel
participant CNU as CollectingNeighborUpdater
participant RSWB as RedStoneWireBlock
participant DRWE as DefaultRedstoneWireEvaluator
participant PBB as PistonBaseBlock
Note over LevB,PBB: all of this is one call stack, inside one packet handler, before the level ticks
LevB->>SL: setBlock POWERED with flags 3
SL->>CNU: the write's own fan-out at the lever, drained on the spot
CNU->>RSWB: neighborChanged at the first dust
RSWB->>DRWE: getBlockSignal sees the lever at 15, so the target is 15
DRWE->>SL: setBlock POWER 15 with flag 2, then seven updateNeighborsAt
CNU->>RSWB: neighborChanged at the second dust, depth-first, ahead of the lever's other directions
RSWB->>DRWE: block signal 0, incoming wire signal 15 minus 1
DRWE->>SL: setBlock POWER 14 with flag 2, then seven more
CNU->>PBB: neighborChanged, and the piston asks only whether the wire's east side is above zero
Note over CNU,PBB: the remaining dozens of updates run against blocks that do not care, and the count resets
LevB->>SL: updateNeighbours by hand, two more fan-outs: the lever and the block it stands on
```
The lever is the place in this trace where the client does not even try.
`LeverBlock.useWithoutItem` writes no state on a `ClientLevel` — it spawns a
particle, and only when the lever is going *on* — so unlike a door, a lever is
not predicted, and the client's dust changes colour only when the block
updates arrive. The dust and the piston do nothing on the client either, but
for the ordinary reason: nothing ever calls their
`BlockBehaviour.neighborChanged` there. The sound
follows the same split for a different reason: `LeverBlock.pull` is handed a
null player, so nobody is excluded and the clicker hears the server's
`ClientboundSoundPacket` like everyone else. Compare
[block interaction](block-interaction.md), where the door passes the clicker
as *except* and they hear their own prediction instead.
Two details in the diagram are worth naming. The lever's `Level.setBlock`
uses flags 3, so `Block.UPDATE_NEIGHBORS` fans out once *before*
`LeverBlock.updateNeighbours` fans out twice more, at the lever's own position
and at the block it stands on — and because nothing was running when the first
one was queued, it drains the entire dust cascade before the other two are
even issued. And the ordering of the seven positions a wire updates is
fixed by no array: they come out of a hash set. The depth-first drain of
`CollectingNeighborUpdater` is [block interaction](block-interaction.md)'s
subject, and it is what puts the second dust's whole cascade ahead of the
lever's remaining directions.
Placing and breaking a wire take a wider path again:
`RedStoneWireBlock.onPlace` and
`RedStoneWireBlock.affectNeighborsAfterRemoval` both call
`RedStoneWireBlock.updateNeighborsOfNeighboringWires`, which walks the four
horizontals and then the diagonals — reaching over a conducting neighbour and
under a non-conducting one — and calls
`RedStoneWireBlock.checkCornerChangeAt` on each. That is another seven
`Level.updateNeighborsAt` per wire found. Which write does what is
[blocks and states](blocks-and-states.md#the-two-update-channels).
## The second implementation
`FeatureFlags.REDSTONE_EXPERIMENTS` is a feature flag, turned on by a built-in
data pack whose entire content is the line that enables it, and
`RedStoneWireBlock.useExperimentalEvaluator` asks the level for it on **every
call** — `RedStoneWireBlock.evaluator` is always the default one, and an
`ExperimentalRedstoneWireEvaluator` is a fresh object per update, because it
carries working state. What changes is not speed but semantics.
`ExperimentalRedstoneWireEvaluator.calculateCurrentChanges` computes the whole
connected network before writing anything. Phase one drains
`ExperimentalRedstoneWireEvaluator.wiresToTurnOff`: a wire whose recomputed
power is lower than its stored value goes to **zero** in the working map
rather than to its new value, and is re-queued into the turn-on deque if it
has block power of its own. Phase two drains
`ExperimentalRedstoneWireEvaluator.wiresToTurnOn`, raising each to its true
value. Both phases spread through
`ExperimentalRedstoneWireEvaluator.propagateChangeToNeighbors` and
`ExperimentalRedstoneWireEvaluator.enqueueNeighborWire`, and every wire they
reach is recorded in `ExperimentalRedstoneWireEvaluator.updatedWires`, an
insertion-ordered map of position to a packed orientation and power. Only then
are the states written — the write pass drops any entry whose stored power
already matches, so what survives it is the wires that really changed — with
`Block.UPDATE_CLIENTS` and, for every wire but sometimes the first,
`Block.UPDATE_SKIP_SHAPE_UPDATE_ON_WIRE`, which
`NeighborUpdater.executeShapeUpdate` honours by skipping any shape update
whose **target** is dust, whatever the source.
The fan-out is the other half of the difference.
`ExperimentalRedstoneWireEvaluator.causeNeighborUpdates` issues one
`Level.neighborChanged` per *connected* side per changed wire — connected
meaning the four horizontals the wire's own state records, plus `Direction.DOWN`
unconditionally and `Direction.UP` never — in
`Orientation.getDirections` order — an order derived from where the update
came from rather than from a fixed array — and, where that side is a redstone
conductor, five more at that conductor's own sides. That is how the
experimental evaluator carries strong power without the seven-position
scattergun. And `RedStoneWireBlock.neighborChanged` ignores wire-sourced
updates entirely in this mode, which is what closes the recursion; the default
evaluator does not, which is what opens it.
## Questions players ask
**Does a long line of dust really count down through every value when it turns
off?** Inside the tick, yes: each wire recomputes independently and tells its
neighbours only when its own number moved, so the far end is reached once for
each value the near end passes through on the way down. On screen, no.
`ChunkHolder.broadcastChanges` builds one packet per changed position per tick
by reading the level, so a position written five times sends the last value
once. The staircase costs neighbour updates, not frames — and the experimental
evaluator exists to make it one ordered pass instead.
**Why does dust point into a block that cannot be powered?** Because the
drawing rule and the powering rule are different rules.
`RedStoneWireBlock.getConnectionState` fills in the missing half of a line
whenever the perpendicular axis is empty, and
`RedStoneWireBlock.getSignal` then answers on the strength of that filled-in
side. Whether the neighbour does anything with the signal is the neighbour's
business.
**Why does dust power the block underneath it but not the one above?**
`RedStoneWireBlock.getSignal` returns zero for `Direction.DOWN` and returns
full power for `Direction.UP` without checking any connection. Those are two
lines in one method, and every "torch under the dust" contraption rests on
them.
**Does a redstone torch really burn out after a fixed number of flickers?**
Yes, and every number in the mechanism is a literal.
`RedstoneTorchBlock.RECENT_TOGGLES` is a weak map from level to a list of
toggles; `RedstoneTorchBlock.tick` prunes anything older than 60 ticks off the
front of it, and `RedstoneTorchBlock.isToggledTooFrequently` burns the torch
out on the **eighth** surviving entry for that position.
`RedstoneTorchBlock.MAX_RECENT_TOGGLES`, `RedstoneTorchBlock.RECENT_TOGGLE_TIMER`
and `RedstoneTorchBlock.RESTART_DELAY` hold 8, 60 and 160, and nothing in the
corpus reads any of the three.
## Where to look
`SignalGetter.getSignal` · `SignalGetter.getDirectSignalTo` ·
`SignalGetter.getBestNeighborSignal` · `SignalGetter.getControlInputSignal` ·
`SignalGetter.DIRECTIONS` · `BlockBehaviour.BlockStateBase.isRedstoneConductor` ·
`LeverBlock.pull` · `LeverBlock.updateNeighbours` ·
`RedStoneWireBlock.neighborChanged` · `RedStoneWireBlock.getBlockSignal` ·
`RedStoneWireBlock.getSignal` · `RedStoneWireBlock.getConnectionState` ·
`RedStoneWireBlock.getConnectingSide` · `RedStoneWireBlock.shouldConnectTo` ·
`RedStoneWireBlock.updateNeighborsOfNeighboringWires` ·
`RedstoneWireEvaluator.getIncomingWireSignal` ·
`DefaultRedstoneWireEvaluator.updatePowerStrength` ·
`ExperimentalRedstoneWireEvaluator.calculateCurrentChanges` ·
`ExperimentalRedstoneWireEvaluator.causeNeighborUpdates` ·
`NeighborUpdater.executeShapeUpdate` · `RedstoneTorchBlock.isToggledTooFrequently`
---
*Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes `tools/verify_names.py`.*
---
# Pistons and block events
> Verified against **Minecraft 26.2** · Part V · A powered piston pushes one stone block, and the client is never told where the moving blocks are.
A piston cannot act when it is asked. A repeater told about a change books a
turn in the appointment book and a wire recomputes itself on the spot;
`PistonBaseBlock.checkIfExtend` does neither. It appends a four-value record
to a set on the level and returns, and the push happens later, in a phase of
the level tick named for exactly this. What comes out the other side is
stranger still: **no block update is ever sent for the moving blocks.** The
placeholders the server writes carry `Block.UPDATE_CLIENTS` deliberately
clear, so nothing incremental is generated for them, and the copy on your
screen exists only because your client re-ran `PistonBaseBlock.moveBlocks`
itself against its own world, off a single `ClientboundBlockEventPacket`. It
is not a prediction that gets confirmed. Nothing checks that the two
animations agree; if that one packet is lost you watch nothing move, and only
the landing write puts the block where everyone else already sees it.
## The cast
| class | what it decides | thread |
|---|---|---|
| `BlockEventData` | the record itself: a position, a block, and two ints | a record, no thread |
| `ServerLevel` | that a block event is *queued* rather than run, and the one phase per tick in which the queue drains | Server |
| `Level` | that on any other level — which in practice means `ClientLevel` — a block event runs **immediately** | Render |
| `PistonBaseBlock` | whether to extend, which of three events to raise, and — at the drain, having re-checked — what actually moves | Server, then the client's copy |
| `PistonStructureResolver` | the set of blocks that move and the set that is destroyed, or a flat refusal | either side, allocated per attempt |
| `MovingPistonBlock` | the placeholder state that occupies a position during the motion | either side |
| `PistonMovingBlockEntity` | the two ticks of motion, the entities shoved along, and what is left behind at the end | both sides, in the block-entity phase |
| `PistonHeadBlock` | the arm once the motion is over, and forwarding neighbour updates back to the base | Server |
## The queue, and which tick it drains in
`Level.blockEvent` runs `BlockBehaviour.BlockStateBase.triggerEvent` on the
spot. `ServerLevel.blockEvent` overrides it to add a `BlockEventData` to
`ServerLevel.blockEvents` and return. That single override is the whole
mechanism, and it is the reason the two sides of a piston behave so
differently: the server always defers, the client never does.
The drain is `ServerLevel.runBlockEvents`, in the *blockEvents* section of
`ServerLevel.tick`, after *tickPending* and *chunkSource* and before
*entities* ([the level tick](../server/server-level-tick.md)). It is worth
being exact about what that timing means, because "a block event is a tick
late" is only sometimes true:
- **Queued by a packet handler — the same tick.**
`MinecraftServer.processPacketsAndTick` drains the queued packets and *then*
calls `MinecraftServer.tickServer` in the same lap ([the server
tick](../server/server-tick.md)), so a lever a player flipped is handled
before the level ticks at all, and the event it raised is drained in that
same level tick.
- **Queued by a scheduled tick — the same tick.** *tickPending* runs before
*blockEvents*, so a repeater firing into a piston is also drained
immediately ([scheduled ticks](../world/scheduled-ticks.md)).
- **Queued by another block event — the same tick.**
`ServerLevel.runBlockEvents` drains until the set is empty, so an event
raised while the drain is running is taken by the same drain.
- **Queued by an entity or a block entity — the next tick.** Those phases run
after *blockEvents*, so anything they raise waits a full lap. A landing
`PistonMovingBlockEntity` is one step short of this group: it raises no event
itself, but its `Level.neighborChanged` can reach a neighbouring piston,
whose `PistonBaseBlock.checkIfExtend` then queues one for next tick.
- **In a chunk that is not block-ticking — parked.** Such an event goes to
`ServerLevel.blockEventsToReschedule` and is re-added *after* the loop, so
it is retried next tick rather than dropped.
`ServerLevel.blockEvents` is a linked hash **set**, so two identical events
raised in one tick collapse into one. And `ServerLevel.doBlockEvent` re-reads
the position and runs the event only if the block there is still the block the
event named — the same promise a scheduled tick makes. When
`BlockBehaviour.BlockStateBase.triggerEvent` returns true, and only then, a
`ClientboundBlockEventPacket` goes to every player within 64 blocks.
The piston is the mechanism's most demanding customer but not its only one.
Three blocks raise events directly — `PistonBaseBlock`, `NoteBlock` and
`PotentSulfurBlock` — and seven block entities raise their own, reaching
themselves back through `BaseEntityBlock.triggerEvent`, which is how a chest
lid, an ender chest, a shulker box, a bell, a decorated pot, a spawner and an
end gateway all get animated on clients that own no copy of their state.
`ComparatorBlock` is the odd one out and worth a moment: it overrides
`BlockBehaviour.BlockStateBase.triggerEvent` to forward to its block entity,
but `ComparatorBlockEntity` overrides nothing and nothing anywhere raises a
comparator event, so the override is dead in both directions.
## One push, tick by tick
```mermaid
sequenceDiagram
participant SL as ServerLevel
participant PBB as PistonBaseBlock
participant PSR as PistonStructureResolver
participant PMBE as PistonMovingBlockEntity
participant CPL as ClientPacketListener
participant CL as ClientLevel
Note over SL,CL: tick N, a packet handler, before the level ticks
SL->>PBB: neighborChanged, so checkIfExtend
PBB->>PBB: getNeighborSignal finds the wire, and EXTENDED is false
PBB->>PSR: resolve, as a dry run. Stone is pushable, air beyond it
PBB->>SL: blockEvent TRIGGER EXTEND with the facing packed in. Nothing moves
Note over SL,CL: tick N, blockEvents phase, still the same tick
SL->>PBB: doBlockEvent re-reads the block, then triggerEvent
PBB->>PBB: getNeighborSignal again. A pulse shorter than the gap dies here
PBB->>PSR: resolve a second time, for real
PBB->>SL: moveBlocks writes MOVING PISTON placeholders at flags 324, one for the stone and one for the arm
PBB->>SL: then triggerEvent itself writes the extended base at flags 67
SL-->>CPL: ClientboundBlockEventPacket within 64 blocks, and a sound packet
CPL->>CL: Level.blockEvent runs immediately on the client
CL->>PBB: the same triggerEvent, the same moveBlocks, against the client's world
Note over SL,CL: tick N, blockEntities phase, and tick N plus 1, both sides
PMBE->>PMBE: progress 0 to 0.5 to 1, moveCollidedEntities under NOCLIP
Note over SL,CL: tick N plus 2, blockEntities phase
PMBE->>SL: the placeholder becomes the real stone at flags 67, and the arm a PISTON HEAD
```
## How a piston decides, and the line that cannot fire
`PistonBaseBlock.getNeighborSignal` is the whole of quasi-connectivity, and it
is one short method. It asks `SignalGetter.hasSignal` at all six neighbours
except the one it faces; then, if none of those answered, it repeats the same
question for five of the neighbours of the position **directly above** the
piston, skipping `Direction.DOWN` because that would only read the piston
again. The piston is not the only block that reaches up like this —
`DispenserBlock.neighborChanged` asks `SignalGetter.hasNeighborSignal` at its
own position *or* at the one above it, and `DropperBlock` inherits that, and
`DoorBlock.getStateForPlacement` does the same — but each of the three writes
the reach out by hand, and no other block in the game has it. That is why
quasi-connectivity is a short list of block-by-block quirks rather than a
redstone rule. What signal means, and how the wire beside the piston comes to
be connected to it at all, is [signal and dust](signal-and-dust.md).
Between the two loops sits a third question — `SignalGetter.hasSignal` at the
piston's *own* position, looking down — and it can never return true.
`SignalGetter.getSignal` consults the strong power pushed into a position only
when the block there is a redstone conductor, and `Blocks.pistonProperties`
declares a piston never to be one; `PistonBaseBlock` overrides no signal
method of its own, so its weak answer is zero. Strong power reaches a piston
the ordinary way, through its conducting neighbours, in the first loop. The
middle call is dead.
`PistonBaseBlock.checkIfExtend` turns the answer into one of three events.
Powered and not extended raises `PistonBaseBlock.TRIGGER_EXTEND` — but only if
a dry-run `PistonStructureResolver.resolve` succeeds first, so a piston with
an immovable wall in front of it queues nothing at all. Unpowered and extended
raises `PistonBaseBlock.TRIGGER_CONTRACT`, or
`PistonBaseBlock.TRIGGER_DROP` when the extension it would retract is still in
flight: the block two ahead is still a `Blocks.MOVING_PISTON` facing the same
way and extending, and either its progress is under half, or it was ticked this very
game tick, or `ServerLevel.isHandlingTick` says the level is still inside the
window that closes when the block-event drain ends.
## What moves, and what is simply gone
`PistonStructureResolver` runs twice per push — once as
`PistonBaseBlock.checkIfExtend`'s dry
run, once for real inside `PistonBaseBlock.moveBlocks` — and produces two
lists, `PistonStructureResolver.toPush` and `PistonStructureResolver.toDestroy`.
`PistonStructureResolver.addBlockLine` walks forward from the piston until it
runs out of blocks — and backwards along the same axis while the block behind
is sticky — refusing the whole push, not merely stopping, when it meets
something unpushable or runs past twelve. Twelve is a literal at each of the
three tests; `PistonStructureResolver.MAX_PUSH_DEPTH` holds it and is read
nowhere.
`PistonStructureResolver.addBranchingBlocks` follows slime and honey sideways,
with `PistonStructureResolver.canStickToEachOther` refusing the one pairing
everybody tests first — slime against honey does not stick.
`PistonBaseBlock.isPushable` is the per-block veto and it is a longer list
than folklore suggests: outside the build height or the world border, obsidian
and its three relatives, a block whose destroy speed is −1, a `PushReaction`
of `PushReaction.BLOCK`, a `PushReaction.DESTROY` where the caller did not
allow destruction, a `PushReaction.PUSH_ONLY` being moved the wrong way, an
already-extended piston, and — the clause that explains the most —
**anything with a block entity**. A push straight down at the bottom of the
world or straight up at the top is refused too, and a piston itself skips the
destroy-speed and push-reaction tests entirely. A chest cannot be pushed
because it has a block entity; that `PistonMovingBlockEntity` has nowhere to
keep one is the reading of the code that makes sense of the clause, not
something the code says.
## The write nobody is told about
A push writes five kinds of position, and their flag words are the page's hook
made concrete. Four are `PistonBaseBlock.moveBlocks`'s; the fifth is written by
`PistonBaseBlock.triggerEvent` after the other four. Which bit does
what is [blocks and states](blocks-and-states.md#the-two-update-channels).
| what | written by | flags | the bits that matter |
|---|---|---:|---|
| each moving block's destination, and the arm | `PistonBaseBlock.moveBlocks` | 324 | `Block.UPDATE_SKIP_BLOCK_ENTITY_SIDEEFFECTS`, `Block.UPDATE_MOVE_BY_PISTON`, `Block.UPDATE_INVISIBLE` — and **no** `Block.UPDATE_CLIENTS` |
| an old arm cleared on a retraction | `PistonBaseBlock.moveBlocks` | 276 | the same three bits again, with `Block.UPDATE_KNOWN_SHAPE` in place of the piston bit — also **not** sent |
| a vacated position, set to air | `PistonBaseBlock.moveBlocks` | 82 | `Block.UPDATE_MOVE_BY_PISTON`, `Block.UPDATE_KNOWN_SHAPE`, `Block.UPDATE_CLIENTS` — this one *is* sent |
| a destroyed block, set to air | `PistonBaseBlock.moveBlocks` | 18 | `Block.UPDATE_KNOWN_SHAPE`, `Block.UPDATE_CLIENTS` |
| the piston base, now extended | `PistonBaseBlock.triggerEvent` | 67 | `Block.UPDATE_MOVE_BY_PISTON`, `Block.UPDATE_CLIENTS`, `Block.UPDATE_NEIGHBORS` |
The vacated row is rarer than it looks. `PistonBaseBlock.moveBlocks` starts
with every pushed position marked for deletion and then unmarks each
destination and, on an extension, the arm — so for a straight push down a line
every origin is somebody's destination and the set empties. In this page's
trace nothing is written at 82 at all: the client is told that the base is
extended, and **nothing** about the two positions now holding placeholders.
Each placeholder's `PistonMovingBlockEntity` is injected by hand
with `Level.setBlockEntity` — `MovingPistonBlock.newBlockEntity` returns null,
because a moving piston is never created by the ordinary block-entity path —
and carries the real block as `PistonMovingBlockEntity.movedState`.
`ClientPacketListener.handleBlockEvent` hands the packet to the base
`Level.blockEvent`, which runs `PistonBaseBlock.triggerEvent` immediately
against the `ClientLevel`: the same resolver, the same placeholders, the same
injected block entities. What the client's copy does not do is the
server's-side-only work — no drops for a crushed block, no game event, no
`BlockBehaviour.BlockStateBase.affectNeighborsAfterRemoval` — and, most
audibly, no sound. `PistonBaseBlock.triggerEvent` passes a null *except*
entity, and `ClientLevel.playSeededSound` plays a sound only when *except* is
the local player — so the piston you hear is the server's
`ClientboundSoundPacket`, arriving beside the event. The particles of a crushed
block are the mirror image: the level event that spawns them is raised inside
`PistonBaseBlock.moveBlocks` on the **client** side only, and only for a block
outside `BlockTags.FIRE`.
## Two ticks of motion, and two ways to end
`PistonMovingBlockEntity.tick` runs in the block-entity phase on both sides
and does one thing per tick: add 0.5 to `PistonMovingBlockEntity.progress`,
after shoving whatever is in the swept slab with
`PistonMovingBlockEntity.moveCollidedEntities` and dragging honey-stuck
entities with `PistonMovingBlockEntity.moveStuckEntities`. The
`PistonMovingBlockEntity.NOCLIP` thread-local is set around each entity's own
move — and holds the push `Direction` rather than a flag — so that a pushed
entity may pass through the very block pushing it.
`PistonMovingBlockEntity.TICKS_TO_EXTEND` is declared as 2, and the 0.5 is
written as a literal — no reader of the constant survives the decompile.
The tick *after* progress reaches 1 is the landing. The entity is removed, and
`Block.updateFromNeighbourShapes` re-fits the moved state to its new
surroundings before it is written at flags 67, with a waterlogged property
cleared if it survived the trip. The client holds five extra
`PistonMovingBlockEntity.deathTicks` before doing the same, which is why the
visual arrival lags the server's slightly — and why it does not matter, since
the server's own write is broadcast anyway.
`PistonMovingBlockEntity.finalTick` is a **different** operation, not an
early-exit form of that one, and the difference is what makes an interrupted
extension clean up after itself. It writes at flags 3 rather than 67, and for
the entity carrying the arm — the one with
`PistonMovingBlockEntity.isSourcePiston` — it writes **air** instead of the
moved state, so a retraction that catches its own extension in flight leaves
nothing behind. That flag is set both on the arm's placeholder and on the one a
contracting piston writes at its own position. It is reached from
`PistonMovingBlockEntity.preRemoveSideEffects`, and directly from
`PistonBaseBlock.triggerEvent`'s contract branch.
## Questions players ask
**Is a piston really a tick late?** The queue is not a fixed delay — it is a
wait for one named phase. A lever flipped by a player and a repeater firing
both land in the same tick the piston was told about, because packets and
scheduled ticks are both handled before the *blockEvents* phase. What you see
as the piston's delay is two ticks of motion and a third for the landing, and
the first of the three is the very tick the piston was told about: the
placeholders' tickers go straight into the level's list, which the
*blockEntities* phase then walks later in the same tick.
**Why did my one-tick pulse move nothing at all?** Because
`PistonBaseBlock.triggerEvent` asks `PistonBaseBlock.getNeighborSignal` again
at the drain, and a piston no longer powered simply returns false. The event
is consumed, no blocks move, and no `ClientboundBlockEventPacket` is sent, so
nobody sees anything happen either.
**Why does a piston push a block that is powering it?** Because the two
questions are asked of different positions.
`PistonBaseBlock.getNeighborSignal` deliberately skips the direction the
piston faces when looking for power, so the block in front never counts as a
source — and then reaches up a block, which is the only place in the game
anything does.
**Can a piston push a chest?** No, and the reason is one clause in
`PistonBaseBlock.isPushable`: anything with a block entity is refused.
`PistonMovingBlockEntity` has a field for the moved *state* and none for a
moved block entity, so there is nowhere to put a chest's contents for the two
ticks of the journey.
**Why did the blocks stay put on my screen when everyone else saw them
move?** Because the placeholders are not synchronised. Your client built its
copy by re-running the push from one `ClientboundBlockEventPacket`, and that
packet is sent once, to players within 64 blocks, only if the server's own
`BlockBehaviour.BlockStateBase.triggerEvent` returned true. Nothing checks
afterwards that the two animations agree; what does converge is the
destination, because the landing write carries `Block.UPDATE_CLIENTS` and is
broadcast like any other.
## Where to look
`Level.blockEvent` · `ServerLevel.blockEvent` · `ServerLevel.runBlockEvents` ·
`ServerLevel.doBlockEvent` · `BlockEventData` ·
`BlockBehaviour.BlockStateBase.triggerEvent` · `BaseEntityBlock.triggerEvent` ·
`PistonBaseBlock.checkIfExtend` · `PistonBaseBlock.getNeighborSignal` ·
`PistonBaseBlock.triggerEvent` · `PistonBaseBlock.moveBlocks` ·
`PistonBaseBlock.isPushable` · `PistonStructureResolver.resolve` ·
`PistonStructureResolver.addBlockLine` ·
`PistonStructureResolver.addBranchingBlocks` ·
`MovingPistonBlock.newMovingBlockEntity` · `PistonMovingBlockEntity.tick` ·
`PistonMovingBlockEntity.finalTick` ·
`PistonMovingBlockEntity.moveCollidedEntities` ·
`ClientPacketListener.handleBlockEvent`
---
*Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes `tools/verify_names.py`.*
---
# Diodes and the observer
> Verified against **Minecraft 26.2** · Part V · A repeater, a comparator and an observer in one circuit — three blocks that learn about the world three different ways, and one of them is not listening to redstone at all.
Put a repeater, a comparator and an observer side by side and they look like
variations on one idea: flat-looking blocks that take a signal in one side and
push one out the other. Two of them genuinely are — `RepeaterBlock` and
`ComparatorBlock` are both `DiodeBlock`s and share all of their output
machinery but the number they emit. The observer is not a diode at all — it is
a `DirectionalBlock`, a full cube, with a six-way facing — and the way it finds
out that
something changed is the page's hook: **`ObserverBlock` fires from
`ObserverBlock.updateShape` — a *shape* update — so the one block whose entire
job is noticing change is not on the channel that carries change
notifications.** That is not a curiosity. It is why an observer sees a door
opened by hand, an event that writes with `Block.UPDATE_NEIGHBORS` clear and
so fires no neighbour update of its own. And the repeater quietly uses the
same trick for its lock.
## The cast
| class | what it decides | thread |
|---|---|---|
| `DiodeBlock` | everything the repeater and the comparator share: what counts as input, what counts as a side input, and how output leaves | Server |
| `RepeaterBlock` | a delay in two-tick units, and whether it is locked | Server |
| `ComparatorBlock` | one arithmetic operation, and how far in front of itself it can see | Server |
| `ComparatorBlockEntity` | one integer — the comparator's whole reason for having a [block entity](block-entities.md) at all | Server |
| `ObserverBlock` | that a neighbour's *state* changed, on a channel the other two do not use for input | Server |
| `Level` | that any write of a state with an analog output pokes the comparators around it | Server |
## Three blocks, five rows
Five rows are the whole of what a redstone circuit sees them do differently.
| | `RepeaterBlock` | `ComparatorBlock` | `ObserverBlock` |
|---|---|---|---|
| **what it reads from the front** | `DiodeBlock.getInputSignal` — the signal at the block it faces, and if that is under 15, the raw `RedStoneWireBlock.POWER` of a wire there | the same, then overridden: an analog output if the block in front has one, else one block further through a conductor | nothing. It reads no signal at all |
| **what it reads from the sides** | `DiodeBlock.getAlternateSignal`, restricted to other diodes (`DiodeBlock.sideInputDiodesOnly` is true), and used only to lock | the same, unrestricted, and used as the second operand | nothing |
| **how it books its turn** | `DiodeBlock.checkTickOnNeighbor` unchanged: `RepeaterBlock.DELAY` doubled, at one of three priorities | overrides it entirely: always a delay of 2, at `TickPriority.HIGH` or `TickPriority.NORMAL` | `ObserverBlock.startSignal` from a shape update: delay 2, no priority, and only if one is not already booked |
| **what it stores** | everything, in the block state | the same, plus one int in a `ComparatorBlockEntity` | everything, in the block state |
| **how it outputs** | `DiodeBlock.updateNeighborsInFront` | the same | `ObserverBlock.updateNeighborsInFront`, an independent copy making the same two calls |
## A diode never writes into its target
The output half is the least-known part of all three blocks, and it is shared.
A diode declares itself a source unconditionally
(`DiodeBlock.isSignalSource`), answers `DiodeBlock.ownSignal` with
`DiodeBlock.getOutputSignal` when `DiodeBlock.POWERED` and zero otherwise, and
restricts `DiodeBlock.getSignal` to the one direction it faces — so a repeater
offers its 15 to precisely one neighbour, and `DiodeBlock.getDirectSignal`
hands out the same value, which is what makes a diode able to strongly power
the block in front of it.
A diode's `HorizontalDirectionalBlock.FACING` points at the **input**.
`DiodeBlock.getStateForPlacement` takes the player's horizontal direction and
reverses it, so the output is at
`Direction.getOpposite` of its *facing*, and that is the position
`DiodeBlock.updateNeighborsInFront` acts on. It does two things there: a
direct `Level.neighborChanged` on the output block, and a
`Level.updateNeighborsAtExceptFromFacing` around that same block, skipping the
direction that points back at the diode. **It never writes a state into the
target.** It notifies, and lets the target read back through
`SignalGetter.getSignal` — which is why a repeater feeding a repeater works
without either of them knowing what the other is.
The signal leaves by an unexpected door. `DiodeBlock.tick` writes
`DiodeBlock.POWERED` with `Block.UPDATE_CLIENTS` alone — flags 2, no
neighbour bit — so `Level.setBlock`'s neighbour fan-out never runs, though its
three shape passes still do. What actually
propagates the change is `DiodeBlock.onPlace`, which
`LevelChunk.setBlockState` runs on the server for any write without
`Block.UPDATE_SKIP_ON_PLACE`, and which calls
`DiodeBlock.updateNeighborsInFront`
([blocks and states](blocks-and-states.md#the-two-update-channels)).
## What each one can see
`DiodeBlock.getInputSignal` reads the block it faces and then, if that gave
less than 15, takes the maximum with the raw `RedStoneWireBlock.POWER` of a
wire sitting there — the special case that lets a diode read a wire whose
connection state does not point at it.
`DiodeBlock.getAlternateSignal` reads the two horizontals perpendicular to the
facing through `SignalGetter.getControlInputSignal`, and
`DiodeBlock.sideInputDiodesOnly` decides what counts. For a repeater it is
true, so only another diode can reach a repeater's side, and the value is used
solely by `RepeaterBlock.isLocked`. For a comparator it is false, so a
redstone block reads as 15, a wire reads as its power, and any signal source
reads as its strong output — and the value is the comparator's second operand.
`ComparatorBlock.getInputSignal` is where comparators earn their reputation.
If the block in front has an analog output, that value replaces the redstone
reading outright. Otherwise, if the reading is under 15 and the block in front
is a redstone conductor, the comparator looks **one block further** and takes
the best of two things there: the analog output of whatever block is at that
position, and the reading of an `ItemFrame` — but only if **exactly one** frame
in that block's space faces the way the comparator's `HorizontalDirectionalBlock.FACING` points. The
direction test comes first and the count second, so a frame pointing some other
way is not counted at all; two frames facing the comparator's way, and the
comparator reads neither.
A container's analog output is
`AbstractContainerMenu.getRedstoneSignalFromContainer`: every slot's count
divided by the smaller of the container's own cap and *that stack's* maximum
size, summed, divided by the number of slots, and mapped onto 0–15. For every
container that reaches this formula the cap is 99 and the stack's own maximum
wins, so a chest of shulker boxes and a chest of cobblestone at the same item
count read very differently.
## Booking a turn, and why a repeater turns off first
All three answer a change by booking a turn rather than acting on it; the
queue itself,
its dedup rule and the drain are [scheduled
ticks](../world/scheduled-ticks.md), which traces a repeater in full. What
belongs here is *which priority each one asks for*, because that is where the
two diodes stop agreeing.
`DiodeBlock.checkTickOnNeighbor` books only when the diode is not locked, only
when the current `DiodeBlock.POWERED` disagrees with the current input, and
only when `LevelTickAccess.willTickThisTick` says nothing is already about to
run there. It picks `TickPriority.EXTREMELY_HIGH` when
`DiodeBlock.shouldPrioritize` holds — when the block it outputs into is itself
a diode whose own input is not on the far side of it, so a diode reading this
one or standing sideways to it, but not one aimed the same way —
`TickPriority.VERY_HIGH` when the diode is currently on, and
`TickPriority.HIGH` otherwise. So a diode's turn-off beats another's turn-on
due on the same tick, and a diode feeding a diode beats both. The only
`TickPriority.NORMAL` booking a repeater makes is `DiodeBlock.setPlacedBy`, at
delay 1, when you place one into an already-powered spot.
`ComparatorBlock.checkTickOnNeighbor` throws all of that away. It has no lock
to consult — `DiodeBlock.isLocked` is false unconditionally and the comparator
does not override it — its delay is a flat 2 whatever the state says, and it
chooses between `TickPriority.HIGH` and `TickPriority.NORMAL` alone; the two
urgent priorities a repeater relies on are not available to it. It also books
on a second condition the repeater does not have: not only when the powered
flag disagrees with the input, but whenever the *computed output value*
differs from the int currently in the block entity.
`DiodeBlock.tick` is what pulse extension is made of. Finding itself off, it
turns on regardless of whether the input is still there — and then, if the
input has already gone, books its own turn-off one delay later at
`TickPriority.VERY_HIGH`. A pulse shorter than the delay is not swallowed; it
is stretched to the delay.
## The channel the observer listens on
```mermaid
flowchart TB
NC["the neighbour channel: Level.updateNeighborsAt and Level.neighborChanged, server only. A write enters it only with Block.UPDATE_NEIGHBORS set, and a block may call it directly"]
SC["the shape channel: Level.neighborShapeChanged, run on both sides by every write without Block.UPDATE_KNOWN_SHAPE"]
RB["RepeaterBlock and ComparatorBlock: DiodeBlock.neighborChanged, then checkTickOnNeighbor"]
RL["RepeaterBlock.updateShape recomputes LOCKED, but only off-axis and only on the server"]
OB["ObserverBlock.updateShape, but only from the direction it faces and only while unpowered"]
SS["ObserverBlock.startSignal books a tick at delay 2, and only if this is not the client and none is booked"]
BOOK["the appointment book"]
TICK["the scheduled tick runs: write POWERED with flag 2. The observer then calls updateNeighborsInFront itself, a diode reaches it as onPlace inside the write"]
NC --> RB --> BOOK
SC --> RL
SC --> OB --> SS --> BOOK
BOOK --> TICK
```
An observer watches for `ObserverBlock.updateShape` arriving from the one
direction it faces, while `ObserverBlock.POWERED` is false, and books a
two-tick appointment. Two ticks later `ObserverBlock.tick` writes the powered
state with flags 2, schedules its own turn-off two ticks after that, and
pulses through `ObserverBlock.updateNeighborsInFront`. The shape channel is
the right one for the job because it carries *your neighbour's state changed*
regardless of whether the neighbour told anybody: a door opened by hand writes
with flags 10 and issues no neighbour update at all, and the observer still
sees it ([block interaction](block-interaction.md)), and dust the observer is watching
carries the same news through [signal and dust](signal-and-dust.md)'s
flag-2 writes.
`RepeaterBlock.LOCKED` works the same way, and it is the only diode property
computed from a redstone reading *outside* tick time — `DiodeBlock.POWERED` is
the only one computed from a reading at all.
`RepeaterBlock.updateShape` recomputes it whenever a neighbour **off the
facing axis** changes, which is the two sides and, harmlessly, up and down; the
value itself comes from the two sides alone. So locking follows a neighbouring
repeater's state without either block scheduling anything.
The tempting conclusion is that both blocks chose the shape channel because it
is the half of the update machinery a client also runs. That is not what the
code does: **both hooks refuse to act on the client.**
`RepeaterBlock.updateShape` recomputes the lock only when the level is not
client-side, and `ObserverBlock.startSignal` returns immediately on a
`ClientLevel`. Nor would it help if they did — a client keeps no appointment
book at all, so a scheduled tick could never fire there. Everything a client
knows about any of these three blocks arrives as a block update.
## One int, and the fan-out that exists to deliver it
A comparator has a block entity for one reason, and it is not a common one:
`ComparatorBlock.calculateOutputSignal` can produce a number that the block
state has nowhere to keep. `DiodeBlock.POWERED` is one bit, and a comparator's
output is 0–15. Plenty of redstone blocks have block entities — the sculk
sensor keeps its last vibration frequency in one and answers with it, and every
container answers from its contents — but a `DaylightDetectorBlockEntity`
stores nothing at all, so having one is no evidence of state that a block state
could not hold.
`ComparatorBlockEntity.getOutputSignal` is that number, written by
`ComparatorBlock.refreshOutputState` — which also flips the powered bit when
it needs to, and then calls `DiodeBlock.updateNeighborsInFront` whether or not
anything changed, whenever the mode is `ComparatorMode.COMPARE`.
The other half of making comparators work is
`Level.updateNeighbourForOutputSignal`. It walks the four horizontals and
notifies any `Blocks.COMPARATOR` it finds; failing that, where the neighbour is
a redstone conductor, it reaches one further and notifies a comparator there
instead, mirroring the comparator's own reach in the opposite direction. Twelve
call sites reach it, and the two general ones are the pair that matters here.
`Level.setBlock` calls it on the server for a write that carries
`Block.UPDATE_NEIGHBORS` and whose new state has an analog output — but an item
entering a chest is not a write at all. That path is `BlockEntity.setChanged`,
which calls it unconditionally, and it is what makes a comparator notice a
hopper filling a chest that nothing else in redstone would have reported.
## Questions players ask
**Why does an observer fire when I open a door next to it, when doors do not
power anything?** Because the observer is not looking for power. It watches
the shape channel, which every ordinary write runs on both sides, and a door
opening is an ordinary write — flags 10, no neighbour updates, three shape
passes.
**Why did my repeater stay on after the input dropped?** Because
`DiodeBlock.tick` turns a repeater on whether or not the input is still there,
and books the turn-off one delay later. That is the mechanism behind pulse
extension, and it is two entries in the scheduled-tick queue rather than any
kind of memory.
**Why can I lock a repeater with another repeater but not with a lever?**
`DiodeBlock.getAlternateSignal` goes through
`SignalGetter.getControlInputSignal` with `DiodeBlock.sideInputDiodesOnly`
true for a repeater, and that flag makes the call answer zero for anything
that is not a `DiodeBlock`.
**Why does my comparator ignore the item frame?** Because
`ComparatorBlock.getInputSignal` gathers only the frames in that block's space
that face the way the comparator's own facing points, and then insists on
exactly one of them. A second frame pointing the same way makes the count two
and the method returns nothing rather than choosing; a second frame pointing
anywhere else is never in the count at all.
## Where to look
`DiodeBlock.isSignalSource` · `DiodeBlock.ownSignal` ·
`DiodeBlock.getDirectSignal` · `DiodeBlock.getInputSignal` ·
`DiodeBlock.getAlternateSignal` · `DiodeBlock.sideInputDiodesOnly` ·
`DiodeBlock.checkTickOnNeighbor` · `DiodeBlock.shouldPrioritize` ·
`DiodeBlock.tick` · `DiodeBlock.updateNeighborsInFront` ·
`DiodeBlock.onPlace` · `RepeaterBlock.getDelay` · `RepeaterBlock.isLocked` ·
`RepeaterBlock.updateShape` · `ComparatorBlock.checkTickOnNeighbor` ·
`ComparatorBlock.calculateOutputSignal` · `ComparatorBlock.getInputSignal` ·
`ComparatorBlock.refreshOutputState` · `ComparatorBlockEntity.getOutputSignal` ·
`AbstractContainerMenu.getRedstoneSignalFromContainer` ·
`Level.updateNeighbourForOutputSignal` · `ObserverBlock.updateShape` ·
`ObserverBlock.startSignal` · `ObserverBlock.tick`
---
*Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes `tools/verify_names.py`.*
---
# VI · Entities
> Verified against **Minecraft 26.2** · Part VI · Everything in the world that is not a block: what one is, who is allowed to move it, how it is described to the other side, and how it stops.
Part V was about a position in a chunk section changing its mind. This part
is about everything that is *not* in the grid — the zombie, the arrow, the
boat, the dropped pickaxe, the invisible marker a data pack left as a
bookmark. They share one base class that is deliberately thin *on behaviour*,
one numbered array for telling clients about themselves, one bag of named numbers, one
collision resolver and one abstract method for being hurt. A player
recognises the part by the things that seem inconsistent about it: a mob
glides where your own movement is crisp, a sheared sheep changes on every
screen at once, a mob you named never despawns, and a hit that lands during
the red flash does nothing at all. Every one of those is the same question in
a different costume — **which of the two programs is allowed to decide
this?**
## The shape of the part
Part VI is a ladder. Each page needs the one below it and nothing above it,
and the second rung is the one everything else leans on.
```mermaid
flowchart BT
A["Entity anatomy — what an entity is"]
B["Authority — who is allowed to simulate it"]
C["Entity lifecycle — how it enters a world and leaves one"]
D["Synched entity data — the channel that describes it"]
E["Attributes — the named numbers it carries"]
F["Movement and collision — what it does"]
G["Goals and brains — why it does it"]
H["Pathfinding — how a decision becomes a direction"]
I["Damage and death — how it stops"]
A -- "one type, one factory, one live object" --> B
B -- "and one side of each pair does the arithmetic" --> C
C -- "now it is in a world, findable and ticking" --> D
D -- "one of six channels that describe it" --> E
E -- "gravity, step height, speed: the physics knobs are attributes" --> F
F -- "something has to set xxa and zza" --> G
G -- "a decision is only a position until something walks there" --> H
H -- "and everything above can be ended by one abstract method" --> I
```
One dependency runs forward instead of back, and it is Part X's rather than
this part's: [the client level](../client/the-client-level.md) opens by saying
it is *not* an authority either, which only lands once
[authority](authority.md) has said what one is. So this part is watched first,
and nothing here waits on Part X.
## Before you start
[The level tick](../server/server-level-tick.md), because half this part's
surprises are claims about *which phase* something ran in — the entity phase
runs *after* the phase that broadcasts entity changes, and that one ordering
explains why an attribute change is a tick late while a sheep sheared out of
the packet queue, before the tick proper, is not. [Tickets and
loading](../world/tickets-and-loading.md), because whether an entity ticks at
all is a property of the chunk it is standing in — for everything except a
player, which is exempt — and [chunk anatomy](../world/chunk-anatomy.md) for the heightmaps that decide
where a mob may spawn. [Points of interest](../world/points-of-interest.md),
because a villager's whole day is claims on them and [goals and
brains](ai-goals-and-brains.md) draws `PoiManager` as a lane rather than
explaining it. [Blocks and states](../blocks/blocks-and-states.md) for the shapes that entities collide
with, and Part IV's *scheduled ticks* is *not* needed — entities keep no
appointment book.
One more Part IV page, for one lecture: [environment attributes and
timelines](../world/environment-attributes-and-timelines.md) before [goals
and brains](ai-goals-and-brains.md). A villager's schedule is a data-pack
`Timeline` looked up at a position, and this part asks that system a
question rather than teaching it.
## Watch in this order
1. [Entity anatomy](entity-anatomy.md) — one `EntityType` from the registry,
through a factory, to a live object the level ticks. The registry's
default is a pig, and that default reaches the network and never reaches
your save file.
2. [Authority](authority.md) — a zombie, a player and a boat each take one
step on both sides. The client runs no physics at all for the mob chasing
you, and the server runs your own player's physics every tick and then
overwrites the answer with a number you sent it.
3. [Entity lifecycle](entity-lifecycle.md) — a zombie appears in the dark,
is ticked for a while, and is either forgotten or written to disk. The
spawner rolls one height per category per chunk per tick, so caves and the
open field compete for the same slice.
4. [Synched entity data](synched-entity-data.md) — a sheep is sheared and
every screen in range agrees within the tick. The slot the wool lives in
is written nowhere in `Sheep`: it is 18 because eighteen slots were handed
out above it, and the packet stops at 254 because 255 means stop.
5. [Attributes](attributes.md) — Strength II lands, and no packet is sent at
all. Eight of the forty attributes never reach the client, and attack
damage is one of them.
6. [Movement and collision](movement-and-collision.md) — one tick of a
falling zombie. The mover answers *what did I walk through* afterwards, by
replaying the tick's movement, which is why fire and water touched in one
step always end in the extinguish.
7. [AI: goals and brains](ai-goals-and-brains.md) — a villager's day, and the
same machinery under a zombie that has none of it. Schedules are gone: a
villager goes to bed because it asked the world what time it is where it
is standing.
8. [Pathfinding](pathfinding.md) — the other half of the same lecture. Giving
up is machinery: the node being walked towards carries a timeout, and the
mob you watch walk into a wall and then wander off is running a scheduled
surrender.
9. [Damage and death](damage-and-death.md) — the part's closer. An arrow, a
dozen owners of one number, and one abstract method. A hit that lands inside the
red flash usually does nothing at all, and when it is stronger than the
last, only its excess lands — silently.
## Reference this part uses
Three of them were written for this part.
[Attributes](../../reference/attributes.md) — all forty, with defaults,
ranges and the syncable flag. [Entity data
serializers](../../reference/entity-data-serializers.md) — all 43, in wire-id
order. [Damage outside `LivingEntity`](../../reference/non-living-damage.md)
— what each of the twenty-one non-living classes does when you hit it. Then
[packets](../../reference/packets.md), [registries](../../reference/registries.md),
[game rules](../../reference/gamerules.md), [math and
primitives](../../reference/math-and-primitives.md) and [diagram
lanes](../../reference/lanes.md).
The part stops at `Avatar`, the class 26.2 inserted below `Player`:
everything player-shaped is Part VIII, drawing an entity is Part XI, and the
prediction ledger behind the blocks you place is Part X.
---
*Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes `tools/verify_names.py`.*
---
# Entity anatomy
> Verified against **Minecraft 26.2** · Part VI · What an entity *is*: one `EntityType` from the registry, through a factory, to a live object the level ticks.
You type */summon pig*, and by the next tick there is a pig standing where
you are. Between the command and the animal are three objects and one factory
call: a `ResourceKey` in `EntityTypeIds`, the `EntityType` that `EntityTypes`
built from it before any world existed, and the `Entity` that the type's
`EntityType.EntityFactory` returns. This page is the vocabulary of that chain,
and the rest of Part VI is built on it. The surprise is what happens when the
name is *wrong*, because that depends entirely on which door the name came
through. `Registries.ENTITY_TYPE` is one of the few **defaulted** registries
and its default is *pig* — and `DefaultedMappedRegistry` overrides nine
lookups to hand it back, `DefaultedMappedRegistry.byId` and
`DefaultedMappedRegistry.getValue` among them. So an entity-type id the client has never
heard of, arriving inside a `ClientboundAddEntityPacket`, is decoded by
`ByteBufCodecs.registry` through `IdMap.byIdOrThrow` — which cannot throw
here, because `DefaultedMappedRegistry.byId` never returns null — and a pig
walks out of the packet. One lookup is overridden the other way:
`DefaultedMappedRegistry.getOptional` calls the *superclass* method and so
still answers empty. `EntityType.CODEC` is `Registry.byNameCodec`, which
resolves through the plain `Registry.get`, untouched here. The same unknown
name in a region file therefore yields nothing at
all: `EntityType.create` logs *Skipping Entity with id …* and leaves a hole
where the entity was. The default reaches the network and never reaches your
save file.
## The cast
| class | what it decides | thread |
|---|---|---|
| `EntityType` | one registered kind: its factory, category, frozen dimensions, feature flags, and the two numbers that decide how it reaches clients | built in a class initialiser, read from both game threads after |
| `EntityTypes` | which 158 kinds exist and what every one of them is *sized* like — the most useful single table in the package | class initialiser, once |
| `Entity` | position, box, network id, synched values, vehicle, removal reason. Deliberately thin on behaviour | the tick thread of whichever level owns it |
| `EntityDimensions` | width, height, eye height, attachment points, and whether `Attributes.SCALE` may touch them | immutable record, shared by every entity of a type |
| `SynchedEntityData` | which of the entity's fields the other side is told about ([synched entity data](synched-entity-data.md)) | written on the owning side, applied on the receiving one |
| `EntityInLevelCallback` | whether the object is *in* a world or merely on the heap | installed by the level's entity manager |
| `ServerEntity` | when a tracked entity's state becomes packets ([entity lifecycle](entity-lifecycle.md)) | server main thread |
| `EntityReference` | how one entity remembers another across a chunk unload | wherever it is resolved |
Only one packet is this page's own: `ClientboundAddEntityPacket`, the birth
announcement. Everything after it — `ClientboundSetEntityDataPacket`,
`ClientboundEntityPositionSyncPacket`, `ClientboundEntityEventPacket`,
`ClientboundRemoveEntitiesPacket` — belongs to a sibling page.
## The type is frozen, the entity is not
An entity is anything in the world that is not a block: mobs, players, items
on the ground, arrows, boats, item frames, experience orbs, the invisible
markers a data pack uses as bookmarks. The kind is an `EntityType`, built once
and never changed. The individual is an `Entity`, and nearly all of its state
is mutable by design.
```mermaid
flowchart TB
ET["EntityType: one object per registered kind, 158 of them"]
DIM["EntityDimensions: width, height, eye height, EntityAttachments, and a fixed flag. Frozen by EntityType.Builder.build"]
CAT["MobCategory: the spawn cap and despawn distance"]
FAC["EntityType.EntityFactory: the constructor reference"]
NET["clientTrackingRange in chunks (default 5), updateInterval in ticks (default 3)"]
E["Entity: the live object"]
SED["SynchedEntityData: eight accessors defined inline, then whatever the subclass chain adds"]
POS["Entity.position at the feet, and Entity.bb, a stored box rather than a computation"]
CB["Entity.levelCallback: EntityInLevelCallback.NULL until a level takes the object"]
REM["Entity.removalReason: null is the whole of the am-I-alive bit"]
RIDE["Entity.vehicle and Entity.passengers, an immutable list"]
ET --> FAC
ET --> DIM
ET --> CAT
ET --> NET
FAC -- "creates" --> E
DIM -- "copied into Entity.dimensions and Entity.eyeHeight, both caches" --> E
E --> SED
E --> POS
E --> CB
E --> REM
E --> RIDE
```
`Entity` implements nine interfaces — `Nameable`, `EntityAccess`,
`ScoreHolder`, `SyncedDataHolder`, `DataComponentGetter`, `ItemOwner`,
`SlotProvider`, `DebugValueSource` and `TypedInstance` over `EntityType` —
which is a fair measure of how many systems reach into it. Health, AI, damage
and inventory are all further down the tree.
In 26.2 the type constants are **not on `EntityType`**. They live in two
parallel files: `EntityTypeIds`, 158 `ResourceKey`s with no reference to any
entity class, and `EntityTypes`, the 158 matching objects that
`EntityType.Builder` produced from them. `MobCategory` — `MobCategory.MONSTER`,
`MobCategory.CREATURE`, `MobCategory.AMBIENT`, `MobCategory.AXOLOTLS`,
`MobCategory.UNDERGROUND_WATER_CREATURE`, `MobCategory.WATER_CREATURE`,
`MobCategory.WATER_AMBIENT`, `MobCategory.MISC` — carries the spawn cap and
despawn distance that [entity lifecycle](entity-lifecycle.md) uses. None of
this is data-driven: `Registries.ENTITY_TYPE` is code-registered, and what a
data pack *can* reach is the tags in `EntityTypeTags`, the loot table at
*entities/<id>*, `DataComponents.ENTITY_DATA` on a spawn egg, and the
per-species variant registries (`Registries.WOLF_VARIANT`,
`Registries.CAT_VARIANT`, `Registries.PAINTING_VARIANT` and the rest).
### Dimensions, attachments and pose
`EntityDimensions` is a record — width, height, eye height, an
`EntityAttachments` map and a *fixed* flag — and
`EntityDimensions.makeBoundingBox` centres the box in X and Z on the position
and grows it **upward**, because the position is the feet.
`EntityDimensions.scale` returns the record unchanged when it is fixed, and
also when both factors are 1. The flag is not the only way to escape `Attributes.SCALE`,
either: plain `Entity.getDimensions` never scales at all, so every non-living
entity ignores scale for free, and `LivingEntity.getDimensions` — which is
final, the overridable hook being `LivingEntity.getDefaultDimensions` —
short-circuits a sleeping entity to `LivingEntity.SLEEPING_DIMENSIONS` before
any scaling happens.
`EntityAttachments` answers where a passenger sits, where the name tag floats,
where the lead attaches: `EntityAttachment.PASSENGER`,
`EntityAttachment.VEHICLE`, `EntityAttachment.NAME_TAG` and
`EntityAttachment.WARDEN_CHEST`, each with a fallback such as
`EntityAttachment.Fallback.AT_HEIGHT` that `EntityAttachments.Builder.build`
fills in from the width and height.
`Pose` is eighteen constants with **explicit wire ids**, not ordinals — the
ones that matter are `Pose.STANDING`, `Pose.CROUCHING`, `Pose.SWIMMING`,
`Pose.FALL_FLYING`, `Pose.SLEEPING`, `Pose.SPIN_ATTACK` and `Pose.DYING`, and
the rest are single-mob animation states such as `Pose.EMERGING` and
`Pose.DIGGING`. `Pose.BY_ID` is built with
`ByIdMap.OutOfBoundsStrategy.ZERO`, so a pose id outside the range decodes
silently to `Pose.STANDING` rather than failing the connection. Pose is the
synched value on the *base* class that changes physics — a dozen subclasses
have their own, from a pufferfish's puff state to a slime's size — and the
loop is worth stating
because everything else on this page hangs off it: `Entity.setPose` writes the
value, `SynchedEntityData.set` calls `Entity.onSyncedDataUpdated`
**inside the setter**, before anything is marked dirty, and that sees
`Entity.DATA_POSE` and calls `Entity.refreshDimensions`, which asks
`Entity.getDimensions` for the new record, overwrites both caches, and calls
`Entity.reapplyPosition` to rebuild the box. The side that set the pose
resizes immediately, the other resizes when the value lands. If the box grew,
`Entity.fudgePositionAfterSizeChange` nudges the entity out of whatever it now
overlaps — but only on the server, only after the first tick, only with
physics on, only when the entity is not a `Player`, and only when the new box
is at most four blocks in both width and height.
## The tree, and the class that was inserted into it
`Entity` has **18** direct subclasses and 191 descendants. `LivingEntity` and
its 124 descendants are two thirds of that; the non-living branches are the
other 66.
*(figure: tree-Entity.svg — a generated SVG, not reproduced here)*
The
Entity tree to three levels, generated from the decompile. Click to enlarge.
The full drawing, with the block, item and screen trees beside it, is in
[what extends what](../../maps/hierarchy.md). What matters here is the shape:
a long spine and a scattering. `LivingEntity` holds 124 of the 191 and has
exactly **three** direct subclasses — `Avatar`, `ArmorStand` and `Mob` —
which is worth saying plainly, because it means **an armour stand is a living
entity with no AI at all**: no `GoalSelector`, no `PathNavigation`, both of
those being `Mob`'s. The `Brain` is not `Mob`'s, though. It is declared on
`LivingEntity`, built in its constructor and written under a *Brain* tag by
every living entity, so an armour stand carries an empty one.
`PathfinderMob` is 86 lines that add walk-target valuation, not movement,
which is why `Ghast` and `Phantom` navigate without ever being one.
`AgeableMob` adds babies, `Animal` and `Monster` split by disposition, and
`Monster` implements `Enemy`, a marker interface carrying nothing but
XP-reward constants.
**`Avatar` is new and it is the biggest structural change in the part.** It
sits between `LivingEntity` and `Player`, and it is 57 lines: the
player-shaped `Avatar.POSES` dimension map, `Avatar.DEFAULT_EYE_HEIGHT` of
1.62, the skin-part and handedness synched values
(`Avatar.DATA_PLAYER_MAIN_HAND`, `Avatar.DATA_PLAYER_MODE_CUSTOMISATION`) and
one abstract method, `Avatar.getProfile`. Its point is `Mannequin` — a
posable, profile-skinned, player-looking entity in the decoration package that
is *not* a `Player` and carries none of the inventory, abilities or hunger.
Anything written against "`Player extends LivingEntity`" is now wrong by one
level. On the client `AvatarRenderer` serves both `AbstractClientPlayer` and
`ClientMannequin`, a client-only subclass that `Mannequin` accepts by holding
a mutable `Mannequin.constructor` factory the client swaps at startup.
Cutting across the tree are the capability interfaces, where most of the
shared behaviour actually lives: `Leashable` is the fattest, with
`Leashable.tickLeash` called from `Entity.baseTick`, and beside it
`Bucketable`, `EquipmentUser`, `NeutralMob`, `Attackable`, `Targeting`,
`TraceableEntity`, `OwnableEntity`, `Shearable`, `PlayerRideableJumping`,
`ItemSteerable`, and `ContainerUser`, with exactly two implementors: `Player`
and `CopperGolem`.
`EntityReference` deserves a name of its own. It stores either a UUID or the
live object, and `EntityReference.getEntity` resolves lazily *and decays back
to the UUID* the moment the target reports itself removed. It is how "who
last hurt me", "who owns this pet" and "who shot this arrow" survive a chunk
unload.
### Where the 716 files are
| subpackage | files | what |
|---|---:|---|
| `entity/ai` | 277 | goals, brains, navigation, attributes, sensors |
| `entity/animal` | 130 | one subpackage per species now |
| `entity/monster` | 84 | likewise |
| `world/entity` itself | 75 | `Entity`, `LivingEntity`, `Mob`, `Avatar`, `EntityType`, `EntityTypes`, the capability interfaces |
| `entity/projectile` | 37 | arrows, fireballs, thrown items |
| `entity/boss` | 24 | dragon and wither |
| `entity/vehicle` | 24 | boats and minecarts |
| `entity/npc` | 15 | villagers and traders |
| `entity/player` | 14 | `Player`, `Inventory`, `Abilities` |
| `entity/decoration` | 12 | armour stands, frames, paintings, `Mannequin` |
| `entity/variant` | 11 | the data-driven mob variants |
| `entity/item`, `entity/raid`, `entity/ambient`, `entity/schedule` | 13 | items, raids, bats, villager day plans |
## From a registry entry to a live object
`EntityTypes` runs `EntityType.Builder.build` for each of the 158 keys in
`EntityTypeIds` and registers the result, and it is that call which freezes
the `EntityDimensions` and its attachment points for the life of the type.
Everything below happens per entity, long afterwards.
```mermaid
sequenceDiagram
participant SumC as SummonCommand
participant ET as EntityType
participant Entity as Entity
participant SL as ServerLevel
participant PESM as PersistentEntitySectionManager
participant SE as ServerEntity
participant CPL as ClientPacketListener
SumC->>SumC: reject out of bounds, then reject on peaceful via isAllowedInPeaceful
SumC->>ET: loadEntityRecursive(tag with an id string, level, EntitySpawnRequest)
ET->>ET: by(ValueInput) reads id through EntityType.CODEC
ET->>ET: create checks canSpawn, then calls the EntityFactory
ET->>Entity: constructor takes the next id, invents a UUID, copies the type dimensions
Entity->>Entity: eight base accessors, then defineSynchedData down the chain, then setPos
ET->>Entity: load(ValueInput) then readAdditionalSaveData
SumC->>Entity: snapTo, the postLoad processor, before any passenger exists
ET->>Entity: each Passengers child loaded the same way, then startRiding
SumC->>SL: tryAddFreshEntityWithPassengers, one addFreshEntity per body
SL->>PESM: addNewEntity
PESM->>Entity: setLevelCallback, and only now is it in a world
Note over SL,Entity: the next server tick
SL->>Entity: setOldPosAndRot, then the tick count rises, then tick
Note over SE,CPL: later, when a player comes into range
SE->>Entity: getAddEntityPacket
SE->>CPL: ClientboundAddEntityPacket, bundled with data, attributes and equipment
CPL->>CPL: createEntityFromPacket, then recreateFromPacket, then ClientLevel.addEntity
```
**Name to type.** `EntityType.by` reads the *id* field through
`EntityType.CODEC`. An unknown id yields nothing, and the entity is dropped
with the log line the opening quoted — this is the save-file half of the hook.
**Type to object.** `EntityType.create` checks `EntityType.canSpawn`: the
feature flags, plus a peaceful-difficulty test gated on the type's own
`EntityType.isAllowedInPeaceful` flag, which is a declared property and not a
synonym for *hostile*. `EntitySpawnRequest.ignoreChecks` skips both. Then the
factory runs. The `Entity` constructor takes the next id from the level,
invents a UUID with `Mth.createInsecureUUID`, copies the type's dimensions
into its cache, and builds the synched-data container: eight accessors defined
inline — the shared flags byte, air supply, custom name and its visibility,
silence, no-gravity, pose and frozen ticks — and *then* the abstract
`Entity.defineSynchedData`, which contributes nothing on the base class and
exists only for the subclasses. It then calls `Entity.setPos` at the origin,
so a fresh entity already has a full-size box rather than the zero-size
`Entity.INITIAL_AABB` the field initialiser gave it.
**Tag to state.** `Entity.load` reads position (clamped to ±3.0000512E7
horizontally), motion, rotation and UUID, then calls the abstract
`Entity.readAdditionalSaveData`, then `Entity.reapplyPosition` again if
`Entity.repositionEntityAfterLoad` says so — which everything says except
`BlockAttachedEntity`, the corpus's one override, so paintings, item frames
and leash knots are precisely the entities that *skip* it and keep the
position their own load computed. The save twin is `Entity.addAdditionalSaveData`, and
passengers save inside their vehicle: `Entity.save` returns false for anything
currently riding, and the vehicle writes them into its *Passengers* list
through `Entity.saveAsPassenger`, using `Entity.getEncodeId`, which is null
for types that never serialise.
**Object to level.** `ServerLevel.tryAddFreshEntityWithPassengers` refuses if
any UUID in the stack is already loaded, then
`ServerLevelAccessor.addFreshEntityWithPassengers` calls
`ServerLevel.addFreshEntity` once per body.
`PersistentEntitySectionManager.addNewEntity` files it into a section and
replaces `EntityInLevelCallback.NULL` with a real callback. *That* is the
moment it stops being an object on the heap;
[entity lifecycle](entity-lifecycle.md) takes it from here.
**Level to client.** `ServerEntity.addPairing` sends
`ServerEntity.sendPairingData` as one bundle: the packet
`Entity.getAddEntityPacket` returns — id, UUID, type, position, velocity,
three rotation *bytes* and one varint of type-specific data — then the
non-default synched values, then attributes and equipment. On the client,
`ClientPacketListener.handleAddEntity` builds the object, calls
`Entity.recreateFromPacket`, and only then adds it to the level.
The spawn-egg path is a different pipeline that meets this one at the end.
`EntityType.spawn` snaps the new entity out of collision with
`EntityType.getYOffset`, aligns head and body rotation, runs
`Mob.finalizeSpawn`, applies a `PostSpawnProcessor`, adds it through
`ServerLevelAccessor.addFreshEntityWithPassengers` and plays an ambient
sound. `SummonCommand` also calls `Mob.finalizeSpawn`, but only when the
command asks for it, and it never touches the Y offset.
## The tick both sides share
`ServerLevel.tickNonPassenger` calls `Entity.setOldPosAndRot`, increments
`Entity.tickCount`, opens a profiler section named after the entity type and
calls `Entity.tick` — through `Level.guardEntityTick`, which turns any
exception into a crash report with the entity's details attached.
`ClientLevel.tickEntities` does the same, through the same guard, having first
skipped anything removed, riding or frozen by the tick-rate manager. No
entity is ever *ticked* on a worker pool, and `Entity` is not thread-safe —
though entities are constructed on one: `ChunkStatus.SPAWN` runs
`NaturalSpawner.spawnMobsForChunkGeneration` on the worldgen executor, so a
chunk's first animals are built and finalised off the main thread before the
chunk ever becomes live.
`Entity.tick` on the base class is one line: call `Entity.baseTick`.
Everything readers remember happening "in tick" is in `Entity.baseTick` — the
dead-vehicle check, the boarding cooldown, the portal handling, the fluid
snapshot, swimming, fire ticking, the lava halving of fall distance, the
below-world check, the leash — or in an override. `LivingEntity.tick` calls up
and then runs `LivingEntity.aiStep`, and `Mob.tick` calls up and refreshes its
goal-control flags through `Mob.updateControlFlags` every five ticks — and
only on the server, which is the one line of this section the client does not
run.
What differs between the sides is not the tick but what the tick is allowed to
*do*, and that is exactly the subject of the next page,
[authority](authority.md).
## Three things about the id
**Why do two entities from different worlds compare equal?** Because
`Entity.equals` compares the network id and nothing else — not the UUID, not
identity — and `Entity.hashCode` *is* the id. Ids are handed out by
`ServerLevel.getNextEntityId` from a static `AtomicInteger` on `ServerLevel`,
so they are process-global and the level is consulted only to avoid a
collision. Never put entities from two levels in one set.
**Why does a client-side entity throw before its packet arrives?**
`Level.getNextEntityId` returns a literal zero and `ClientLevel` does not
override it, while zero is the reserved invalid id and `Entity.getId` throws
*Tried to access entity ID before ID assignment* on it. Between construction
and `Entity.setId` — which `Entity.recreateFromPacket` performs — a
client-side entity has no id, and therefore no equality and no hash.
`ServerLevel.getNextEntityId` skips zero deliberately.
**Why is my hitbox offset from where I think the entity is?**
`Entity.position` is the bottom centre and the box grows up from the feet.
**Why did the hitbox not change when I changed the size?** Because
`Entity.dimensions`, `Entity.eyeHeight` and `Entity.bb` are caches, and only
two things refresh them unasked for every entity: a pose change on the base
class and `LivingEntity.onAttributeUpdated` on `Attributes.SCALE`. Twelve
subclasses do the same for a value of their own — `AgeableMob` on the baby
bit among them — and everything else has to call `Entity.refreshDimensions`
itself.
The two eye-height accessors disagree while a cache is stale:
`Entity.getEyeHeight` with no argument reads the cache, the `Pose` overload
recomputes.
**Why is a player never created from its own entity type?** Because
`EntityTypes.PLAYER` was built with `EntityType.Builder.createNothing`, so its
factory returns null, and it is `EntityType.Builder.noSave` and
`EntityType.Builder.noSummon` besides. `EntityType.create` for a player always
yields null, which is why `ClientPacketListener.createEntityFromPacket`
special-cases it and hand-builds a `RemotePlayer` from the player info it
already has.
**Why does that entity never move smoothly?** Possibly because it is on a
list. `EntityType.trackDeltas` names ten types whose velocity is simply never
sent — `EntityTypes.PLAYER`, `EntityTypes.WITHER`, `EntityTypes.BAT`,
both item frames, `EntityTypes.PAINTING`, `EntityTypes.LEASH_KNOT`,
`EntityTypes.LLAMA_SPIT`, `EntityTypes.END_CRYSTAL` and
`EntityTypes.EVOKER_FANGS` — one of the few places in the codebase where
behaviour is a literal list of types rather than a tag or a flag. Or because
of the other two numbers: `EntityType.clientTrackingRange` is in **chunks**
and `EntityType.updateInterval` in ticks, both fixed at registration.
`EntityTypes.MARKER` has a tracking range of 0 and is never sent to anyone,
and `EntityTypes.AREA_EFFECT_CLOUD` has an update interval of
*Integer.MAX_VALUE*.
**Why does entity render distance depend on my render distance?** Because
`Entity.viewScale` is a **static** field on `Entity` — process-global state —
and `LevelExtractor` writes it from the effective render distance *and* the
entity-distance option together, not from the option alone.
## Where to look
`EntityTypeIds` · `EntityTypes` · `EntityType.Builder.build` ·
`EntityType.CODEC` · `EntityType.by` · `EntityType.create` ·
`EntityType.canSpawn` · `EntityType.loadEntityRecursive` ·
`EntityType.spawn` · `MobCategory` · `EntityDimensions.makeBoundingBox` ·
`EntityAttachments` · `Pose.BY_ID` · `Entity` · `Entity.defineSynchedData` ·
`Entity.load` · `Entity.refreshDimensions` · `Entity.setRemoved` ·
`Entity.baseTick` · `Entity.getAddEntityPacket` ·
`PersistentEntitySectionManager.addNewEntity` · `LivingEntity` · `Avatar` ·
`Mob` · `PathfinderMob` · `EntityReference` · `ClientboundAddEntityPacket`
---
*Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes `tools/verify_names.py`.*
---
# Authority: who is allowed to simulate
> Verified against **Minecraft 26.2** · Part VI · A zombie, a player and a boat each take one step, on the server and on the client, and only one side of each pair does any arithmetic.
A zombie walks towards you across a field. Both programs have a `Zombie`
object; both tick it every twentieth of a second; both call the same
`Entity.tick`. Only one of them works out where it goes. Standing next to
the zombie is another player, and their copy is the other way up — and the
boat you are sitting in is a third case again, authoritative on your machine
and nowhere else. Five predicates on `Entity` — one of them final — decide
all of it, and they invert the naive picture in **both** directions: **the client runs no physics
at all for the mob chasing you, while the server runs your player's physics
every tick and then overwrites the answer with a number your client sent.**
This is the single most error-prone idea in the entity part, and four other
pages depend on it — [movement and collision](movement-and-collision.md)
here, [input to movement](../player/input-to-movement.md) in Part VIII,
[what the client is told](../networking/what-the-client-is-told.md) in Part
IX, and [the client level](../client/the-client-level.md) in Part X. It is
stated in full once, here.
## The cast
| class | what it decides | thread |
|---|---|---|
| `Entity` | the five predicates, and every gate inside `Entity.move` that reads them | both main threads |
| `Player` | overrides four of the five, and is the reason the picture inverts | both |
| `Mob` | narrows `Entity.isEffectiveAi` with `Mob.isNoAi` | both, but only the server's copy acts on the answer |
| `LivingEntity` | `LivingEntity.aiStep`, which either simulates or coasts | both |
| `ClientPacketListener` | whether an inbound position packet moves the entity or only updates a codec | client main |
| `ServerGamePacketListenerImpl` | re-runs the client's numbers for the entities the client owns | server main |
## Five predicates, and the final one the other four hang off
`Entity.isLocalInstanceAuthoritative` is the root and it is **final** — no
class overrides it. It asks a different question on each side: on the client,
*am I locally client-authoritative?*; on the server, *am I **not**
client-authoritative?* Everything else hangs off those two.
```mermaid
flowchart TD
Q["Entity.isLocalInstanceAuthoritative — final"]
Q -- "on the client" --> LCA["Entity.isLocalClientAuthoritative"]
Q -- "on the server" --> NCA["not Entity.isClientAuthoritative"]
LCA --> LCAD["base: my controlling passenger's answer, or false"]
LCA --> LCAP["Player: am I the local player?"]
NCA --> CAD["base: my controlling passenger's answer, or false"]
NCA --> CAP["Player: always true"]
Q --> SIM["Entity.canSimulateMovement — defaults to it"]
Q --> AI["Entity.isEffectiveAi — defaults to it"]
SIM --> SIMP["Player overrides: not a client, or I am the local player"]
AI --> AIP["Player overrides: the same"]
AI --> AIM["Mob narrows: and not Mob.isNoAi"]
```
Two things in that picture are easy to miss. The base implementations of
both `Entity.isLocalClientAuthoritative` and `Entity.isClientAuthoritative`
**delegate to the controlling passenger** — an entity with nobody steering it
answers false to both, and an entity with a rider inherits the rider's answer.
That single line is the whole vehicle model. And `Player` overrides
`Entity.canSimulateMovement` and `Entity.isEffectiveAi` to something
*different* from the root — *not a client, or I am the local player* — which
is what lets the server simulate a player it is not authoritative for.
Three classes narrow the AI predicate further and are worth naming because
they are the exceptions people trip over: `Mob.isEffectiveAi` adds
`Mob.isNoAi`, which is where the *NoAI* tag actually bites, and both
`ArmorStand.isEffectiveAi` and `Mannequin.isEffectiveAi` add a physics or
immovability flag of their own.
## Three cases, read on both sides
The columns are three shapes an entity can have; the rows are what each
predicate answers and what follows from it.
| | a tracked mob | a player | a boat you are riding |
|---|---|---|---|
| `Entity.isClientAuthoritative` | false | **true**, unconditionally | true, inherited from you |
| `Entity.isLocalInstanceAuthoritative`, **server** | **true** | false | false |
| `Entity.isLocalInstanceAuthoritative`, **client** | false | true only on *your own* player | **true**, on your machine only |
| `Entity.canSimulateMovement`, server | true | **true** — the override | false |
| `LivingEntity.travel` runs on the server | yes | yes, and the result is overwritten | n/a |
| `LivingEntity.travel` runs on the client | **never** | only for your own player | n/a — `AbstractBoat.floatBoat` and `AbstractBoat.controlBoat` do it instead |
| `Entity.checkFallDamage` inside `Entity.move` | server only | **your own client only** — the server reaches it by the packet path | client only |
| what the other side does instead | is interpolated, and stands still when the interpolation runs out | applies your movement packet | applies its own copy's packet |
The row that surprises people is the last-but-one. `Entity.move` gates
`Entity.checkFallDamage` on `Entity.isLocalInstanceAuthoritative`, which for
a player is true on your own client and **false on the server** — so the copy
that runs it every tick is the one that cannot hurt you.
`LivingEntity.checkFallDamage` needs a `ServerLevel` before it computes any
damage, so your client only accumulates the fall distance and lets
`Block.fallOn` fire. The server reaches fall damage from
`Entity.doCheckFallDamage` instead, driven by the movement packet, which is
[Part VIII's subject](../player/input-to-movement.md).
### The mob: the client is not correcting, it is replaying
A client-side zombie fails `Entity.isLocalInstanceAuthoritative` because
nothing is riding it, so `Entity.canSimulateMovement` is false and
`LivingEntity.aiStep` never reaches `LivingEntity.travel`. What it does
instead is the branch `LivingEntity.aiStep` opens with: if an
`InterpolationHandler` is running, step it; **otherwise scale the stored
delta by 0.98** — and nothing then applies that delta, because the only thing
that would is `Entity.move`, which on this side only `LivingEntity.travel`
reaches. There is no collision, no gravity, no friction and no attempt at
prediction. It is not simulating and being corrected — it is replaying what
`ClientboundMoveEntityPacket` and `ClientboundEntityPositionSyncPacket` tell
it, and standing perfectly still when the interpolation runs out.
### The player: simulated twice, believed once
A `ServerPlayer` passes `Entity.canSimulateMovement` and
`Entity.isEffectiveAi` — both true on the server by `Player`'s override — so
the server's copy runs the whole of `LivingEntity.travel` during the entity
phase of its tick. It also fails `Entity.isLocalInstanceAuthoritative`, so
none of the consequences that gate on it fire. Then the next
`ServerboundMovePlayerPacket` arrives, and
`ServerGamePacketListenerImpl.handleMovePlayer` moves the player again with
`MoverType.PLAYER` and the *client's* distance, and finishes with
`Entity.absSnapTo` at the client's claimed position. The server's own
simulation is a sanity check that produced a number nobody uses.
One consequence reaches a block. `SweetBerryBushBlock.entityInside` needs to
know how far the entity moved this tick, and it asks
`Entity.isClientAuthoritative` to decide **how to find out**:
`Entity.getKnownMovement` for a player, whose movement the server did not
compute, and old-position-minus-current for everything else. Authority is not
only about physics — it is about which of two numbers is real.
### The boat: authoritative on exactly one machine
Sit in a boat and the base delegation makes it yours. On your client
`Entity.isLocalClientAuthoritative` walks to the controlling passenger, finds
you, and returns true, so your machine simulates the boat for real. On the
server the same delegation makes `Entity.isClientAuthoritative` true, so the
server's copy is *not* authoritative and does not simulate — it zeroes its
own delta outright. Every other client's copy does the same, and is moved
only by `AbstractBoat.interpolation`.
```mermaid
sequenceDiagram
participant CL as ClientLevel
participant LP as LocalPlayer
participant AB as AbstractBoat
participant Wire as the network
participant SGPL as ServerGamePacketListenerImpl
participant SL as ServerLevel
participant CPL as ClientPacketListener
CL->>AB: tickNonPassenger, and isLocalInstanceAuthoritative is true
AB->>AB: floatBoat, then controlBoat, then move for real
LP->>Wire: ServerboundMoveVehiclePacket.fromEntity, once per client tick
Wire->>SGPL: handleMoveVehicle
SGPL->>AB: move with MoverType.PLAYER and my distance, then absSnapTo
SGPL->>AB: setOnGroundWithMovement then doCheckFallDamage
Note over SGPL,SL: the server never simulated it, so this is where the boat gets its physics consequences
SGPL-->>Wire: nothing, when the move is accepted
SGPL->>Wire: ClientboundMoveVehiclePacket, only when it is rejected
Wire->>CPL: handleMoveVehicle
CPL->>AB: absSnapTo the server's position, then echo a ServerboundMoveVehiclePacket back
```
The inbound half of that is the sharpest demonstration of what the predicate
is for. `ClientboundMoveVehiclePacket` is not a routine update — the server
sends it only when it has *rejected* your movement, and the client applies it
only for a vehicle it is authoritative for, and then immediately echoes a
`ServerboundMoveVehiclePacket` back to confirm. Meanwhile the ordinary
per-entity position packets take the opposite branch:
`ClientPacketListener.handleEntityPositionSync` and
`ClientPacketListener.handleMoveEntity` both check
`Entity.isLocalInstanceAuthoritative` and, when it holds, **do not move the
entity** — `ClientPacketListener.handleMoveEntity` decodes the delta into the
entity's position codec and stops there, and
`ClientPacketListener.handleEntityPositionSync` records the absolute position
in the codec on either branch. The server's opinion about where your boat is
gets recorded and ignored.
## Where the gates actually sit
Authority is not one flag consulted once. It is read at eight places in
`Entity.move` and `LivingEntity.aiStep` alone, and three of those eight read
the same member:
- the vertical collision flags and `Entity.setOnGroundWithMovement` run if
the entity moved vertically **or** is locally authoritative — the
horizontal flags are always updated;
- `Entity.checkFallDamage` runs only if it is locally authoritative;
- `Entity.restituteMovementAfterCollisions` — the bounce — runs on
`Entity.canSimulateMovement`;
- the step sound and `GameEvent.STEP` run if this is not a client **or** the
instance is locally authoritative;
- in `LivingEntity.aiStep`, the 0.98 decay of the stored delta runs
precisely when `Entity.canSimulateMovement` is **false** — it is the
not-authoritative branch, not a fallback inside the authoritative one;
- `Mob.serverAiStep` runs on `Entity.isEffectiveAi` **and** not client-side,
and `LivingEntity.travel` on `Entity.canSimulateMovement` **and**
`Entity.isEffectiveAi`;
- `Entity.applyEffectsFromBlocks` follows the travel fork, on the same
not-a-client-or-authoritative test as the step sound.
The last one has a fork in front of it. If the controlling passenger is a
`Player` and the mob is alive, `LivingEntity.travelRidden` runs instead —
and it has its own `Entity.canSimulateMovement` test, zeroing the delta
outright when it fails. That is the path every horse, pig and happy ghast
takes.
## What the predicates explain
**Why does a mob rubber-band and my own player does not?** Neither one is
being corrected, and for opposite reasons. Your client does simulate your own
player — and a boat or a horse you are riding, and a dropped item, which
consult no predicate at all — but it never simulates a tracked mob, so there
is nothing about the mob for the server to disagree with. What you see on a
mob is the gap between position packets, walked by `InterpolationHandler`.
**Why does a boat feel responsive and a horse feel heavy?** Both are ridden,
and both are authoritative on your machine — but a horse is a `LivingEntity`
going through `LivingEntity.travelRidden`, which asks the mob for its own
speed and drag, while a boat runs its own physics directly. The authority
answer is the same; the layer above it is not.
**Does *NoAI* stop a mob moving?** Completely, and by more than the obvious
route. It stops `Mob.serverAiStep`, because `Mob.isEffectiveAi` is what
`LivingEntity.aiStep` gates that call on — and the same predicate is the
second half of the gate on `LivingEntity.travel`, so nothing reaches
`Entity.move` for that mob either. A *NoAI* mob does not even fall.
## Where to look
`Entity.isLocalInstanceAuthoritative` · `Entity.isLocalClientAuthoritative` ·
`Entity.isClientAuthoritative` · `Entity.canSimulateMovement` ·
`Entity.isEffectiveAi` · `Player.isLocalPlayer` · `Mob.isNoAi` ·
`LivingEntity.aiStep` · `LivingEntity.travel` · `LivingEntity.travelRidden` ·
`Entity.move` · `Entity.checkFallDamage` · `Entity.doCheckFallDamage` ·
`ServerGamePacketListenerImpl.handleMovePlayer` ·
`ServerGamePacketListenerImpl.handleMoveVehicle` ·
`ClientPacketListener.handleEntityPositionSync` ·
`ClientPacketListener.handleMoveEntity` ·
`ClientPacketListener.handleMoveVehicle` · `InterpolationHandler` ·
`SweetBerryBushBlock.entityInside` · `AbstractBoat.floatBoat` ·
`AbstractBoat.controlBoat`
---
*Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes `tools/verify_names.py`.*
---
# Entity lifecycle
> Verified against **Minecraft 26.2** · Part VI · A zombie spawns in a dark chunk at night, is ticked for a while, and then either despawns or is written to disk when the chunk unloads.
Night falls, you are standing in a field, and somewhere behind you a zombie
appears. What the server actually did is smaller and stranger than it looks.
Once a tick, for each chunk near a player and each mob category still under
its cap, `NaturalSpawner.getRandomPosWithin` rolls a random x, a random z and
**one** y — a single uniform draw between the world bottom and the surface
height of that column. Three group attempts follow, and they jitter only x
and z. So the whole of one category's chance in one chunk this tick lives on
**one horizontal slice**: every eligible category gets its own slice, caves
and the open field compete for the same rolls, and a world with more vertical
space between bedrock and grass spreads those rolls thinner over the surface.
Everything else on this page — the caps, the light test, the despawn radius,
the write to disk — hangs off that one roll, or off the moment several
rejections later where the mob is finally allowed to exist.
## The cast
| class | what it decides | thread |
|---|---|---|
| `NaturalSpawner` | every test between a chunk and a mob, in one file of static methods | server main |
| `NaturalSpawner.SpawnState` | the per-tick census, the global cap per `MobCategory`, and the biome energy budget through `PotentialCalculator` | server main, rebuilt each tick |
| `LocalMobCapCalculator` | whether any player near *this* chunk is still under the per-player limit | server main, rebuilt each tick |
| `SpawnPlacements` | the placement type, heightmap and predicate for each `EntityType` — code, not data | a static table, read on the server main thread |
| `PersistentEntitySectionManager` | which entities exist, which are findable, which tick, which chunks are queued to unload | server main, with one concurrent load inbox |
| `Visibility` | the three-state projection of `FullChunkStatus` that everything above reads | an enum, read on both sides |
| `EntityTickList` | the set the tick walks, and the double buffer that makes mutating it mid-walk safe | server main, and the client's main thread for `ClientLevel` |
| `EntityStorage` | the *entities/* region files, separate from the block ones | reads on the IO pool, deserialises and writes on the server main thread |
## A spawn attempt is a filter, not a conversation
Almost every step of the spawner is a **rejection**. Drawing it as a
conversation hides that, so here it is as the cascade it is — top to bottom
in the order the code runs, with everything that can drop an attempt drawn as
an arrow leaving the path.
```mermaid
flowchart TD
T["ServerChunkCache.tickChunks, once a tick"] --> ST["NaturalSpawner.createState walks every entity in the level, skipping MISC and persistent mobs"]
ST --> CAT{"NaturalSpawner.getFilteredSpawningCategories"}
CAT -->|"a monster category, and the monster game rules are off"| X1["this category is dropped from the tick's list"]
CAT -->|"a persistent category, and game time is not a multiple of 400"| X1
CAT -->|"already at the global cap for the category"| X1
CAT -->|"eligible"| CH{"ChunkMap.collectSpawningChunks, then shuffled"}
CH -->|"no ticking chunk there"| X2["this chunk is skipped"]
CH -->|"ChunkMap.playerIsCloseEnoughForSpawning fails, meaning no non-spectator player within 128 blocks measured horizontally to the chunk centre"| X2
CH --> TICK{"ServerLevel.canSpawnEntitiesInChunk"}
TICK -->|"not entity-ticking, or outside the world border"| X2
TICK --> LOC{"LocalMobCapCalculator.canSpawn, per category"}
LOC -->|"every nearby player is at their own cap, or there is no nearby player at all"| X2
LOC --> POS["NaturalSpawner.getRandomPosWithin, a random x and z and ONE y between the world bottom and the surface"]
POS -->|"the roll landed at the very bottom"| X2
POS --> RC{"is the block at that position a redstone conductor?"}
RC -->|"yes, before any species is picked"| X2
RC -->|"no"| JIT["three group attempts, jittering only x and z, the y fixed"]
JIT --> NP{"a non-spectator player anywhere in the level?"}
NP -->|"no"| X3["this attempt is dropped"]
NP --> D{"NaturalSpawner.isRightDistanceToPlayerAndSpawnPoint"}
D -->|"within 24 blocks of the nearest player"| X3
D -->|"within 24 of the respawn point, and the respawn point is in this dimension"| X3
D -->|"jittered into a neighbouring chunk that cannot spawn"| X3
D --> PICK{"NaturalSpawner.getRandomSpawnMobAt, weighted, once per group"}
PICK -->|"the list is empty, or a reduced-water-ambient biome, 98 per cent of the time"| X3
PICK --> TY{"NaturalSpawner.isValidSpawnPostitionForType, the typo is Mojang's"}
TY -->|"the category is MISC"| X3
TY -->|"too far out for a type that cannot spawn far from a player"| X3
TY -->|"unsummonable, or the species is no longer in the list at this exact block"| X3
TY -->|"SpawnPlacements.isSpawnPositionOk fails on the placement type"| X3
TY -->|"SpawnPlacements.checkSpawnRules fails, and this is where the light test lives"| X3
TY -->|"the type's spawn box collides with the world"| X3
TY --> BUD{"NaturalSpawner.SpawnState.canSpawn, the biome energy budget"}
BUD -->|"over budget"| X3
BUD --> MAKE["EntityType.create, and ONLY NOW does a Mob object exist"]
MAKE -->|"feature-flagged off, or Peaceful and not allowed there"| X4["this category's attempt on this chunk returns"]
MAKE --> OBS{"Mob.checkSpawnRules and Mob.checkSpawnObstruction, on the real object"}
OBS -->|"either fails, or it would despawn instantly anyway"| X3
OBS --> FIN["Mob.finalizeSpawn, then addFreshEntityWithPassengers, then SpawnState.afterSpawn"]
```
The boundary that matters is the one marked **only now**. Everything above it
is decided against the `EntityType` — the placement type, the heightmap
([chunk anatomy](../world/chunk-anatomy.md)), the light rule, the collision
box — because constructing a mob to ask it costs
more than answering from the type. Nothing above that line has an object to
call a method on. `Monster.checkMonsterSpawnRules` is the light rule for a
zombie, and it hands the light half to `Monster.isDarkEnoughToSpawn`, three
tests in a row: sky light against a random draw from
zero to 31, then the dimension's `DimensionType.monsterSpawnBlockLightLimit`
if that limit is below 15, then the local brightness against a sample of
`DimensionType.monsterSpawnLightTest`. The last of those is where storms come
in: during thunder the brightness is computed with a fixed sky-darkening
of 10 instead of the level's current one, which is what lets monsters spawn
outdoors in the daytime. The light rule is per-dimension data,
not a constant, and `EntitySpawnReason.ignoresLightRequirements` exempts
exactly one reason, `EntitySpawnReason.TRIAL_SPAWNER`.
Construction is itself a filter, and the harshest-tempered one:
`EntityType.create` returns null when the type is feature-flagged off or the
difficulty is Peaceful and the type is not `EntityType.isAllowedInPeaceful`,
and `NaturalSpawner.spawnCategoryForPosition` answers a null by returning
outright — not by trying the next position. On Peaceful the spawner does all
the work up to construction and then abandons this category's attempt.
### The two caps, and where 289 comes from
A mob must pass both caps, and they are counted differently. The **global**
cap in `NaturalSpawner.SpawnState.canSpawnForCategoryGlobal` is
`MobCategory.getMaxInstancesPerChunk` — 70 for `MobCategory.MONSTER`, 10 for
`MobCategory.CREATURE` — times the number of spawnable chunks, divided by
`NaturalSpawner.MAGIC_NUMBER`. That divisor is 17², and the 17 is not
arbitrary: `DistanceManager` tracks spawn chunks out to eight chunks from
each player over a neighbourhood that includes diagonals, so one player
contributes a Chebyshev square of 17×17 chunks. The constant normalises the
cap back into *seventy monsters per player's worth of area*, which is why it
grows with player count and shrinks when players stand together.
The **local** cap is `LocalMobCapCalculator.canSpawn`, and it is a veto
rather than a budget: it walks the players near this chunk and answers yes
the moment it finds one under the raw per-chunk number for the category. With
no player near the chunk the walk finds nobody and the answer is **no**.
(`SharedConstants.DEBUG_IGNORE_LOCAL_MOB_CAP` is the development switch that
turns that half off.) The census both caps count from skips any mob that is
`Mob.isPersistenceRequired` or `Mob.requiresCustomPersistence` — named,
leashed or ridden — so a named zombie costs nothing against either cap. That
is the same predicate pair that makes `Mob.checkDespawn` return early, which
is why *name it and it stays* and *name it and it stops counting* are one
fact and not two.
### The four constants that are not the numbers
`NaturalSpawner` declares `NaturalSpawner.MIN_SPAWN_DISTANCE` 24,
`NaturalSpawner.SPAWN_DISTANCE_CHUNK` 8 and
`NaturalSpawner.SPAWN_DISTANCE_BLOCK` 128, and **not one of the three is read
anywhere in the game** — the live values are the literals 576.0 and 16384.0
at their use sites, both already squared. The two that *are* read are
`NaturalSpawner.MAGIC_NUMBER` and one more. That one,
`NaturalSpawner.INSCRIBED_SQUARE_SPAWN_DISTANCE_CHUNK`, is neither 8 nor 24:
it is the floor of 8 divided by the square root of two, so **5**, and
`DistanceManager.hasPlayersNearby` uses it as the fast *yes* of a three-way
answer — inside 5 chunks certainly near, beyond 8 certainly not, and in
between fall through to the real per-player distance test. Reading a name and
believing the number is how a page gets this wrong.
### What finalizeSpawn settles for the whole pack
`Mob.finalizeSpawn` adds a triangular random bonus to `Attributes.FOLLOW_RANGE`
under `Mob.RANDOM_SPAWN_BONUS_ID` and rolls a 5 % chance of left-handedness.
`Zombie.finalizeSpawn` then rolls loot-pickup and door-breaking against local
difficulty, equipment and its enchantments, and — the part players notice —
returns a `Zombie.ZombieGroupData` that the loop feeds back into the *next*
mob of the same group. Baby-or-adult is decided once, by the first zombie, and
inherited by the rest: a spawn group is all-baby or all-adult, never mixed. A
baby gets a 5 % roll at an existing unridden `Chicken` in a box five blocks
wide and three tall, and *only if that roll fails* a second 5 % roll to
create one.
Two different limits end it. `Mob.isMaxGroupSizeReached` breaks the current
group and lets the next of the three attempts start; `Mob.getMaxSpawnClusterSize`
returns outright and kills all three. The base value is four, and seven
species change it — horses to 6, fish and wolves to 8, and ghasts, happy
ghasts and pillagers **down** to 1.
## The other ways in
Natural spawning is one caller of `LevelWriter.addFreshEntity` among many.
`BaseSpawner` drives the `SpawnerBlockEntity` and `TrialSpawner` the trial
chambers ([block entities](../blocks/block-entities.md)); `SpawnEggItem` and
`SummonCommand` are the deliberate ones; `AgeableMob.getBreedOffspring` makes
babies; and five `CustomSpawner` implementations — `PhantomSpawner`,
`PatrolSpawner`, `CatSpawner`, `WanderingTraderSpawner` and `VillageSiege` —
are ticked as a list by `ServerLevel.tickCustomSpawners` after the chunks.
Each stamps one of the nineteen `EntitySpawnReason` constants, though not a
distinct one — phantoms and cats both count as *natural*, sieges and
wandering traders both as *event* — and that reason never leaves the server: nothing about *why* something spawned
crosses the wire.
## Entry: what addFreshEntity actually does
`LevelWriter.addFreshEntity` is a default method that returns **false**.
`Level` does not override it and neither does `ClientLevel`. Exactly two
classes do. `ServerLevel.addFreshEntity` is the one this page is about.
`WorldGenRegion.addFreshEntity` is the other, and it does something entirely
different: it writes the entity straight into the `ChunkAccess`'s own list and
never touches `PersistentEntitySectionManager` at all. That is the
`EntitySpawnReason.CHUNK_GENERATION` path — worldgen mobs are parked in the
proto-chunk as NBT and only enter the manager later, when the chunk is
promoted and `PersistentEntitySectionManager.addWorldGenChunkEntities` is
handed them ([the generation pipeline](../world/chunk-generation-pipeline.md)).
On the client the only way in is `ClientLevel.addEntity`, called from the
packet handler, and it begins by *removing* whatever already holds that
network id.
```mermaid
sequenceDiagram
participant SL as ServerLevel
participant PESM as PersistentEntitySectionManager
participant CM as ChunkMap
participant ETL as EntityTickList
participant Mob as Mob
participant ES as EntityStorage
participant Wire as the network
Note over SL: the tick it is created
SL->>SL: addFreshEntityWithPassengers walks getSelfAndPassengers, vehicle first
SL->>PESM: addNewEntity
PESM->>PESM: claim the UUID, put it in its EntitySection, install the Callback
PESM->>SL: LevelCallback.onCreated
PESM->>CM: startTracking, through ServerChunkCache.addEntity
CM->>Wire: ClientboundAddEntityPacket, bundled with data, attributes, equipment, passengers and leash
PESM->>ETL: startTicking, EntityTickList.add
Note over SL: every later tick
SL->>Mob: checkDespawn, for every entry in the tick list
SL->>Mob: tickNonPassenger, only when the chunk is in entity-ticking range
Note over PESM: the tick the chunk drops to hidden
PESM->>ETL: stopTicking, EntityTickList.remove
PESM->>CM: stopTracking
CM->>Wire: ClientboundRemoveEntitiesPacket
Note over PESM: some later PersistentEntitySectionManager.tick
PESM->>ES: storeEntities, and only then UNLOADED_TO_CHUNK
```
`ServerLevel.EntityCallbacks` is the class those callbacks land in — five of
`LevelCallback`'s seven appear in the figure — and
it is where a surprising amount of the level hangs: the scoreboard entry, the
players list and the sleeping-player recount, waypoint tracking, the
navigating-mob set the block-change notifier walks, the `EnderDragonPart` id
registrations, and the dynamic `DynamicGameEventListener` registration
([game events](../world/game-events-and-vibrations.md)).
## Findable, ticking, or neither
`Visibility` is the whole idea in three constants, and
`Visibility.fromFullChunkStatus` is the projection: `FullChunkStatus.FULL`
makes a chunk's entities findable, `FullChunkStatus.ENTITY_TICKING` makes them
tick, anything less hides them
([tickets and loading](../world/tickets-and-loading.md)).
```mermaid
stateDiagram-v2
state "Visibility.HIDDEN" as H
state "Visibility.TRACKED" as T
state "Visibility.TICKING" as K
[*] --> H
H --> T : chunk reaches FULL, startTracking adds it to EntityLookup and ChunkMap sends the ClientboundAddEntityPacket bundle
T --> K : chunk reaches ENTITY_TICKING, startTicking adds it to EntityTickList
K --> T : below ENTITY_TICKING, stopTicking removes it from EntityTickList
T --> H : below FULL, stopTracking sends ClientboundRemoveEntitiesPacket and the chunk key joins chunksToUnload
K --> H : straight down in one call, stopTicking first and stopTracking second
H --> [*] : a later manager tick writes the section and marks UNLOADED_TO_CHUNK
note right of H : hidden is not written yet. The client was told at the status change, the disk hears several ticks later.
```
The asymmetry is real and worth stating precisely.
`PersistentEntitySectionManager.updateChunkStatus` runs its four tests in a
fixed order — stop ticking, stop tracking, start tracking, start ticking — so
on the way **up** an entity becomes trackable before it becomes tickable, and
on the way **down** it stops ticking before it stops being tracked. That order
holds only on the chunk-status path. The other transition path, an entity
walking across a section boundary into a differently-statused section, runs
through `PersistentEntitySectionManager.Callback` instead, which does tracking
first in *both* directions and then ticking, and fires
`LevelCallback.onSectionChange` at the end. And the always-ticking exemption,
`Entity.isAlwaysTicking`, which lifts an entity clear of every one of those
filters, is claimed by exactly one class in 26.2: `Player`.
## The tick it gets, and the despawn check it gets anyway
The entity block of `ServerLevel.tick` is skipped in its entirety once the
level has gone 300 ticks without an active ticket. Otherwise the tick walks
`EntityTickList` and, for each entry that is neither removed nor frozen by
`TickRateManager.isEntityFrozen`, calls `Entity.checkDespawn` — whose
base implementation is *empty*, overridden only by `Mob`, `EnderDragon`,
`WitherBoss` and `ShulkerBullet`, so for an item or an arrow it is a no-op
call. Only **after** that does the range test run: a `ServerPlayer` is exempt
outright, everything else needs `DistanceManager.inEntityTickingRange` for its
own chunk, and an entity already riding a live vehicle returns here to be
ticked by `ServerLevel.tickPassenger` instead.
So despawn is checked for every member of the tick list while ticking is not.
The two sets should agree, and mostly do — the tick list *is* the ticking set
— but they read different sources: membership follows the chunk holder's
promoted `FullChunkStatus`, while the per-tick gate reads the simulation
tracker directly, and the two need not have converged in the same tick.
`EntityTickList` is what makes that walk safe. It holds two id-keyed maps and
a nullable reference to whichever one is being iterated. Adding or removing
during a walk copies the live map into the spare and **swaps** them, so the
in-flight iterator finishes over the original, untouched map and the mutation
lands in the new one. A second concurrent walk is refused outright, by
checking that reference rather than a boolean.
## Ending one: Mob.checkDespawn
Left in a loaded chunk, the zombie ends through `Mob.checkDespawn`, whose
first branch consults no player at all: on Peaceful, anything whose type is
not `EntityType.isAllowedInPeaceful` is discarded on the spot, ahead of even
the persistence check. Past that, a persistent mob has its
`LivingEntity.noActionTime` pinned to zero and is done.
Everything else is measured against the nearest non-spectator player — and if
there is no player in the level at all, both remaining branches do nothing, so
a mob alone in a world never despawns by distance. Beyond
`MobCategory.getDespawnDistance` — 128 blocks for every category except
`MobCategory.WATER_AMBIENT`, which is 64 — it is discarded instantly. Beyond
`MobCategory.getNoDespawnDistance`, a flat 32 for every category, it is
discarded on a 1-in-800 roll, but only once `LivingEntity.noActionTime` has
passed 600; inside that 32 the same method resets that counter to zero, so
standing near a mob keeps it alive. Both distance branches also require
`Mob.removeWhenFarAway`, the per-species veto — and it is broader than people
expect: `Animal` returns false for *every* animal, tamed or not, and
`Villager` for every villager, so a wild cow on a hilltop never despawns by
distance at all. That is why *128 blocks and it is gone* is a species-dependent
rule and not a universal one. What both branches
call is `Entity.discard`, which destroys and does not save.
## Ending two: the chunk goes away
Walk far enough instead and the chunk falls out of entity-ticking: the zombie
stops ticking but stays findable. Fall to `Visibility.HIDDEN` and two things
happen, several ticks apart. At the status change,
`PersistentEntitySectionManager.updateChunkStatus` stops ticking and stops
tracking the section's entities, and stopping tracking is what reaches
`ChunkMap` and sends `ClientboundRemoveEntitiesPacket` — the client is told
*then*, not at the write. The chunk key goes into the manager's unload set,
and some later `PersistentEntitySectionManager.tick` runs
`PersistentEntitySectionManager.processUnloads` over it.
That later step is not a formality, and it can refuse. A chunk whose entity
data is still being read back off disk is deferred to a future tick. A chunk
that has entities to save but has *never been read* is **loaded first**, so
the two sets can be merged — the unload triggers a load. Only then does
`EntityStorage.storeEntities` write the *Entities* list and a *Position* into
the *entities/* region files ([chunk storage](../world/chunk-storage.md)),
which are separate from the block *region/* files and which remember the
chunks that came back empty so they are never re-read. Each saved entity and
its passengers then take `Entity.RemovalReason.UNLOADED_TO_CHUNK` and drop
their level callback.
Two rules decide what is in that file. Passengers are written **inside** their
vehicle, never beside it, so `Entity.shouldBeSaved` refuses any entity that is
riding something; and a vehicle whose passengers are exactly one player is
refused too, because it travels in that player's own data instead. The clause
that is easy to miss is the first one in the method: an entity already
carrying a non-saving removal reason is skipped, which is what keeps a
discarded mob still sitting in a section out of the file.
## Five reasons, one label
| reason | destroys | saves | what leaves it behind |
|---|---|---|---|
| `Entity.RemovalReason.KILLED` | yes | no | death, in every sense the game means it |
| `Entity.RemovalReason.DISCARDED` | yes | no | `Entity.discard`, every despawn, the client replacing a network id |
| `Entity.RemovalReason.UNLOADED_TO_CHUNK` | no | **yes** | the unload above — the only reason that saves |
| `Entity.RemovalReason.UNLOADED_WITH_PLAYER` | no | no | a vehicle travelling inside a player's own save data |
| `Entity.RemovalReason.CHANGED_DIMENSION` | no | no | a portal, where the entity is rebuilt on the far side |
*Destroys* means `LevelCallback.onDestroyed` fires — the scoreboard entry and
the waypoint go. An unloading zombie keeps all of it, because
`Entity.RemovalReason.UNLOADED_TO_CHUNK` does not destroy. The five are not a
state machine: `Entity.setRemoved` writes the reason **only if none is set**,
so the first one wins and a second call cannot change it — though the rest of
`Entity.setRemoved` still runs, dropping passengers and firing
`EntityInLevelCallback.onRemove` with the *new* reason. It drops its passengers unconditionally, but dismounts the entity
from its own vehicle only when the reason destroys. The one link that survives
a removal by design is an `EntityReference` held by somebody else — it keeps a
UUID, upgrades to the object on first resolution, and falls back to the UUID
when the target goes, which is how *who last hurt me* survives a chunk unload.
## Where to look
`NaturalSpawner.spawnCategoryForPosition` · `NaturalSpawner.SpawnState` ·
`LocalMobCapCalculator` · `ChunkMap.collectSpawningChunks` ·
`SpawnPlacements` · `EntitySpawnReason` · `Mob.finalizeSpawn` ·
`Mob.checkDespawn` · `MobCategory` · `ServerLevel.addFreshEntity` ·
`PersistentEntitySectionManager.updateChunkStatus` · `Visibility` ·
`EntityLookup` · `EntitySection` · `EntitySectionStorage` · `LevelCallback` ·
`EntityInLevelCallback` · `EntityTickList` · `ServerLevel.tickNonPassenger` ·
`Level.guardEntityTick` · `EntityStorage` · `Entity.RemovalReason` ·
`Entity.setRemoved` · `TransientEntitySectionManager`
Before this page: [authority](authority.md), on which side is allowed to
decide any of it. After it: [synched entity data](synched-entity-data.md) —
what the `ClientboundAddEntityPacket` bundle above is carrying, and how it
stays current.
---
*Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes `tools/verify_names.py`.*
---
# Synched entity data
> Verified against **Minecraft 26.2** · Part VI · A player shears a sheep: one byte flips on the server, and the wool disappears on every screen in tracking range.
You right-click a sheep with shears. Somewhere on the server a single byte
changes — bit four of one entry in a nineteen-slot array the sheep carries —
and before that tick ends, every player watching has been told, by a packet
that spends four bytes after the entity id. That array is `SynchedEntityData`,
the channel the server uses to describe an entity to the clients that see it: the
health bar over another player, a sneaking crouch, an armour stand's pose,
an item frame's item, this sheep's wool. It is a numbered array, and the
numbers are the surprising part. **The slot the wool lives in is not written
anywhere in `Sheep`: it is 18 because eighteen slots were handed out above
`Sheep` in its superclass chain, and one new field on `Entity` would renumber
every entity in the game.** Ids are ordinals handed out down the class tree
by a single shared `ClassTreeIdRegistry`, which walks only the *superclass*
chain: `Entity` takes 0 to 7, `LivingEntity` 8 to 14, `Mob` 15, `AgeableMob`
16 and 17, and `Sheep`, last in the chain, gets 18.
Nothing names these numbers, nothing writes them down, and they stop at 254
— because on the wire, 255 means *end of packet*.
## The cast
| class | what it decides | thread |
|---|---|---|
| `SynchedEntityData` | one entity's numbered array, and whether anything in it has changed since the last flush | one container per side, confined to that side's main thread |
| `SynchedEntityData.DataItem` | one slot: its value, the default it was built with, and its own dirty flag | with its container |
| `EntityDataAccessor` | the key — an int id and a serializer, equal to another accessor **on the id alone** | immutable, shared by every instance of the class |
| `ClassTreeIdRegistry` | which id a `SynchedEntityData.defineId` call gets, from the last id already taken by an ancestor class | whichever thread first loads the class |
| `EntityDataSerializers` | the 43 registered serializers and the wire id of each, in registration order | a static block, once |
| `ServerEntity` | whether this entity sends anything this tick, and what | the server main thread |
| `ClientboundSetEntityDataPacket` | the wire form: an entity id, then id/serializer/value triples, then 255 | encoded on the Netty pipeline, built on the server thread |
| `ClientPacketListener` | applying an incoming batch to the client's own container | the client main thread |
## Nineteen slots, and where the numbers come from
`SynchedEntityData.defineId` is called from the static initialiser of an
entity class and asks `ClassTreeIdRegistry.define` for a number.
`ClassTreeIdRegistry` keeps one map from class to *last id issued*, and
`ClassTreeIdRegistry.getLastIdFor` walks up the superclass chain until it
finds an entry — so a subclass continues its parent's numbering rather than
starting over. Java's guarantee that a superclass initialises before its
subclass is the entire ordering mechanism. `ClassTreeIdRegistry.getCount` is
the same walk plus one, and it is what sizes the array.
A `Sheep` — `Entity` → `LivingEntity` → `Mob` → `PathfinderMob` →
`AgeableMob` → `Animal` → `Sheep`, of which `PathfinderMob` and `Animal`
define nothing — therefore has exactly nineteen slots:
| id | field | serializer | default |
|---:|---|---|---|
| 0 | `Entity.DATA_SHARED_FLAGS_ID` | `EntityDataSerializers.BYTE` | 0 |
| 1 | `Entity.DATA_AIR_SUPPLY_ID` | `EntityDataSerializers.INT` | `Entity.getMaxAirSupply` |
| 2 | `Entity.DATA_CUSTOM_NAME` | `EntityDataSerializers.OPTIONAL_COMPONENT` | empty |
| 3 | `Entity.DATA_CUSTOM_NAME_VISIBLE` | `EntityDataSerializers.BOOLEAN` | false |
| 4 | `Entity.DATA_SILENT` | `EntityDataSerializers.BOOLEAN` | false |
| 5 | `Entity.DATA_NO_GRAVITY` | `EntityDataSerializers.BOOLEAN` | false |
| 6 | `Entity.DATA_POSE` | `EntityDataSerializers.POSE` | `Pose.STANDING` |
| 7 | `Entity.DATA_TICKS_FROZEN` | `EntityDataSerializers.INT` | 0 |
| 8 | `LivingEntity.DATA_LIVING_ENTITY_FLAGS` | `EntityDataSerializers.BYTE` | 0 |
| 9 | `LivingEntity.DATA_HEALTH_ID` | `EntityDataSerializers.FLOAT` | 1.0 |
| 10 | `LivingEntity.DATA_EFFECT_PARTICLES` | `EntityDataSerializers.PARTICLES` | empty |
| 11 | `LivingEntity.DATA_EFFECT_AMBIENCE_ID` | `EntityDataSerializers.BOOLEAN` | false |
| 12 | `LivingEntity.DATA_ARROW_COUNT_ID` | `EntityDataSerializers.INT` | 0 |
| 13 | `LivingEntity.DATA_STINGER_COUNT_ID` | `EntityDataSerializers.INT` | 0 |
| 14 | `LivingEntity.SLEEPING_POS_ID` | `EntityDataSerializers.OPTIONAL_BLOCK_POS` | empty |
| 15 | `Mob.DATA_MOB_FLAGS_ID` | `EntityDataSerializers.BYTE` | 0 |
| 16 | `AgeableMob.DATA_BABY_ID` | `EntityDataSerializers.BOOLEAN` | false |
| 17 | `AgeableMob.AGE_LOCKED` | `EntityDataSerializers.BOOLEAN` | false |
| 18 | `Sheep.DATA_WOOL_ID` | `EntityDataSerializers.BYTE` | 0 |
The declaration order and the *definition* order are two different lists.
Ids come from where `SynchedEntityData.defineId` sits in the class body; the
values come from the `Entity` constructor, which defines its own eight and
then calls the abstract `Entity.defineSynchedData` that every subclass
overrides and chains up through. `LivingEntity` defines its seven in a
different order from the one that numbered them, and it does not matter,
because `SynchedEntityData.Builder.define` writes each item at
`EntityDataAccessor.id`. `SynchedEntityData.Builder.build` then refuses to
hand over a container with any slot still null, naming the id it is missing —
which is why `SynchedEntityData.get` can index the array with no bounds check
at all.
Four of the nineteen are bitfields, and they are the dense part of the
channel. Slot 0 is `Entity.FLAG_ONFIRE` 0, `Entity.FLAG_SHIFT_KEY_DOWN` 1,
`Entity.FLAG_SPRINTING` 3, `Entity.FLAG_SWIMMING` 4,
`Entity.FLAG_INVISIBLE` 5, `Entity.FLAG_GLOWING` 6 and
`Entity.FLAG_FALL_FLYING` 7 — bit 2 is unnamed and unused — behind
`Entity.getSharedFlag` and `Entity.setSharedFlag`, whose parameter carries a
purpose-built `Entity.Flags` type-use annotation that every call site inside
`Entity` ignores in favour of a bare integer. Slot 8 carries *using an item*,
*off hand* and *spin attack* (`LivingEntity.LIVING_ENTITY_FLAG_IS_USING`,
`LivingEntity.LIVING_ENTITY_FLAG_OFF_HAND`,
`LivingEntity.LIVING_ENTITY_FLAG_SPIN_ATTACK`), slot 15 no-AI, left-handed
and aggressive behind `Mob.setNoAi`, `Mob.setLeftHanded` and
`Mob.setAggressive`, and slot 18 packs a `DyeColor` id into the low nibble
and *sheared* into bit four — `Sheep.getColor`, `Sheep.setColor`,
`Sheep.isSheared`, `Sheep.setSheared`, and the same storage read out as
`DataComponents.SHEEP_COLOR` by `Sheep.get` and written back through
`Sheep.applyImplicitComponent`.
The numbering belongs to the class, not to the concept. `Avatar` — the class
26.2 inserts between `LivingEntity` and `Player` — owns
`Avatar.DATA_PLAYER_MAIN_HAND` and `Avatar.DATA_PLAYER_MODE_CUSTOMISATION`,
so the skin-part toggles belong to every avatar, while
`Player.DATA_PLAYER_ABSORPTION_ID`, `Player.DATA_SCORE_ID` and the two
shoulder parrots (`Player.DATA_SHOULDER_PARROT_LEFT`,
`Player.DATA_SHOULDER_PARROT_RIGHT`, optional ints rather than NBT) sit one
level further down.
## The serializer is the other half of the key
An `EntityDataAccessor` is an id *and* an `EntityDataSerializer`, and the
serializer is what turns the value into bytes. The interface is deliberately
thin: a `StreamCodec` returned by `EntityDataSerializer.codec` — returned,
not extended — paired with an `EntityDataSerializer.copy` that defends the
container against a caller mutating a value it already handed over.
`EntityDataSerializer.ForValueType` is the immutable case, where copying is
identity, and `EntityDataSerializer.forValueType` builds one from a codec
alone.
`EntityDataSerializers` registers 43 of them into a
`CrudeIncrementalIntIdentityHashBiMap`, from a single static block, so
**registration order is the wire id** — `EntityDataSerializers.BYTE` is 0,
`EntityDataSerializers.POSE` is 20, `EntityDataSerializers.HUMANOID_ARM` is
42 and last. Several of them carry `Holder`s of data-pack registries — the
per-species variants, painting variants, resolvable profiles — which is why
the buffer on both sides is a `RegistryFriendlyByteBuf` rather than a plain
one ([codecs](../foundations/codecs-nbt-json.md)). The full list with wire
ids and value types is [the serializer table](../../reference/entity-data-serializers.md).
`EntityDataSerializers.registerSerializer` is public and is the only thing
that fills the bimap: vanilla calls it 43 times from that one block, and
nothing else in the tree ever calls it again. It is a mod extension point
shipped with no caller outside its own file.
## The trace: a sheep is sheared
```mermaid
sequenceDiagram
participant MPGM as MultiPlayerGameMode
participant SGPL as ServerGamePacketListenerImpl
participant Sheep as Sheep
participant SED as SynchedEntityData
participant CM as ChunkMap
participant SE as ServerEntity
participant CPL as ClientPacketListener
MPGM->>MPGM: predicts locally with Player.interactOn, unless spectator
MPGM->>SGPL: ServerboundInteractPacket(entity id, hand, relative location, secondary)
Note over SGPL: server tick, before MinecraftServer.tickServer runs
SGPL->>SGPL: Entity.setShiftKeyDown from the packet flag, then range and border checks
SGPL->>Sheep: Player.interactOn to Entity.interact to Mob.interact
Sheep->>Sheep: checkAndHandleImportantInteractions, then Entity.interact, then Sheep.mobInteract
Sheep->>Sheep: Sheep.shear — sound, then dropFromShearingLootTable, then setSheared
Sheep->>SED: SynchedEntityData.set(Sheep.DATA_WOOL_ID, bit four set)
SED->>Sheep: Entity.onSyncedDataUpdated, then DataItem.setDirty and isDirty
Note over SGPL,SE: same tick, ServerLevel.tick chunkSource phase
CM->>SE: ChunkMap.tick reaches this sheep, ServerEntity.sendChanges
SE->>SED: isDirty opens the gate, then SynchedEntityData.packDirty
SED-->>SE: one DataValue — id 18, serializer 0, one payload byte
SE->>CPL: ClientboundSetEntityDataPacket, queued now and flushed at the end of the tick
Note over SED: one container per side — the server's above, the client's below
CPL->>SED: handleSetEntityData to assignValues, per item then the batch
Note over CPL: next frame — SheepRenderer.extractRenderState reads Sheep.isSheared
```
**The click.** `MultiPlayerGameMode.interact` sends a
`ServerboundInteractPacket` — a flat record of entity id, hand, an
*entity-relative* hit location and the secondary-action flag, attacks having
left for `ServerboundAttackPacket` — and *then*, on the next line, runs the
interaction locally as a prediction, unless the local game mode is spectator.
The packet goes first.
**The server checks the geometry, not the outcome.**
`ServerGamePacketListenerImpl.handleInteract` confirms the thread with
`PacketUtils.ensureRunningOnSameThread` and that the client has loaded,
resolves the entity with `ServerLevel.getEntityOrPart`, tests the world
border and `Player.isWithinEntityInteractionRange`, and checks the held item
against the level's feature flags. Before any of the geometry, though, it
writes the packet's secondary-action flag straight into
`Entity.setShiftKeyDown` — which is itself a synched-data write on the
*player*, so every interaction packet is also a potential update on slot 0.
**Dispatch down the hierarchy.** `Player.interactOn` calls `Entity.interact`,
which dispatches virtually to the most derived override, `Mob.interact`.
That runs three things in a fixed order: `Mob.checkAndHandleImportantInteractions`
(name tags, spawn eggs), then the superclass hook `Entity.interact` with its
leashing branch, and only if that passes, `Sheep.mobInteract` — the base
hook runs *between* the two mob hooks, not before them. The shears test is
item identity against `Items.SHEARS`, not a tag and not a component, plus
`Sheep.readyForShearing` from the shared `Shearable` interface, whose other
implementors are `MushroomCow`, `SnowGolem`, `Bogged`, `CopperGolem` and
`SulfurCube`. A sheep that is *not* ready returns `InteractionResult.CONSUME`
rather than falling through, which is why shears on an already-sheared sheep
do nothing visible at all.
**The effect, and one byte.** `Sheep.shear` plays `SoundEvents.SHEEP_SHEAR`,
drops wool through `LivingEntity.dropFromShearingLootTable` with
`BuiltInLootTables.SHEAR_SHEEP` — the tool is passed in, so the loot table
can see it — and calls `Sheep.setSheared`, which ors bit four into slot 18.
`SynchedEntityData.set` compares the new value against the current one,
stores it, calls `Entity.onSyncedDataUpdated` for that accessor, and *then*
marks the item and the container dirty. `Sheep` does not override the hook,
and the base implementation reacts to exactly one accessor, `Entity.DATA_POSE`,
by calling `Entity.refreshDimensions`. Back in `Sheep.mobInteract`:
`Entity.gameEvent` with `GameEvent.SHEAR` for the sculk listeners
([game events](../world/game-events-and-vibrations.md)),
`ItemStack.hurtAndBreak` on the shears, and `InteractionResult.SUCCESS_SERVER`.
**The send, in the same tick.** `MinecraftServer.processPacketsAndTick`
drains the queue with `PacketProcessor.processQueuedPackets` and only then
calls `MinecraftServer.tickServer`, so the shear happened before the tick
proper began. `ServerLevel.tick` reaches its *chunkSource* phase after
block and fluid ticks and before block events and entity ticking, and that
phase runs `ChunkMap.tick`, whose loop over `ChunkMap.TrackedEntity` is the
only caller of `ServerEntity.sendChanges` in the tree. The dirty flag opens
the gate, `ServerEntity.sendDirtyEntityData` calls `SynchedEntityData.packDirty`,
and one `ClientboundSetEntityDataPacket` goes to every tracking player and
to the entity itself. After the entity id, the wire carries an unsigned byte
18, a var-int 0 for `EntityDataSerializers.BYTE`, one payload byte and then
the terminator 255 — and that terminator is the whole reason ids stop at
254. Both the pack and the unpack side write and test the literal,
incidentally, so the public `ClientboundSetEntityDataPacket.EOF_MARKER` is
referenced by nothing, exactly like the private
`SynchedEntityData.MAX_ID_VALUE` beside it.
The packet is not on the wire yet. `MinecraftServer.tickChildren` calls
`ServerCommonPacketListenerImpl.suspendFlushing` on every player before it
ticks any level and `ServerCommonPacketListenerImpl.resumeFlushing` at the
end of the tick, so a whole tick's packets leave together.
**The apply, and the frame.** `ClientPacketListener.handleSetEntityData`
looks the entity up in `ClientLevel` and silently drops the entire packet if
the id is unknown, then calls `SynchedEntityData.assignValues`, which checks
that the incoming serializer is the one the accessor was defined with — a
mismatch throws, loudly, on the client — stores each value, fires
`Entity.onSyncedDataUpdated` per item and then the batch overload once.
Nothing tells the renderer. Next frame `LevelExtractor` walks the visible
entities, `EntityRenderDispatcher.extractEntity` builds a `SheepRenderState`,
and `SheepRenderer.extractRenderState` copies `Sheep.isSheared` and
`Sheep.getColor` into it, after which `SheepWoolLayer` draws nothing. The
wool vanishing is a *layer skipped*, not a model swap — and only one layer,
because `SheepWoolUndercoatLayer` tests colour, baby and invisibility but
never the sheared flag, so a sheared coloured sheep still draws its undercoat.
## The gate that holds a packet back
```mermaid
flowchart TD
IN["ChunkMap.tick: section changed, or Entity.needsSync, or the chunk is in entity-ticking range"] --> SC["ServerEntity.sendChanges, opening with Entity.updateDataBeforeSync"]
SC -->|"an ItemFrame, every tenth tick — the map bypass"| DATA["ServerEntity.sendDirtyEntityData"]
SC --> GATE{"tickCount is a multiple of EntityType.updateInterval, or Entity.needsSync, or SynchedEntityData.isDirty"}
GATE -->|"yes"| SEND["position, rotation and motion"] --> DATA
GATE -->|"no"| HOLD["nothing goes out, and the dirty flags survive to the next tick"]
```
Two tests stand between a dirty byte and the wire. `ChunkMap.tick` decides
whether `ServerEntity.sendChanges` is called at all; an entity that is
tracked but outside entity-ticking range, and not moving between sections,
simply is not asked, and keeps its dirty data until one of the three
conditions becomes true. Inside `ServerEntity.sendChanges`, the interval gate
covers the position block *and* the usual
`ServerEntity.sendDirtyEntityData` call, which is why shearing a sheep also
sends that sheep's position delta this tick: synched data is, incidentally,
a latency channel for movement.
The interval comes from `EntityType.updateInterval`, fixed when
`ChunkMap.TrackedEntity` constructs the `ServerEntity`. `EntityTypes.PLAYER`
sets 2 and `EntityType.Builder` defaults to 3. Thirty-seven types set the
interval explicitly, most of them at 10 or 20 — and seven of those, item
frames, paintings, leash knots and their kin, set *Integer.MAX_VALUE*.
**Integer.MAX_VALUE** — the update interval of `EntityTypes.ITEM_FRAME`,
which is to say its interval branch never fires again after tick zero.
That is exactly why `ServerEntity.sendChanges` has an `ItemFrame` special
case that calls `ServerEntity.sendDirtyEntityData` every tenth tick *before*
the gate: it is the only path to the synched-data flush that skips the
interval test, and without it a map in a frame would update only when
something else set `Entity.needsSync`. (Two *sends* also sit outside the
gate — the passengers packet and the `Entity.hurtMarked` motion packet — but
neither touches the data channel.)
`ServerEntity.handleMinecartPosRot` calls it too, but from inside the gate,
not around it. `Entity.syncPosition` is the other lever: it
realigns the tracker's own counter so the very next evaluation lands on a
multiple of the interval, which works even when that interval is
*Integer.MAX_VALUE*.
`Entity.updateDataBeforeSync` opens `ServerEntity.sendChanges`, ahead of the
gate, and it is the hook `LivingEntity` overrides to reconcile its effects:
`LivingEntity.updateInvisibilityStatus` and the glowing status write slot 0,
and the swirl list goes into slot 10. A mob effect that expired this tick can
therefore dirty the container and open its own gate, in the same call that
goes on to read the flag.
## Five more channels, all keyed by the same entity id
Synched data is one of six clientbound descriptions of an entity, and
knowing which one a fact travels on answers most *why does the client not
know that* questions. There is no serverbound counterpart to any of them.
The client cannot write to the channel directly — though one of its packets
does move two slots by proxy: `ServerboundClientInformationPacket` reaches
`ServerPlayer.updateOptions`, which sets the skin-customisation byte and the
main hand.
| channel | packets | note |
|---|---|---|
| synched data | `ClientboundSetEntityDataPacket` | to trackers **and self** — `ServerEntity.Synchronizer.sendToTrackingPlayersAndSelf` |
| attributes | `ClientboundUpdateAttributesPacket` | flushed by the same `ServerEntity.sendDirtyEntityData` ([attributes](attributes.md)) |
| equipment | `ClientboundSetEquipmentPacket` | incremental updates bypass `ServerEntity` entirely — `LivingEntity.handleEquipmentChanges` sends them, to trackers only, not the wearer |
| mob effects | `ClientboundUpdateMobEffectPacket`, `ClientboundRemoveMobEffectPacket` | the authoritative list, unlike the swirl in `LivingEntity.DATA_EFFECT_PARTICLES` |
| position and motion | `ClientboundMoveEntityPacket`, `ClientboundEntityPositionSyncPacket`, `ClientboundRotateHeadPacket`, `ClientboundSetEntityMotionPacket`, `ClientboundMoveMinecartPacket` | the block the synched-data gate shares; `ClientboundTeleportEntityPacket` is the exception, sent from `Entity` itself |
| one-shot events | `ClientboundEntityEventPacket` | a single byte from `ServerLevel.broadcastEntityEvent`, dispatched by `Entity.handleEntityEvent` — `EntityEvent` declares 62 of them |
Pairing a new viewer runs the same machinery once. `ServerEntity.addPairing`
calls `ServerEntity.sendPairingData`, which bundles `ClientboundAddEntityPacket`
with a `ClientboundSetEntityDataPacket`, attributes, equipment, passengers and
a leash link into one `ClientboundBundlePacket`. The data packet is built not
from a fresh pack but from `ServerEntity.trackedDataValues`, a snapshot taken
in the `ServerEntity` constructor from `SynchedEntityData.getNonDefaultValues`
and refreshed only when `SynchedEntityData.packDirty` later returns something. Two consequences:
an entity still entirely at its defaults sends **no** data packet on pairing
at all, and any change made while the tracker sat outside its send gate is
already folded into that cache.
## Questions players ask
**Why is a freshly loaded entity described by so little?** Because defaults
never travel. `SynchedEntityData.getNonDefaultValues` skips any item still
equal to its `SynchedEntityData.DataItem.initialValue`, so pairing describes
only what has changed. Both sides construct their own defaults independently;
if they ever disagreed, no packet would correct it.
**If a value changes twice in a tick, do I get two packets?** No — and not
always for the reason you would guess. `SynchedEntityData.packDirty` clears
each flag as it packs, so one flush carries one value per slot. But the
comparison in `SynchedEntityData.set` is against the *current* value, not
the last one sent: setting A then B then A within a tick dirties the item
twice and then sends A, a value the client already had. The only thing that
never dirties is setting a slot to what it already holds — and even that can
be overridden, because `SynchedEntityData.set` has a three-argument
force-dirty form that skips the comparison entirely. `Display` uses it to
restart an interpolation whose delay was re-set to the same number;
`CopperGolem`'s two uses of it are redundant, because the value it writes is
always the previous weather state and so always different.
**Why does my client-side change never reach the server?** Nothing stops
`SynchedEntityData.set` on the client — `LocalPlayer` prediction does it
constantly — but `ServerEntity.sendDirtyEntityData` is the only caller of
`SynchedEntityData.packDirty` in the whole tree. The client's dirty flag is read by no one, and
the next server value overwrites the slot ([authority](authority.md)). The
container is not useless to the client, though: `ClientPacketListener.handleRespawn`
copies the old player's non-default values straight into the new one, the
single client-to-client use of the channel.
**Can two mods both add a field to `LivingEntity`?** Only by accident. Ids
are ordinals, so inserting, removing or reordering a `SynchedEntityData.defineId` on a base
class shifts every id below it in every subclass. Two mods claiming the same
number either collide in `SynchedEntityData.Builder.define`, which rejects a
duplicate, or land a value in the wrong slot and throw the serializer check
on the client. `SynchedEntityData.Builder.define` also has an off-by-one in
its bounds test — an id exactly equal to the array length slips past and dies
on the array write instead.
**Does any of this affect the physics the client simulates?** One value does.
`Entity.DATA_POSE` travels on its own `EntityDataSerializers.POSE`, and
`Entity.onSyncedDataUpdated` turns an incoming pose into
`Entity.refreshDimensions` — a synched value resizing a hitbox on both sides.
Everything else on the channel is cosmetic to the client, or read back by
gameplay code that already knew.
Two details that are only visible from the whole tree. The batch overload
`SyncedDataHolder.onSyncedDataUpdated`, fired by
`SynchedEntityData.assignValues` after every packet, is overridden by nothing
in 7,055 classes — it is the only place a client could see a whole update
atomically, and it is dead. And `Display` is the one class that treats
accessor ids as *values*: `Display.RENDER_STATE_IDS` is an int set of the
eight ids that force a render-state rebuild, tested against each incoming
accessor, with `Display.TextDisplay` keeping a second set of its own. It is
the sharpest demonstration in the codebase that these numbers really are
ordinals.
## Where to look
`SynchedEntityData` · `SynchedEntityData.Builder` ·
`SynchedEntityData.DataItem` · `SynchedEntityData.DataValue` ·
`EntityDataAccessor` · `EntityDataSerializer` · `EntityDataSerializers` ·
`ClassTreeIdRegistry` · `Entity.defineSynchedData` ·
`Entity.onSyncedDataUpdated` · `Entity.updateDataBeforeSync` ·
`ServerEntity.sendChanges` · `ServerEntity.sendDirtyEntityData` ·
`ServerEntity.sendPairingData` · `ChunkMap.tick` · `ChunkMap.TrackedEntity` ·
`ClientboundSetEntityDataPacket` · `ClientPacketListener.handleSetEntityData` ·
`Sheep.mobInteract` · `Shearable`
---
*Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes `tools/verify_names.py`.*
---
# Attributes
> Verified against **Minecraft 26.2** · Part VI · Strength II is applied to a player: one modifier lands on one attribute, nothing goes on the wire, and the swing three seconds later reads the new number.
Strength II lands on you and thirty seconds later it wears off. In between,
one `AttributeModifier` — an amount of +6, an operation of
`AttributeModifier.Operation.ADD_VALUE`, an id of *effect.strength* — sits on
one attribute of one entity, and every swing you make asks for that attribute
and gets a bigger number back. The same mechanism is behind armour points, a
horse's jump strength, the extra reach of a creative-mode player and the
distance at which a mob notices you: *ask a question, get a number*, cheaply,
with a defined order of operations. What is surprising is what does not
happen. **Strength II sends no packet at all.** Eight of the forty registered
attributes are not client-syncable and `Attributes.ATTACK_DAMAGE` is one of
them, so for the whole thirty seconds your own client's copy of your attack
damage sits at the base value it was born with — 1.0 — and nothing ever tells
it otherwise.
> **A different system with the same words.** `world/attribute` is
> *environment* attributes — per-position world properties like sky darkness
> ([environment attributes and
> timelines](../world/environment-attributes-and-timelines.md)) — with its own
> registries and its own class also named `AttributeModifier`. Nothing on this
> page refers to it.
## The cast
| class | what it decides | thread |
|---|---|---|
| `Attribute` | one named number's default, its description id, its `Attribute.Sentiment` (tooltip colour only) and the one boolean that decides whether the client is ever told | built in the `Attributes` class initialiser, read from every thread after |
| `RangedAttribute` | the minimum and the maximum, and `RangedAttribute.sanitizeValue` — the clamp. The only subclass, and every registered attribute is one | as above |
| `AttributeSupplier` | what attributes an `EntityType` has at all, and their base values. Frozen | built at class-init, inside `DefaultAttributes` |
| `AttributeMap` | which of two dirty sets a change lands in, and therefore whether a packet is sent | server main thread for mutations, client main thread for the mirror |
| `AttributeInstance` | the number: a base value, three modifier indices, a dirty flag and a cache | as above |
| `AttributeModifier` | an `Identifier`, an amount and an operation. A record, and the identifier alone is its identity | immutable, shared |
| `LivingEntity` | when the update set drains, and what reacts to a change | both sides, in `LivingEntity.tick` |
| `ServerEntity` | when the sync set drains and what goes on the wire | server main thread, in the level tick's *chunkSource* phase |
## Five objects, two dirty sets, one filter
```mermaid
flowchart TB
ATTR["Attribute, always a RangedAttribute: a default, a minimum, a maximum, a sentiment, and one boolean called syncable"]
SUP["AttributeSupplier: one frozen prototype map per EntityType, held in DefaultAttributes"]
MAP["AttributeMap: one per LivingEntity, holding only the instances something has asked for"]
INST["AttributeInstance: a base value, a cached value and a dirty flag"]
BYOP["modifiersByOperation: three buckets. What calculateValue walks"]
BYID["modifierById: the identity index, and the duplicate check"]
PERM["permanentModifiers: the subset AttributeMap.pack writes to disk"]
UPD["attributesToUpdate: every dirtied attribute"]
SYNC["attributesToSync: only the syncable ones"]
REACT["LivingEntity.refreshDirtyAttributes in the entities phase, calling onAttributeUpdated, then clear"]
SEND["ServerEntity.sendDirtyEntityData in the chunkSource phase, then clear"]
WIRE["ClientboundUpdateAttributesPacket to every tracking player and the entity itself"]
PAIR["AttributeMap.getSyncableAttributes: NOT a dirty set. It filters the whole live map, for ServerEntity.sendPairingData"]
ATTR -- "registered once, by the class initialiser of Attributes" --> SUP
SUP -- "createInstance copies a prototype into a fresh instance" --> MAP
MAP --> INST
INST --> BYOP
INST --> BYID
INST --> PERM
INST -- "setDirty calls AttributeMap.onAttributeModified, which always adds here" --> UPD
INST -- "and additionally here, only if the attribute is syncable" --> SYNC
UPD --> REACT
SYNC --> SEND
SEND --> WIRE
MAP -. "a newly tracking player gets this instead" .-> PAIR
PAIR -.-> WIRE
```
The two sets are **not a partition**: `AttributeMap.onAttributeModified`
always adds to the update set and *additionally* to the sync set when the
attribute is syncable, so a syncable attribute is in both and a non-syncable
one is in the update set alone.
## Forty numbers, every one of them clamped
The forty constants of `Attributes` register themselves in their own static
field initialisers, into `BuiltInRegistries.ATTRIBUTE` under
`Registries.ATTRIBUTE`. `Attributes.bootstrap` does nothing but return
`Attributes.MAX_HEALTH`, and exists only as the class-loading trigger
`BuiltInRegistries` needs — which is also why a data pack can *reference* an
attribute but never add one.
Every one of the forty is a `RangedAttribute`, and `RangedAttribute` is the
only subclass of `Attribute`, so every attribute in the game has a minimum
and a maximum and every computed value passes through one clamp. Several of
those bounds are the reason a mechanic behaves the way it does:
`Attributes.MAX_HEALTH` has a minimum of 1, so no entity's maximum health can
reach zero, and `Attributes.KNOCKBACK_RESISTANCE` has a minimum of −2, so
*amplified* knockback is a legal value rather than a bug. The full list — id,
constant, default, minimum, maximum, syncable, sentiment — is
[the attribute table](../../reference/attributes.md).
What has to be said here is the syncable flag, because it explains most of
what a client and a server disagree about. **Eight** of the forty never reach
the client: `Attributes.ATTACK_DAMAGE`, `Attributes.ATTACK_KNOCKBACK`,
`Attributes.KNOCKBACK_RESISTANCE`, `Attributes.FOLLOW_RANGE`,
`Attributes.TEMPT_RANGE`, `Attributes.SPAWN_REINFORCEMENTS_CHANCE` — whose
registry id is *spawn_reinforcements*, disagreeing with its own constant name
— and the pair `Attributes.WAYPOINT_TRANSMIT_RANGE` and
`Attributes.WAYPOINT_RECEIVE_RANGE`. An attribute is syncable only because
its registration line called `Attribute.setSyncable`, and that setter is
public and has no freeze behind it, on an object that lives in a registry.
Nothing calls it after bootstrap. Nothing stops it either.
## The prototype, frozen at class-init
`DefaultAttributes` holds one `AttributeSupplier` per `EntityType`, each
built by chaining builders: `LivingEntity.createLivingAttributes` is the
twenty-six-entry base, `Mob.createMobAttributes` adds
`Attributes.FOLLOW_RANGE` at 16, `Monster.createMonsterAttributes` adds
`Attributes.ATTACK_DAMAGE` and nothing else, and each species' own builder
finishes the job. `AttributeSupplier.Builder.build` keeps the **last** entry
for a repeated attribute, which is how `Zombie.createAttributes` overrides
the attack damage the monster builder added, the follow range the mob builder
added and the follow range the mob builder added — the movement speed it
also declares has no earlier entry to beat. Anything
outside `MobCategory.MISC` with no supplier at all is logged by
`DefaultAttributes.validate`.
A prototype is frozen in the sense that `AttributeSupplier.Builder.build`
arms a callback that throws on any later write: reading one is fine, dirtying
one throws. Reading an attribute the type does not have is also fatal — the
fallback in `AttributeSupplier` raises rather than returning a default, which
is why `LivingEntity.getAttribute` is nullable and
`LivingEntity.getAttributeValue` is not.
`Player.createAttributes` is on `Player`, not on `Avatar`. That intermediate
class ([entity anatomy](entity-anatomy.md)) owns the player-shaped hitbox and
the skin data but not the attribute set, so `Mannequin` — the other `Avatar` —
is registered with `LivingEntity.createLivingAttributes` and gets the plain
living set, including the registry's default movement speed of 0.7 rather
than a player's 0.1. The default is not a dead value: the wandering trader,
the phantom and the slime are registered with the bare `Mob` and `Monster`
builders, neither of which sets a speed either.
## The map, and which set a change lands in
`AttributeMap` starts empty. `AttributeMap.getValue` and
`AttributeMap.getBaseValue` answer from the prototype when the entity has no
instance of its own, and change nothing. `AttributeMap.getInstance` is
different: it creates the instance on demand through
`AttributeSupplier.createInstance`, which copies the frozen template with
`AttributeInstance.replaceFrom` — which ends in `AttributeInstance.setDirty`.
So **asking for an instance is a mutation**: the first call to
`LivingEntity.getAttribute` for a syncable attribute enqueues it for
broadcast before any modifier exists, and a share of the attribute packets a
busy server sends are caused by something merely asking.
The two sets drain in different phases of the same tick, and that is where
the visible lag comes from. `ServerEntity.sendDirtyEntityData` is reached
from `ChunkMap.tick`, which runs inside `ServerLevel.tick`'s *chunkSource*
phase — **before** the *entities* phase ([the level
tick](../server/server-level-tick.md)). An attribute dirtied during an
entity's own tick (equipment, an effect, sprinting, powder snow, anything in
`ServerPlayer.updatePlayerAttributes`) has therefore already missed this
tick's send — and a dirty *attribute* set is not one of the three things that
open `ServerEntity.sendChanges`'s gate, so it waits for the next tick whose
count is a multiple of the entity's update interval: the tick after next for a
player, the third for the default. Only a mutation made *before* the
level tick — a command, an interaction handled out of the packet queue at the
top of the server tick — reaches the wire in the tick that produced it. It is
the same phase ordering that puts a block entity's writes a tick late
([block entities](../blocks/block-entities.md)).
The update set drains in the entities phase, in
`LivingEntity.refreshDirtyAttributes`, which calls
`LivingEntity.onAttributeUpdated` once per dirtied attribute and then clears
the set. That hook has exactly four branches: clamp health down to a reduced
maximum health, clamp absorption, call `Entity.refreshDimensions` on a scale
change, and register or unregister the transmitted waypoint with the
`ServerWaypointManager`. Subclasses add two more —
`ServerPlayer.onAttributeUpdated` takes the *receive* half of the waypoint
pair, and `Mob.onAttributeUpdated` recomputes the pathfinder's node budget
through `PathNavigation.updatePathfinderMaxVisitedNodes` on a change to
`Attributes.FOLLOW_RANGE` **or** `Attributes.TEMPT_RANGE`
([pathfinding](pathfinding.md) owns that budget).
`LivingEntity.refreshDirtyAttributes` is called from `LivingEntity.tick` with
no side check, so the **client** runs `LivingEntity.onAttributeUpdated` too,
clamping health and resizing an entity whose scale changed — which is why the
waypoint branch inside it is the one that has to test for a `ServerLevel`
explicitly.
Two more things the map decides, and between them they answer *why did my
`/attribute` change survive death?* `AttributeMap.pack` writes **every
instantiated instance**, base value included, so a base value set by command
persists with no modifier attached to carry it — `AttributeInstance.pack`
writes only the permanent modifiers of each. And on respawn,
`ServerPlayer.restoreFrom` always calls `AttributeMap.assignBaseValues` but
calls `AttributeMap.assignPermanentModifiers` only on a *full* restore:
returning from the End, not an ordinary death. Base values always come
across, a command-added modifier only sometimes.
## The instance: three indices and one cached number
An `AttributeInstance` keeps its modifiers three times over: bucketed by
`AttributeModifier.Operation` for the arithmetic, indexed by `Identifier` for
lookup and duplicate detection, and a second id-index of the *permanent* ones
for saving. A modifier's identity is its `Identifier` alone — there is no
UUID and no name — so two systems that pick the same identifier for the same
attribute collide, and `AttributeInstance.addTransientModifier` and
`AttributeInstance.addPermanentModifier` **throw** rather than silently
overwrite. `AttributeInstance.addOrUpdateTransientModifier` and
`AttributeInstance.addOrReplacePermanentModifier` are the safe forms. Most of
vanilla removes by id before it adds; three mobs and `AttributeCommand`
instead guard with `AttributeInstance.hasModifier` before adding.
Transient versus permanent is *purely* about saving: both kinds sit in the
same indices, both affect the value identically, both go on the wire, and
only the permanent ones are packed. Mob-effect modifiers are added
permanently, and that is the only reason they survive a reload — effects are
restored from NBT straight into the active list without going through the
apply path, so the hook that would add the modifier never runs on load. On
the client, meanwhile, *every* modifier is transient, because
`ClientPacketListener.handleUpdateAttributes` sets the base value, wipes the
whole modifier list and re-adds the incoming ones with
`AttributeInstance.addTransientModifier`. A client attribute map is never
packed and never persisted.
`AttributeInstance.getValue` recomputes through
`AttributeInstance.calculateValue` only when the dirty flag is set, and the
flag starts true, so the first read always computes — three passes and one
clamp:
```mermaid
flowchart TB
B["base value: the prototype's, or one assigned by AttributeMap.assignBaseValues or by the attribute command"]
P1["pass 1: add the amount of every ADD_VALUE modifier"]
P2["pass 2: for each ADD_MULTIPLIED_BASE modifier, add the post-pass-1 base times its amount. Each reads the same base, so these do NOT compound"]
P3["pass 3: for each ADD_MULTIPLIED_TOTAL modifier, multiply the running total by one plus its amount. Each reads the last one's output, so these DO compound"]
C["RangedAttribute.sanitizeValue, once: NaN collapses to the minimum, anything else is clamped between the minimum and the maximum"]
O["cachedValue, returned unchanged until the next setDirty"]
B --> P1 --> P2 --> P3 --> C --> O
```
Operation order is therefore global, not insertion order, and intermediate
values are never clamped. Within a bucket, iteration order is a hash map's —
safe only because each bucket's arithmetic is commutative.
### …except in the other implementation, which is insertion-ordered
`ItemAttributeModifiers.compute` is a second, disagreeing implementation of
the same idea. It walks an item's `ItemAttributeModifiers.Entry` list in
declaration order, applying each entry's operation to the running total as it
goes, with no three-pass grouping at all. It is not a duplicate of
`AttributeInstance.calculateValue` and it does not agree with it. Its one
caller in the whole game is `Mob.getApproximateAttributeWith` — the "would
this weapon be better than the one I am holding?" estimate a mob makes when
deciding whether to pick an item up.
## Where the modifiers come from
Equipment is the busiest source. `LivingEntity.detectEquipmentUpdates`, in
the server-only half of `LivingEntity.tick`, only dispatches;
`LivingEntity.collectEquipmentChanges` does the work, adding each incoming
stack's modifiers as *transient* (removing by id first) through
`ItemStack.forEachModifier`, which merges
`DataComponents.ATTRIBUTE_MODIFIERS` with the enchantment modifiers from
`EnchantmentHelper.forEachModifier`. Exactly eight vanilla enchantments carry
`EnchantmentEffectComponents.ATTRIBUTES` — fire and blast protection,
respiration, aqua affinity, depth strider, swift sneak, sweeping edge,
efficiency — and `EnchantmentAttributeEffect` has a second, location-based
path through `EnchantmentAttributeEffect.onChangedBlock` that exactly one
uses: soul speed, registered under
`EnchantmentEffectComponents.LOCATION_CHANGED` instead.
The rest, in one breath: `MobEffect` declarations, permanently and scaled by
amplifier; item components, whose `ItemAttributeModifiers.Display` decides
whether *and how* a tooltip line appears — its default form adds the reader's
own base value back in for `Item.BASE_ATTACK_DAMAGE_ID` and
`Item.BASE_ATTACK_SPEED_ID`, so a weapon shows a total rather than a bonus;
`SetAttributesFunction` in loot tables; `Zombie.handleAttributes` at spawn;
`LivingEntity.setSprinting`; `ServerPlayer.updatePlayerAttributes`; and
`AttributeCommand`, whose *modifier add* is **permanent**, and so saved.
One packet, one direction: `ClientboundUpdateAttributesPacket`, server to
every tracking player **and to the entity itself** — which is why your own
client has a live attribute map at all. At most 128 attributes fit in one
(checked on encode as well as decode), each snapshot carrying the attribute
holder, the base value and the complete, uncapped modifier list. There is no
serverbound attribute packet.
## The trace: Strength II
```mermaid
sequenceDiagram
participant EffC as EffectCommands
participant LE as LivingEntity
participant ME as MobEffect
participant AttrM as AttributeMap
participant AttrI as AttributeInstance
participant SE as ServerEntity
EffC->>LE: addEffect(Strength, amplifier 1)
LE->>LE: onEffectAdded, guarded server-side
LE->>ME: addAttributeModifiers(the map, amplifier 1)
ME->>AttrM: getInstance(Attributes.ATTACK_DAMAGE)
AttrM->>AttrI: createInstance copies the frozen prototype, replaceFrom ends in setDirty
AttrI-->>AttrM: onAttributeModified adds it to attributesToUpdate
ME->>AttrI: removeModifier(effect.strength), then addPermanentModifier(+6, ADD_VALUE)
AttrI-->>AttrM: setDirty again. ATTACK_DAMAGE is not syncable, so attributesToSync stays empty
Note over LE,SE: the next server tick: chunkSource phase, then entities phase
SE-->>SE: sendDirtyEntityData finds an empty set and sends nothing
LE->>LE: refreshDirtyAttributes drains the update set, onAttributeUpdated matches no branch
Note over LE,AttrI: three seconds later, inside Player.attack
LE->>AttrM: getAttributeValue(Attributes.ATTACK_DAMAGE)
AttrM->>AttrI: getValue, dirty, so calculateValue
AttrI-->>LE: 1.0 base plus the sword's base_attack_damage plus 6.0
```
`MobEffects.STRENGTH` is declared with one attribute modifier: +3 on
`Attributes.ATTACK_DAMAGE`, `AttributeModifier.Operation.ADD_VALUE`, under
the id *effect.strength*, and the amount is multiplied by amplifier + 1 when
the modifier is built — so Strength II is **+6**. (`MobEffects.WEAKNESS` is
the same construction at −4.) `LivingEntity.addEffect` puts the instance into
the active list and calls `LivingEntity.onEffectAdded`, which is guarded
server-side and hands the entity's `AttributeMap` to
`MobEffect.addAttributeModifiers`. If the effect was already running, the
path is `LivingEntity.onEffectUpdated` instead, which removes and re-adds and,
unlike the add path, refreshes the dirty attributes on the spot.
`MobEffect.addAttributeModifiers` calls `AttributeMap.getInstance`, and that
is where the instance for `Attributes.ATTACK_DAMAGE` is born, copying the
frozen prototype's base value: 1.0 for a player, 3.0 for a zombie. Creation
dirties it, before any modifier exists. Then the effect removes its own id
and adds the +6 with `AttributeInstance.addPermanentModifier` — the remove is
not optional, since the plain add throws on a duplicate id — and dirties it
again. Both times the callback adds to the update set and consults the
syncable flag before touching the sync set, so the sync set stays empty and
**no packet is sent at all**. Next tick, `ServerEntity.sendDirtyEntityData`
finds nothing to send and `LivingEntity.onAttributeUpdated` matches none of
its four branches.
Three seconds later `Player.attack` asks `LivingEntity.getAttributeValue` for
the attack damage. The instance is dirty, so `AttributeInstance.calculateValue`
runs: the addition bucket holds the sword's *base_attack_damage* modifier
from `DataComponents.ATTRIBUTE_MODIFIERS` and the effect's +6, the two
multiplication buckets are empty, the clamp passes, the result is cached. The
rest of the swing — cooldown scale, enchantment bonuses, the crit — belongs
to [the sword swing](../player/the-sword-swing.md). Expiry runs it backwards:
`LivingEntity.onEffectsRemoved` calls `MobEffect.removeAttributeModifiers`,
which removes by id from all three indices and dirties one last time.
### Swap Strength for Speed and the missing limb appears
`MobEffects.SPEED` targets `Attributes.MOVEMENT_SPEED` with
`AttributeModifier.Operation.ADD_MULTIPLIED_TOTAL`, and movement speed *is*
syncable. Everything above is identical until the callback, which now fills
the sync set too. `ServerEntity.sendDirtyEntityData` drains it in the next
chunkSource phase and emits a `ClientboundUpdateAttributesPacket` carrying
the base value and **every** modifier on that attribute — sprinting, powder
snow, the effect, all of them — and `ClientPacketListener.handleUpdateAttributes`
rebuilds the client's instance from scratch. That difference, one boolean on
the `Attribute`, is the whole design.
## Questions players ask
**Why does a frozen mob flood the network?** `LivingEntity.aiStep` calls
`LivingEntity.removeFrost` and `LivingEntity.tryAddFrost` back to back,
server-side, with no test for whether anything changed. Each has a gate — the
remove only dirties when the modifier is actually there, the add needs a
non-air block underfoot *and* a non-zero frozen counter — but when both hold,
the pair destroys and re-creates a modifier on `Attributes.MOVEMENT_SPEED`,
dirtying a *syncable* attribute twenty times a second and re-sending that
entity's whole movement-speed modifier list for as long as it stays frozen.
Compare `ServerPlayer.updatePlayerAttributes`, which runs just as often but
uses `AttributeInstance.addOrUpdateTransientModifier` with a constant
modifier object, and so dirties nothing after the first tick.
**Why does the client show the wrong number for a mob?** Because for eight
attributes it was never told, and for the rest it was told a tick or more
late. The client is authoritative about none of it: it reads its own
`Attributes.MOVEMENT_SPEED` in `AbstractClientPlayer.getFieldOfViewModifier`
and its own reach through `Player.blockInteractionRange` from whatever the
last packet left in the map ([movement](movement-and-collision.md)).
## Where to look
`Attributes` · `Attribute.setSyncable` · `RangedAttribute.sanitizeValue` ·
`DefaultAttributes` · `LivingEntity.createLivingAttributes` ·
`AttributeSupplier.Builder.build` · `AttributeSupplier.createInstance` ·
`AttributeMap.getInstance` · `AttributeMap.onAttributeModified` ·
`AttributeMap.getAttributesToSync` · `AttributeMap.getAttributesToUpdate` ·
`AttributeMap.getSyncableAttributes` · `AttributeMap.pack` ·
`AttributeInstance.replaceFrom` · `AttributeInstance.setDirty` ·
`AttributeInstance.calculateValue` · `ItemAttributeModifiers.compute` ·
`LivingEntity.collectEquipmentChanges` · `MobEffect.addAttributeModifiers` ·
`LivingEntity.refreshDirtyAttributes` · `LivingEntity.onAttributeUpdated` ·
`ServerEntity.sendDirtyEntityData` · `ServerEntity.sendPairingData` ·
`ClientboundUpdateAttributesPacket` ·
`ClientPacketListener.handleUpdateAttributes` · `AttributeCommand`
---
*Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes `tools/verify_names.py`.*
---
# Movement and collision
> Verified against **Minecraft 26.2** · Part VI · One tick of a falling zombie: 0.08 of gravity, one swept box against a stone floor, and the four booleans everything downstream reads.
A zombie is two blocks above stone with nothing pushing it sideways. Its
tick builds one delta vector, hands it to `Entity.move`, gets back the part
of it the world allowed, and sets four booleans from the difference. Then it
has to answer a harder question: *what did I just walk through?* It does not
answer that by sampling the destination. Every segment of the tick's
movement was recorded into a deque, `Entity.movementThisTick`, and
`Entity.applyEffectsFromBlocks` replays those segments afterwards — in the
same axis order the collision used, visiting every block the swept box
actually crossed, `AABB.collidedAlongVector` rather than a static overlap.
And the effects that replay finds are not applied where they are found: they
are queued into an `InsideBlockEffectApplier.StepBasedCollector` and flushed
in `InsideBlockEffectType` declaration order, so fire and water touched in
the *same* step of the replay always end in the extinguish, whatever order
the blocks came in.
## The cast
| class | what it decides | thread |
|---|---|---|
| `Entity` | the geometry: clipping, stepping up, bouncing, whether you are on the ground and what you are standing on | server main, or client main for whoever is authoritative |
| `LivingEntity` | the physics above it: gravity, drag, friction, swimming, gliding, climbing | same |
| `CollisionGetter` | which blocks are candidates, through `BlockCollisions`, and which one is holding you up | same |
| `Shapes` / `VoxelShape` | the clipping arithmetic, one axis at a time | same |
| `Entity.Movement` | one recorded segment — from, to, and the pre-collision vector that fixes the replay's axis order | same |
| `InsideBlockEffectApplier.StepBasedCollector` | when a block effect actually happens, and in what order | same |
| `EntityFluidInteraction` | the once-per-tick snapshot of water and lava height, eye depth and current | same |
| `ServerEntity` | whether this tick's new position costs a short delta or an absolute sync | server main, in the broadcast phase |
## Who is allowed to run this at all
A tracked mob is simulated on the server and merely *carried* on the client
— nothing in its own tick reaches `Entity.move`, though a piston or a shulker
box can still shove it there from a block entity — while a player is the
other way up, client-authoritative on both sides, so the client simulates for
real and the server re-runs it as a check. The predicate that decides is
`Entity.isLocalInstanceAuthoritative` rather than a bare "am I the client",
and `Entity.canSimulateMovement` and `Entity.isEffectiveAi` default to it —
though several of the gates below are written as *not a client **or**
authoritative*, and `Player` overrides both predicates to exactly that. Which
call site reads which, and why a mob and a player invert, is
[authority](authority.md); this page notes each gate where the trace hits it.
## The tick
```mermaid
sequenceDiagram
participant SL as ServerLevel
participant LE as LivingEntity
participant Entity as Entity
participant CG as CollisionGetter
participant Shapes as Shapes
participant Block as Block
participant SE as ServerEntity
SL->>Entity: tickNonPassenger — setOldPosAndRot, bump tickCount, tick
Entity->>Entity: baseTick — updateFluidInteraction snapshots water and lava, lava halves fallDistance
LE->>LE: aiStep — coast-or-interpolate, deadzone, applyInput, serverAiStep
LE->>LE: travel picks travelInAir (not in fluid, not gliding)
LE->>Entity: moveRelative at 0.02 of flying speed, then move(SELF, deltaMovement)
Entity->>CG: collide — getEntityCollisions, the world border, getBlockCollisions
CG->>Shapes: per candidate getCollisionShape, a full cube short-circuits, else joinIsNotEmpty
Entity->>Shapes: collideWithShapes — axisStepOrder, Y first, then the larger horizontal
Shapes-->>Entity: the allowed vector, y clipped by the stone floor
Entity->>Entity: Movement recorded, setPos, then the collision booleans
Entity->>CG: setOnGroundWithMovement — findSupportingBlock names the block
Entity->>Block: checkFallDamage — fallOn, then GameEvent.HIT_GROUND
Entity->>Entity: restitution, step sound, block speed factor
LE->>LE: back in travelInAir — subtract 0.08 of gravity, then the drags
LE->>Entity: applyEffectsFromBlocks — replay the deque, flush the collector
LE->>LE: pushEntities — cramming check, then doPush
Note over SL,SE: the next tick, in the chunkSource phase, before the entity loop runs again
SL->>SE: sendChanges — a short Pos delta, or an absolute sync because onGround changed
```
## Building the delta
`Entity.baseTick` clears the cached block state, records whether the eyes
were in water, and calls `Entity.updateFluidInteraction` — one sweep that
fills the water and lava trackers of `EntityFluidInteraction` with their
heights and accumulated current. Everything downstream — `Entity.isInWater`,
`Entity.isInLava`, `Entity.getFluidHeight`, `Entity.isEyeInFluid` — reads
that snapshot and never the live world. Fire ticks after it, lava *halves*
`Entity.fallDistance` rather than clearing it, and `Entity.checkBelowWorld`
discards anything 64 below the world floor — except a `LivingEntity`, which
overrides the hook and takes four points of *fell out of the world* damage a
tick instead.
`LivingEntity.aiStep` is the order of every mob's tick and worth memorising:
interpolate-or-coast, head turn, equipment, a deadzone that zeroes any delta
component under 0.003 (a squared-horizontal test instead, for players),
`LivingEntity.applyInput`, `Mob.serverAiStep` — the goal selector and the
movement control, which set `LivingEntity.xxa` and `LivingEntity.zza`
([AI](ai-goals-and-brains.md), [pathfinding](pathfinding.md)) — the jump
branch, gliding, the travel branch, `Entity.applyEffectsFromBlocks`,
animation, freezing, `LivingEntity.pushEntities`. Our zombie's jump branch
is skipped before `Entity.onGround` is ever consulted, because the branch is
gated on `LivingEntity.jumping` and a falling zombie is not asking to jump.
The travel branch is a fork, not a call. If the controlling passenger is a
`Player` and the mob is alive it is `LivingEntity.travelRidden` — the path
every horse, pig and happy ghast takes, and the reason a ridden mob's input
comes from `LivingEntity.getRiddenInput` rather than its own AI. Otherwise,
and only if both `Entity.canSimulateMovement` and `Entity.isEffectiveAi`
hold, it is `LivingEntity.travel`, which picks one of three:
`LivingEntity.travelInFluid` (splitting again into
`LivingEntity.travelInWater` and `LivingEntity.travelInLava`),
`LivingEntity.travelFallFlying` — the elytra model, lift from the square of
the pitch cosine — or `LivingEntity.travelInAir`.
`LivingEntity.shouldTravelInFluid` picks the first, and note what it reads:
the *cached* in-water and in-lava flags, with the live `FluidState` at the
block position used only for `LivingEntity.canStandOnFluid`, which is how a
strider walks on lava.
`LivingEntity.travelInAir` probes the block below through
`Entity.getBlockPosBelowThatAffectsMyMovement` — 0.500001 down — for its
friction: airborne, 1.0, and on stone `Block.getFriction`'s 0.6 through
`LivingEntity.computeModifiedFriction`. For an airborne entity
`LivingEntity.getFrictionInfluencedSpeed` then returns
`LivingEntity.getFlyingSpeed`: **0.02** for a mob nobody is riding, which is
why you have almost no air control — a literal, not
`Attributes.FLYING_SPEED`, which is not even in the base living attribute
set. `Entity.moveRelative` rotates the input by the yaw and adds it to
`Entity.deltaMovement` through `Entity.setDeltaMovement`, which silently
discards the whole vector if it is not finite, so NaN never enters the
physics state.
Every knob on the entity's side is a syncable attribute
([attributes](attributes.md)): `Attributes.GRAVITY` (0.08),
`Attributes.STEP_HEIGHT` (0.6), `Attributes.MOVEMENT_SPEED` (0.7),
`Attributes.JUMP_STRENGTH` (0.42), `Attributes.SAFE_FALL_DISTANCE` (3.0),
`Attributes.FALL_DAMAGE_MULTIPLIER`, `Attributes.MOVEMENT_EFFICIENCY`,
`Attributes.WATER_MOVEMENT_EFFICIENCY`, `Attributes.AIR_DRAG_MODIFIER`,
`Attributes.FRICTION_MODIFIER`, `Attributes.BOUNCINESS`. The world's half is
four block properties ([blocks and states](../blocks/blocks-and-states.md)):
| property | default | who changes it |
|---|---|---|
| `Block.getFriction` | 0.6 | 0.98 on ice, packed ice and `Blocks.FROSTED_ICE`, 0.989 on blue ice, 0.8 on `Blocks.SLIME_BLOCK` |
| `Block.getSpeedFactor` | 1.0 | 0.4 on soul sand and honey |
| `Block.getJumpFactor` | 1.0 | 0.5 on honey |
| `Block.getBounceRestitution` | 0.0 | 1.0 on `Blocks.SLIME_BLOCK`, 0.75 on beds |
`MoverType` names who is moving you, in five constants. `MoverType.PISTON` is
the one with real machinery — `Entity.limitPistonMovement` collapses the
vector to a single axis, `Entity.applyPistonMovementRestriction` clamps it to
±0.51 per game tick, and that path alone is exempt from the *multiply* by
`Entity.stuckSpeedMultiplier` — it still clears the field
([pistons](../blocks/pistons-and-block-events.md)). `MoverType.SHULKER_BOX`
makes a `Shulker` teleport rather than move, and `MoverType.SELF` and
`MoverType.PLAYER` are read together by `Player.maybeBackOffFromEdge` ([input
to movement](../player/input-to-movement.md)).
## Resolving one axis at a time
`Entity.move` opens with two things that are easy to miss.
`Entity.stuckSpeedMultiplier` is applied to the delta and *cleared* in the
same breath, zeroing `Entity.deltaMovement` with it — that pair is the whole
cobweb, berry-bush and powder-snow model — and `Entity.noPhysics` is an escape hatch above
even it: an entity with it set skips collision entirely and has all four
booleans cleared.
```mermaid
flowchart TD
COLLIDE["Entity.collide"]
GATHER["collect the colliders: every entity box, the world border if you are near it, then BlockCollisions over the swept box"]
RESOLVE["Entity.collideWithShapes"]
AXIS["Direction.axisStepOrder — Y first, always, then the larger horizontal axis, then the smaller. Each axis clips the box already displaced by the earlier ones"]
TEST{"step height above zero, colliding horizontally, and on or hitting the ground?"}
FLAT["return the flat result"]
HEIGHTS["Entity.collectCandidateStepUpHeights — every Y face of every candidate shape inside maxUpStep, sorted ascending"]
RETRY["retry the whole resolve at the next candidate height"]
MORE{"any more horizontal distance than the flat attempt?"}
WIN["return that one, minus the drop back to the old floor"]
COLLIDE --> GATHER --> RESOLVE --> AXIS --> TEST
TEST -- no --> FLAT
TEST -- yes --> HEIGHTS --> RETRY --> MORE
MORE -- "no, try the next candidate" --> RETRY
MORE -- "no candidates left" --> FLAT
MORE -- yes --> WIN
```
Two things in the gathering stage surprise people. The first is that
**collision is against shapes, not blocks**: a candidate contributes
whatever `BlockBehaviour.BlockStateBase.getCollisionShape` says, which for a
fence is 1.5 blocks tall — to walk into *and* to stand on. The 1.0 you see
outlined is `BlockBehaviour.BlockStateBase.getShape`, the selection box, and
`CrossCollisionBlock` builds the two from different heights. The mover only
ever asks for the first. The second is that most entities are not colliders
at all: `EntityGetter.getEntityCollisions` wraps with `Shapes.create` the
box of every entity that answers `Entity.canBeCollidedWith`, and the base
class answers **false** — so the mob standing next to you contributes
nothing, while boats, living shulkers and happy ghasts do. (Pushing is a different
predicate, `Entity.isPushable`, and belongs to the crowding pass below.)
`BlockCollisions` walks the box with a `Cursor3D`, reads chunks through
`CollisionGetter.getChunkForCollisions` — a full chunk if it is already
there, null otherwise, and a missing chunk is simply stepped past, so an
entity at the edge of loaded space falls through empty space rather than
blocking the tick. A full cube short-circuits to a box intersection;
anything else goes through `Shapes.joinIsNotEmpty`.
The step-up loop in the figure is the part worth slowing down for. It does
not guess a height and it does not pick the best one. It harvests the Y
coordinates of the candidate shapes that lie above the entity's feet and
within `Entity.maxUpStep`, skipping the height the flat attempt already
tried, sorts them
ascending, and retries the *whole* resolve at each until one yields any more
horizontal distance than the flat attempt — and returns that one. It is the
lowest step that helps, which is also why an entity can step onto a shape's
internal ledge and not only its top face. `Entity.maxUpStep` is zero on the
base class and `LivingEntity.maxUpStep` reads `Attributes.STEP_HEIGHT`,
raised to at least 1.0 when a `Player` is riding — which is how a ridden
horse climbs a full block.
## What the move reports back
Before committing, one clip: if `Entity.fallDistance` is non-zero and the
allowed movement is at least a block long, `Entity.move` casts a ray up to
eight blocks along it for `BlockTags.FALL_DAMAGE_RESETTING` and resets the
fall distance on any hit. Then an `Entity.Movement` record — from, to, and
the pre-collision delta — goes onto `Entity.movementThisTick`, and
`Entity.setPos` moves the point and the bounding box together.
The four booleans are then computed by comparing what was asked with what
was allowed: `Mth.equal` on the two horizontals, but **exact** inequality on
Y, and the whole vertical block only runs if the entity moved vertically at
all or is authoritative. `Entity.onGround` is therefore a comparison and not
a raycast — it is set from `Entity.verticalCollisionBelow`, meaning the
vertical component was clipped and it was negative. The only geometric probe
is `CollisionGetter.findSupportingBlock`, reached through
`Entity.setOnGroundWithMovement` and `Entity.checkSupportingBlock`, and it
answers *which* block is holding you (for sounds and the speed factor), not
*whether* — probing a paper-thin slab under the box, retrying with the box
shifted back along the movement if that finds nothing, and setting
`Entity.onGroundNoBlocks` when it still does.
`Entity.checkFallDamage` runs next, only when this instance is authoritative.
It adds the downward movement to `Entity.fallDistance` and, on landing, calls
`Block.fallOn`, posts `GameEvent.HIT_GROUND` and resets the distance.
`Block.fallOn` is what calls `LivingEntity.causeFallDamage`,
`LivingEntity.calculateFallPower` subtracts
`Attributes.SAFE_FALL_DISTANCE` and `LivingEntity.calculateFallDamage`
multiplies by `Attributes.FALL_DAMAGE_MULTIPLIER` and checks
`EntityTypeTags.FALL_DAMAGE_IMMUNE` ([damage](damage-and-death.md)). Our
two-block fall is 2 − 3 < 0, so the power is zero and **nothing at all**
happens: the landing particles are gated on a positive power and the fall
sound on positive damage, so only the game event and the reset fire. That
reset is reached from more places than you would guess — landing, entering
water in `Entity.updateFluidInteraction`, climbing in
`LivingEntity.handleOnClimbable`, every `LivingEntity.rideTick`, under
`MobEffects.SLOW_FALLING` or `MobEffects.LEVITATION` at the top of the
travel branch, `Entity.makeStuckInBlock`, and the tag clip above. Lava
halves it instead.
Then `Entity.restituteMovementAfterCollisions`, gated on
`Entity.canSimulateMovement`: a real restitution model, not slime-block
code. It reflects the horizontal components, and for a downward hit combines
`Attributes.BOUNCINESS` with `Block.getBounceRestitution`, damped to 80% for
non-living entities, gated on the impact being at least one tick of gravity
— which is why nothing jitters at rest on a slime block — and opted out of
by `BlockTags.SUPPRESSES_BOUNCE` or by crouching. A bounce posts
`GameEvent.BOUNCE` ([game events](../world/game-events-and-vibrations.md))
and sets `Entity.syncPosition`. `Entity.applyMovementEmissionAndPlaySound`
follows, gated on *not client-side or authoritative*: it accumulates
`Entity.moveDist` and fires `Entity.playStepSound` plus `GameEvent.STEP`
when it passes `Entity.nextStep`. Last, the horizontal components are
multiplied by `Entity.getBlockSpeedFactor` — soul sand's 0.4, lerped towards
1 by `Attributes.MOVEMENT_EFFICIENCY`.
## And then gravity
Control returns to `LivingEntity.travelInAir`, *after* the move, and only
now is gravity subtracted: `Entity.getEffectiveGravity`, 0.08, or capped at
0.01 while falling with `MobEffects.SLOW_FALLING`. `MobEffects.LEVITATION`
replaces that step entirely rather than modifying it, and a client-side
entity standing over an unloaded chunk gets a hard-coded −0.1. The
horizontals are then multiplied by block friction times a 0.91 scaled by
`Attributes.AIR_DRAG_MODIFIER`, the vertical by a 0.98 scaled by the same —
`Attributes.FRICTION_MODIFIER` touches only the block-friction term, and
block friction is 1.0 unless `Entity.onGround`. The whole drag step is
skipped when `LivingEntity.shouldDiscardFriction` is set. Climbing lives
inside this same step: `LivingEntity.handleOnClimbable` clamps the fall
speed on a `BlockTags.CLIMBABLE` block, and a separate clamp in
`LivingEntity.handleRelativeFrictionAndCalculateMovement` sets the vertical
component to 0.2 when a climbing or powder-snow entity is either colliding
horizontally or jumping — which is the whole of "you go up a ladder by
pressing into it".
So the delta `Entity.move` consumes carries the *previous* tick's gravity.
That is one of two conventions in the codebase, and the other is
`Entity.applyGravity`, which runs *before* the move. **An `ItemEntity` does
it the other way, and the contrast is the clearest way to see both.**
`ItemEntity.tick` applies gravity (a default of 0.04) before `Entity.move`
and drag after it, **reverses** any downward velocity on landing at half
strength — items bounce, they do not merely damp — and skips the move
entirely when it is resting still on the ground and the tick count says it is
not this item's turn, calling
`Entity.applyEffectsFromBlocksForLastMovements` on the previous tick's
segments instead. Its `Entity.getMovementEmission` is
`Entity.MovementEmission.NONE`, so it makes no step sounds. Neither
convention is wrong.
The fluid snapshot has one exception, and it is a useful one:
`LivingEntity.checkFallDamage` re-runs `Entity.updateFluidInteraction` from
*inside* `Entity.move` whenever the entity is not already in water (and
`ItemEntity.tick` re-runs it too), which is exactly why falling into water
cancels the fall damage in the same tick that entered it.
## What did I pass through
`Entity.applyEffectsFromBlocks` runs on the same gate as the step sound —
not client-side, or authoritative. It drains `Entity.movementThisTick` into
`Entity.finalMovementsThisTick` first — substituting a single old-position-to-
position segment when the deque is empty, and appending a final segment when
the entity ended somewhere the last recorded one did not — and only then runs
the replay, which opens by calling `Block.stepOn` for the block underfoot,
gated on `Entity.onGround`.
Each segment is replayed in the *same axis order the collision used* —
`Direction.axisStepOrder` again, over the segment's stored pre-collision
vector — and `Entity.checkInsideBlocks` walks each leg with
`BlockGetter.forEachBlockIntersectedBetween`, testing each block with
`AABB.collidedAlongVector` (through `Entity.collidedWithShapeMovingFrom`)
rather than a static overlap at the destination, and calling
`BlockBehaviour.BlockStateBase.entityInside`, `Entity.onInsideBlock` and
`FluidState.entityInside` on what it finds. `Entity.visitedBlocks` is the
deduplicator: a block is visited at most once across the whole replay,
however many segments cross it. Two budgets bound the work — sixteen sweep
steps per segment, which is not sixteen blocks, because every block the box
covers at one end of the sweep shares a single step index; and
`Entity.movementThisTick` merges its two oldest entries once it reaches a
hundred, buying bounded memory with a little precision. A segment that
exhausts its steps gets one last zero-length visit at the destination, which
covers every block the box ends up inside.
Nothing found is applied inline. Each effect is queued into the
`InsideBlockEffectApplier.StepBasedCollector`, which flushes a step's worth
at a time in `InsideBlockEffectType` declaration order —
`InsideBlockEffectType.FREEZE`, `InsideBlockEffectType.CLEAR_FREEZE`,
`InsideBlockEffectType.FIRE_IGNITE`, `InsideBlockEffectType.LAVA_IGNITE`,
`InsideBlockEffectType.EXTINGUISH` — so fire and water touched in the same
step always end in the extinguish. The reordering is strictly per step:
`InsideBlockEffectApplier.StepBasedCollector.advanceStep` flushes as the
replay advances and
`InsideBlockEffectApplier.StepBasedCollector.applyAndClear` runs the
accumulated list at the end, so across steps the order stays chronological
and fire in a *later* step than water still burns you.
## Off it goes
`LivingEntity.pushEntities` closes the tick. It collects pushable
neighbours through `Level.getPushableEntities` — a different predicate from
the collision one, and on the client `ClientLevel.getPushableEntities`
returns at most the local player, never the crowd. On a server it applies
`GameRules.MAX_ENTITY_CRAMMING` (default 24, checked one tick in four, the
damage 6) and then calls `LivingEntity.doPush` → `Entity.push`, a
horizontal-only impulse scaled by 0.05 and ignored below a hundredth of a
block.
Nothing has crossed the network yet. `ServerEntity.sendChanges` runs in the
chunk-source phase of `ServerLevel.tick`, which comes *before* the entity
loop ([the level tick](../server/server-level-tick.md)) — so this tick's
movement is broadcast at the start of the next one. It becomes a short delta,
`ClientboundMoveEntityPacket.Pos`, only when it can: not too big for a
short, no more than 400 ticks since the last teleport, not riding, the
entity does not demand precision, **and `Entity.onGround` still matches what
the last absolute sync recorded**. That last condition is the common case, and it is a
real cost: every landing and every step off a ledge forces a full
`ClientboundEntityPositionSyncPacket`. `Entity.syncPosition` forces the next
send outright, and `ClientboundSetEntityMotionPacket` carries the delta
separately. On the receiving side,
`ClientPacketListener.handleEntityPositionSync` moves only an entity that is
*not* locally authoritative, and snaps rather than interpolates past 64
blocks of correction — otherwise it feeds `InterpolationHandler`, three
steps by default, which is the coast branch of `LivingEntity.aiStep` doing
its real job.
## Where to look
`LivingEntity.aiStep` · `LivingEntity.travel` · `LivingEntity.travelInAir` ·
`LivingEntity.handleRelativeFrictionAndCalculateMovement` ·
`LivingEntity.handleOnClimbable` · `Entity.move` · `Entity.collide` ·
`Entity.collideBoundingBox` · `Entity.collideWithShapes` ·
`Entity.collectCandidateStepUpHeights` · `CollisionGetter` ·
`BlockCollisions` · `Shapes.collide` · `Entity.setPos` ·
`Entity.setOnGroundWithMovement` · `Entity.checkSupportingBlock` ·
`Entity.checkFallDamage` · `Entity.restituteMovementAfterCollisions` ·
`Entity.applyMovementEmissionAndPlaySound` ·
`Entity.updateFluidInteraction` · `EntityFluidInteraction` ·
`Entity.applyEffectsFromBlocks` · `Entity.checkInsideBlocks` ·
`InsideBlockEffectApplier.StepBasedCollector` · `InsideBlockEffectType` ·
`LivingEntity.pushEntities` · `MoverType` · `InterpolationHandler` ·
`ServerEntity.sendChanges`
---
*Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes `tools/verify_names.py`.*
---
# AI: goals and brains
> Verified against **Minecraft 26.2** · Part VI · A villager's day — wake, claim a job site, work, meet at the bell, walk home to bed — and the same tick under a zombie that has none of it.
It is dawn in a village. One villager climbs out of bed, walks to its
composter and works there until the bell rings. Ten blocks away a zombie
catches fire, sees the villager, and comes for it. Both are `Mob`s, both are
driven by the same `Mob.serverAiStep` on the server thread, and neither is
running a script: each is being *re-asked*, every tick or every other tick,
what it would like to be doing now. They are asked in two completely
different ways, and the villager's is the surprising one. Its day looks like
a timetable, and a reader who goes hunting for the class holding that
timetable will not find one — *Schedule* does not exist in 26.2. A `Brain`
holds an `EnvironmentAttribute` of `Activity`, a pointer into the *world*
rather than a table on the mob, and `Brain.updateActivityFromSchedule` asks
the `EnvironmentAttributeSystem` what that attribute's value is **at this
position, at this time**. The villager goes to bed because it asked the world
what hour it is *where it is standing*. The answer comes out of a data-pack
`Timeline` — `Timelines.VILLAGER_SCHEDULE`, a 24000-tick loop whose adult
track reads *10 idle, 2000 work, 9000 meet, 11000 idle, 12000 rest* beside a
baby track that swaps *play* in — and because the lookup takes a position,
the day can in principle differ by location. That system is [environment
attributes and
timelines](../world/environment-attributes-and-timelines.md); this page only
asks it a question.
## The cast
| class | what it decides | thread |
|---|---|---|
| `GoalSelector` | which goals run, by holding a four-entry table of `Goal.Flag` to the goal that owns it | server, from `Mob.serverAiStep` |
| `Goal` | whether it wants to run, whether it may be interrupted, and which flags it needs | as above |
| `WrappedGoal` | the arbitration — `WrappedGoal.canBeReplacedBy` — plus the priority and the running bit | as above |
| `Brain` | which activity is active, what the memories hold, and which behaviours are asked at all | server, from `Mob.customServerAiStep` |
| `MemoryModuleType` | the vocabulary a brain thinks in: 116 constants, of which the 53 with a codec are the mob's entire saved mind | declared, never ticked |
| `Sensor` | when to look at the world, on its own scan rate, and which memories to write | server, from `Brain.tick` |
| `ActivityData` | one activity's prioritised behaviour list, its memory requirements, and the memories erased when it stops | built per body by `Brain.ActivitySupplier` |
| `Sensing` | whether this mob can see that entity, memoised for exactly one tick — and *both* systems go through it | server, cleared at the top of `Mob.serverAiStep` |
## Seven things they do differently
| | the goal selector | the brain |
|---|---|---|
| **what holds the state** | `GoalSelector.availableGoals`, an insertion-ordered set of `WrappedGoal`, beside a lock table and a set of disabled flags | a memory map, a sensor map, and `Brain.availableBehaviorsByPriority` — priority to activity to behaviour set |
| **what fills it** | `Mob.registerGoals`, once, from the constructor, and only when the level is a `ServerLevel` | `Brain.Provider.makeBrain`, from an activity list built *per body* — and built again whenever the body changes |
| **what decides** | `Goal.canUse`, re-asked on every other tick | `Behavior.hasRequiredMemories` then `Behavior.checkExtraStartConditions`, asked once a tick |
| **what arbitrates** | the flag table. Lower priority number wins a contested flag, and a non-interruptable incumbent wins outright | the active activity. A behaviour whose activity is not active is not asked at all |
| **what persists across a save** | nothing. Not the running set, not the flags | 53 of the 116 memories, through `Brain.Packed` |
| **what the world can push in** | `Mob.updateControlFlags` every five ticks, and the leash, both on one selector only | the schedule attribute, POI claims, hostiles seen by sensors, `Attributes.FOLLOW_RANGE` |
| **which mobs use it** | every `Mob`. 58 goal classes and 10 targeting ones | 20 classes override `LivingEntity.makeBrain` — but only `Villager` sets a schedule |
Every row below is one of those lines, taken in turn.
### Where both of them sit in one mob tick
The profiler section names, because they are what a profile actually shows:
```
LivingEntity.tick
LivingEntity.aiStep
"ai" the guard: server side, and Mob.isEffectiveAi
"newAi" Mob.serverAiStep
"sensing" Sensing.tick — the line-of-sight memo is cleared
"targetSelector" ┐ GoalSelector.tick on the full pass,
"goalSelector" ┘ tickRunningGoals(false) on the off tick
"navigation" PathNavigation.tick — see pathfinding
"mob tick" Mob.customServerAiStep → "villagerBrain" → Brain.tick
"controls" "move" / "look" / "jump"
"jump" LivingEntity's own jump handling — outside the guard
"travel" LivingEntity.travel — where the body actually moves
"headTurn" Mob.tickHeadTurn — no side check, so both sides
```
Note the scope of that guard. `LivingEntity.aiStep` runs on the client too;
what it wraps in *server side and effective AI* is the one call to
`Mob.serverAiStep`, not the jump and travel sections beneath it.
`Mob.isEffectiveAi` is the more interesting half of the condition, because
`Mob` narrows it with `Mob.isNoAi` — which is where the *NoAI* tag takes
effect. On the client neither selector nor brain is ticked at all — only the
jump, travel and head-turn sections beneath the gate, and the debug
renderers.
## What holds the state
`GoalSelector` holds three things and none of them is a plan: a set of
`WrappedGoal`, a map from `Goal.Flag` to the goal currently holding it, and
a set of flags that have been switched off. There is no state machine and no
sequence. The only persistent state a goal system has is *which goals are
running* and *which flags are held*, and a `Mob` keeps two independent
copies of it, `Mob.goalSelector` and `Mob.targetSelector`.
A `Brain` holds a great deal more, and the piece to keep hold of is
`Brain.availableBehaviorsByPriority`: a sorted map from priority, to
activity, to a set of `BehaviorControl`. Priority is the *outer* key, so the
whole brain is walked in priority order regardless of which activity a
behaviour belongs to. Beside it sit the memory map, the sensor map, the
per-activity requirements, the per-activity erase lists, the core
activities, the active set and a default of `Activity.IDLE`.
Two things about ownership surprise people. `Brain` is declared on
`LivingEntity`, not on `Mob` — *every* living entity has one, the player
included, and the base implementation hands back an empty one that reports
itself `Brain.isBrainDead`. And the activity list is not static:
`Brain.ActivitySupplier` is asked for it **per body**, which is how a
villager's profession selects its work package.
## What fills it
Goals go in exactly once. The `Mob` constructor calls `Mob.registerGoals`
only when the level it is being built into is a `ServerLevel`, so a
client-side mob's selectors are empty for its whole life. After that the set
is fixed, but for the few mobs that add or remove a goal on a state change.
Memories are filled continuously, by sensors and by behaviours alike. A
`Sensor` looks at the world and writes what it saw: the default scan rate is
20 ticks (`Sensor.DEFAULT_SCAN_RATE`), `GolemSensor` uses 200 and
`SecondaryPoiSensor` 40, and `Sensor.randomlyDelayStart` offsets each one at
construction so a village does not scan in lockstep. What *registers* a
memory slot is declaring it — every memory a sensor lists in
`Sensor.requires` and every memory a behaviour names in its entry condition
is registered when the brain is built. Reading one that was never registered
throws *Unregistered memory fetched*: a behaviour has been installed on a mob
that has no idea what it is talking about.
The two systems share one piece of machinery here, and it is easy to file
under the wrong heading. `Sensing` is the per-mob line-of-sight memo, cleared
once per tick at the top of `Mob.serverAiStep`, and it is not the goal
system's alone: `TargetingConditions.test` routes every line-of-sight check
through `Mob.getSensing`, and the shared conditions the brain's sensors use
have that check on by default.
Those shared conditions deserve a second look. `Sensor` holds **six** static
`TargetingConditions` objects, used by every brain mob in the world, and
re-ranges all six from *this* body's `Attributes.FOLLOW_RANGE` immediately
before every scan. That is correct only because AI is strictly
single-threaded — there is not a future, an executor or a thread anywhere in
the AI packages — and it is about as clear a demonstration of the fact as the
codebase offers.
## What decides
A goal is asked `Goal.canUse` **every other tick**, staggered across mobs by
`tickCount + id`, with an exception for a mob's first two ticks, where the
full pass runs whatever the parity. On the off tick
`GoalSelector.tickRunningGoals` is called with *false*, so only goals that
answer `Goal.requiresUpdateEveryTick` are ticked at all — and, more
importantly, **no goal is stopped**, because the `Goal.canContinueToUse`
sweep lives in the full pass. A goal that lost its reason to run on an off
tick keeps running until the next even one.
The full pass, `GoalSelector.tick`, is three phases, and they are the next
level down in a profile. *goalCleanup* stops every running goal that either
holds a now-disabled flag or fails `Goal.canContinueToUse`, then drops every
lock whose holder is no longer running. *goalUpdate* walks the set again and
starts anything that is not running, holds no disabled flag, can take all its
flags, and answers `Goal.canUse`. *goalTick* — reached through
`GoalSelector.tickRunningGoals` with *true* — ticks the survivors.
A behaviour is asked once per tick, and the first question is not about the
behaviour at all. `Brain.tick` runs four fixed phases:
```mermaid
flowchart TB
A["Brain.tick"]
B["1. forgetOutdatedMemories — every MemorySlot counts down, and an expired one clears itself"]
C["2. tickSensors — every Sensor, each counting its own scan rate down to zero before it looks at anything"]
D["3. startEachNonRunningBehavior — walk availableBehaviorsByPriority, lowest number first"]
E{"is this activity in activeActivities?"}
F["skipped whole. A behaviour of an inactive activity is never even asked"]
G["tryStart: hasRequiredMemories, then checkExtraStartConditions"]
H["RUNNING, with an end timestamp rolled between minDuration and maxDuration"]
I["4. tickEachRunningBehavior — tickOrStop on everything now RUNNING"]
J{"timed out, or canStillUse false?"}
K["doStop. canStillUse defaults to false, so most behaviours stop inside the same Brain.tick that started them, and Behavior.tick is never called at all"]
L["Behavior.tick"]
A --> B --> C --> D --> E
E -- "no" --> F --> I
E -- "yes, and the behaviour is STOPPED" --> G --> H --> I
I --> J
J -- "yes" --> K
J -- "no" --> L
```
The branch marked *skipped whole* is what this page turns on. **An activity
is a filter, not a mode.** The brain's active set is always the core
activities plus exactly one other, so `Activity.CORE` behaviours run at every
hour of the day and switching activity only swaps the second half. (*Core
activities* is plural in the API and singular in practice: nothing in 26.2
calls `Brain.setCoreActivities` with anything but `Activity.CORE` alone.)
The *canStillUse* branch is sharper than "a behaviour runs for one tick".
`Behavior.canStillUse` defaults to false, and phases 3 and 4 are both inside
the *same* `Brain.tick` — so for a behaviour that does not override it,
`Behavior.tick` is not called once. Everything it does, it does in
`Behavior.start`. The duration rolled at start between the behaviour's
minimum and maximum (`Behavior.DEFAULT_DURATION` is 60) matters only for the
ones that do override it, which is why the same behaviour class configured
with different bounds behaves differently in two packages.
## What arbitrates
On the goal side, **the flag table is the arbiter, not the priority list**.
`GoalSelector.availableGoals` is insertion-ordered and never sorted; priority
only settles a contested flag. Two goals with no flag in common run together
whatever their numbers, and two that share one never do.
`WrappedGoal.canBeReplacedBy` is the whole rule: the incumbent must answer
`Goal.isInterruptable`, and the challenger's number must be strictly lower.
There is a small piece of craft in how that is arranged. `GoalSelector` never
puts a placeholder in its lock table; it reads the table with a *default* — a
sentinel `WrappedGoal` of maximum priority that reports itself not running —
so *this flag is free* and *this flag is held by someone worse than me* are
the same `WrappedGoal.canBeReplacedBy` call on an entry that may not exist.
On the brain side the arbiter is the active set, and the fallback is silent.
`Brain.setActiveActivityIfPossible` checks the target activity's memory
requirements and, if they do not hold, calls `Brain.useDefaultActivity`
instead. A jobless villager at tick 2000 is not "off schedule": the switch to
`Activity.WORK` fails its `MemoryModuleType.JOB_SITE` requirement and the
villager is idle by construction. Switching is also not free — the brain
first erases, for every activity leaving the set, the memories that
activity's `ActivityData` names as *memoriesToEraseWhenStopped*. It is one of
the few places the brain mutates state rather than reading it.
## What persists across a save
A goal system saves nothing. Which goals were running, which flags were held,
how far through an attack a mob was — all of it is rebuilt from scratch when
the chunk reloads and the constructor calls `Mob.registerGoals` again.
A brain saves `Brain.Packed`, and `Brain.pack` walks the memories keeping
only those whose `MemoryModuleType` can serialise: 53 of 116, the remaining
63 transient by construction. Time-to-live travels with them, so a memory can
expire across a reload as easily as within a tick — `MemorySlot` counts down
in phase 1 of every `Brain.tick` and clears itself at zero.
The reason `Brain.Packed` exists as a first-class shape is that a brain is
built more than once in a mob's life. `Villager.refreshBrain` stops every
running behaviour, packs the current memories, and runs the provider again
from the packed state with a fresh activity list. Changing profession does
that; so does growing up, which is how a baby swaps
`EnvironmentAttributes.BABY_VILLAGER_ACTIVITY` for
`EnvironmentAttributes.VILLAGER_ACTIVITY`.
## What the world can push in
Into a goal selector, two things. The first is `Mob.updateControlFlags`,
called from `Mob.tick` on the server every five ticks. It sets
`Goal.Flag.MOVE` and `Goal.Flag.LOOK` from one question — *is a `Mob`
steering me* — and `Goal.Flag.JUMP` from that **and** *am I in an
`AbstractBoat`*. So a mob a mob is riding loses all three, and a mob sitting
in a boat by itself loses only the jump. The second is the leash:
`Mob.leashTooFarBehaviour` disables `Goal.Flag.MOVE` outright and
`PathfinderMob.closeRangeLeashBehaviour` puts it back. Both touch
**`Mob.goalSelector` only**; `Mob.targetSelector` is never disabled.
`GoalSelector.tick` then stops any running goal holding a disabled flag and
refuses to start another.
Into a brain, rather more: the schedule attribute, whose value comes from the
world; hostiles, players, items, beds and golems, all written by sensors that
query the level; and POI claims, which go through the shared `PoiManager`
([points of interest](../world/points-of-interest.md)). Beyond that, a change
to `Attributes.FOLLOW_RANGE` or `Attributes.TEMPT_RANGE` reaches
`Mob.onAttributeUpdated`, which recomputes the pathfinder's node budget
([attributes](attributes.md), [pathfinding](pathfinding.md)).
**Neither of them crosses the network.** There is no AI packet. What a client
sees are consequences — head rotations, motion, position deltas, pose
changes, the occasional entity-event byte — plus a debug channel that costs
nothing until someone subscribes: `Mob.registerDebugValues` registers
`DebugSubscriptions.ENTITY_PATHS` and `DebugSubscriptions.GOAL_SELECTORS` for
every mob, and `DebugSubscriptions.BRAINS` only for one that is not
brain-dead.
Nor is either of them data-driven. The villager's day is data
(`Timelines.VILLAGER_SCHEDULE` in `Registries.TIMELINE`) — but
`VillagerProfession` and `PoiType` are not: both are `BuiltInRegistries`
bootstrapped from code, with no directory under the built-in data pack. And
**behaviours and goals are code** too,
plain Java lists in `VillagerGoalPackages` and the `*Ai` classes, not
registered and not addressable from a data pack.
## Which mobs use which
Every `Mob` has both fields, and almost every mob uses exactly one. Twenty
classes override `LivingEntity.makeBrain` — `Villager`, `Piglin`, `Warden`,
`Hoglin`, `Frog`, `Allay`, `Axolotl`, `Goat` and twelve more — and each keeps
its behaviour lists in a class named for the mob: `PiglinAi`, `WardenAi`,
`FrogAi`. There are eighteen such classes for twenty mobs, and the two
exceptions are worth naming. `Zoglin` keeps its lists inline, in the mob
itself. And `Villager`'s live in `VillagerGoalPackages` — genuinely the 26.2
name, and not a typo: it is the last survivor of the old convention, on a
mob that has no goals at all. Nor do most of the other nineteen: a brain mob
typically registers none.
**One** — brain mobs with a schedule. `Brain.setSchedule` has exactly two
call sites and both are in `Villager`, picking the adult attribute or the
baby one. The other nineteen never consult a clock: they call
`Brain.setActiveActivityToFirstValid`, which walks a priority list and takes
the first activity whose memory requirements hold. That is how `PiglinAi`
picks *fight* over *idle* and `FrogAi` picks *tongue* over *swim*, with no
time of day involved anywhere.
## The brain's trace: a villager's day
```mermaid
sequenceDiagram
participant Brain as Brain
participant MTS as MoveToTargetSink
participant AP as AcquirePoi
participant PM as PoiManager
participant UAFS as UpdateActivityFromSchedule
participant EAS as EnvironmentAttributeSystem
participant SIB as SleepInBed
Note over Brain: one Brain.tick, behaviours tried in ascending priority
Brain->>MTS: priority 1, core — WALK_TARGET present and PATH absent
Note over MTS: a wanted position leaves here for the pathfinder
Brain->>AP: priority 6, core — runs at every hour of the day
AP->>PM: findAllClosestFirstWithType(acquirable job sites, 48, HAS_SPACE)
PM-->>AP: the five best, closest first
AP->>AP: one path to all five at once, claimed only if Path.canReach
AP->>PM: take(pos), then set POTENTIAL_JOB_SITE
Brain->>UAFS: priority 99 — the last behaviour in the package
UAFS->>Brain: updateActivityFromSchedule, refused if under 21 ticks old
Brain->>EAS: getValue(VILLAGER_ACTIVITY, this position)
EAS-->>Brain: Timelines.VILLAGER_SCHEDULE says WORK from tick 2000
Brain->>Brain: requirements met, or fall back to the default silently
Note over Brain: the next Brain.tick is the first to run the work package
Note over Brain: tick 12000, REST, which has no requirement and always takes
Brain->>SIB: rest package, priority 3
SIB->>SIB: startSleeping, record LAST_SLEPT, clear the walk target
```
Read the priorities in that diagram as the ordering claims they are. The
schedule behaviour sits at 99, the last slot in every package that has one,
so the activity a villager switches to is never the one the rest of *this*
tick runs: the switch lands and the next tick acts on it. And it is only
consulted when a behaviour asks — `Brain.updateActivityFromSchedule` refuses
if fewer than 21 ticks have passed since the last one (the test is a strict
*greater than* 20). Five of the ten packages carry no such behaviour: core,
panic and hide have nothing at 99, pre-raid and raid have `ResetRaidStatus`
there instead. The omission is how they pin the
villager, and each of the three carries its own way out rather than leaving it
to the clock: `VillagerCalmDown` sits at priority 0 in the panic package and
calls `Brain.updateActivityFromSchedule` itself the moment the fear memories
clear, `SetHiddenState` does the same for hide, and `ResetRaidStatus` for the
two raid packages. Nothing is asking the clock on a schedule; the escape
hatch asks once, on its own terms.
The rest of the day hangs off that. **Claiming a job site** is `AcquirePoi`
from the core package: it asks `PoiManager.findAllClosestFirstWithType` for
free points of interest matching the profession within 48 blocks, takes the
best five, and runs a single pathfind with all five as targets at once — and
it claims only one the villager can actually *reach*, testing `Path.canReach`
before `PoiManager.take` ([pathfinding](pathfinding.md)). A bell across a
ravine is invisible to a villager. What it writes is
`MemoryModuleType.POTENTIAL_JOB_SITE`, not the job site itself;
`AssignProfessionFromJobSite` waits until the villager is within two blocks
of that position, then erases the memory, writes `MemoryModuleType.JOB_SITE`
and sets the profession — which is why walking to the workstation is a
required step and not decoration.
**Work** is a weighted `RunOne` over six: `WorkAtPoi` (or `WorkAtComposter`),
`StrollAroundPoi`, `StrollToPoi`, `StrollToPoiList`, `HarvestFarmland` and
`UseBonemeal`;
`WorkAtPoi` wants 300 ticks since the last check and 1.73 blocks or less to
the workstation. **Walking anywhere** is `MoveToTargetSink`, entered on *walk
target present, path absent*: it turns a `MemoryModuleType.WALK_TARGET` into
a path, hands it to the navigation, and records a failure as
`MemoryModuleType.CANT_REACH_WALK_TARGET_SINCE`. Below that hand-off is
[pathfinding](pathfinding.md) and then [movement and
collision](movement-and-collision.md). **Bed** is `SleepInBed`: a bed within
two blocks, unoccupied, in the right dimension, at least 100 ticks since it
was last woken. It never times out — it ends because `Brain.isActive` for
`Activity.REST` goes false at dawn and the core `WakeUp` behaviour calls
`LivingEntity.stopSleeping`.
## The goal selector's trace: the zombie
A `Zombie` has a `Brain`, because every `LivingEntity` does, but it is the
base one — no memories, no sensors, no behaviours, `Brain.isBrainDead` true.
Everything it will ever do comes out of `Mob.registerGoals`, run once in the
constructor: **seven** goals in `Mob.goalSelector` — a turtle-egg attack
goal, a `SpearUseGoal`, a `ZombieAttackGoal`, a `MoveThroughVillageGoal`, a
`WaterAvoidingRandomStrollGoal`, a `LookAtPlayerGoal` and a
`RandomLookAroundGoal` — and **five** in `Mob.targetSelector`, one
`HurtByTargetGoal` and four `NearestAttackableTargetGoal`s.
Every other tick each of the twelve is re-asked, and the flag table settles
it. The target goals hold `Goal.Flag.TARGET` and write `Mob.setTarget`. The
attack goals hold `Goal.Flag.MOVE` and `Goal.Flag.LOOK` and drive the
navigation, with `SpearUseGoal` at priority 2 sitting above `ZombieAttackGoal`
at 3, so it is usually the one holding them. `LookAtPlayerGoal` wants
`Goal.Flag.LOOK` alone and `RandomLookAroundGoal` wants `Goal.Flag.MOVE` as
well, and both lose to whoever already has them. No activity, no schedule,
nothing persisted, nothing the world can push in: the zombie's entire mind is
a handful of running bits and one target field. Even that field is not read
directly — `Mob.getTarget` filters through `Mob.asValidTarget` on every call,
so a target that turned creative or spectator is gone the moment it is asked
for, and brain mobs source theirs from `Mob.getTargetFromBrain` instead.
## Questions players ask
**Why does a ridden mob stop moving on its own but still glare at me?**
Because `Mob.updateControlFlags` disables `Goal.Flag.MOVE`, `Goal.Flag.JUMP`
and `Goal.Flag.LOOK` on `Mob.goalSelector` and never touches
`Mob.targetSelector`. Target selection is a separate `GoalSelector` with its
own lock table, and nothing in the game switches it off. A boat alone does
less than people expect: it costs the mob only `Goal.Flag.JUMP`.
**Why did the villager ignore a perfectly good workstation?** Either it could
not reach it — `AcquirePoi` pathfinds before it claims, and an unreachable
site is skipped — or it claimed it and has not walked there yet, in which
case the memory still says *potential* job site and the profession has not
changed. `PoiCompetitorScan` will also hand a contested claim to the more
experienced villager, and `ValidateNearbyPoi` erases it if the block is gone.
**Why does a spooked villager stay spooked past bedtime?** Because the
schedule does not push, it is pulled — and the panic package has nothing at
priority 99 to pull it. `VillagerCalmDown` is what lets the clock back in.
## Where to look
`GoalSelector.tick` · `WrappedGoal.canBeReplacedBy` · `Goal.Flag` ·
`Mob.registerGoals` · `Mob.serverAiStep` · `Mob.updateControlFlags` ·
`Sensing` · `TargetingConditions.test` · `Brain.tick` ·
`Brain.updateActivityFromSchedule` · `Brain.setActiveActivityIfPossible` ·
`Brain.setActiveActivityToFirstValid` · `Brain.Provider` · `Brain.Packed` ·
`ActivityData` · `MemoryModuleType` · `MemorySlot` · `Sensor` ·
`Behavior.tryStart` · `Behavior.canStillUse` · `BehaviorBuilder` ·
`GateBehavior` · `RunOne` · `VillagerGoalPackages` · `Villager.refreshBrain` ·
`AcquirePoi` · `MoveToTargetSink` · `SleepInBed` · `Timelines.VILLAGER_SCHEDULE`
---
*Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes `tools/verify_names.py`.*
---
# Pathfinding
> Verified against **Minecraft 26.2** · Part VI · A villager decides to walk to its bed, and a hundred ticks later it is standing still against a fence, having formally given up.
A behaviour has produced a position and stopped caring. Everything between
that position and a mob actually leaning into a direction is this page: one
A\* search over a snapshot of already-loaded chunks, a `Path` of nodes, and a
control that has to be told again every single tick. The part of it people
recognise is the failure. **Giving up is machinery, not an absence of it** —
every node the mob is walking towards carries a timeout computed from its
distance and the mob's current speed, three times over that budget abandons
the path outright, and a separate check every hundred ticks asks whether the
mob has covered a quarter of the ground its speed says it should have. The
mob you watch walk into a wall and then wander off is running a scheduled
surrender.
Above the waterline is [goals and brains](ai-goals-and-brains.md), which
decides *where*; this page is *how*, and it is the same machinery whichever
decision system asked.
## The cast
| class | what it decides | thread |
|---|---|---|
| `PathNavigation` | when to search, what budget to search with, when to give up | server main |
| `PathNavigationRegion` | which blocks the search is allowed to see — a snapshot, never a load | server main |
| `NodeEvaluator` | what a block *is* to this mob, as a `PathType` | server main |
| `PathTypeCache` | the 4,096-entry memo that makes that affordable | server main, owned by `ServerLevel` |
| `PathFinder` | the A\* itself, bounded by a node budget | server main |
| `Path` | the node list, and whether it actually reaches the target | server main, with a stream codec for the debug channel only |
| `MoveControl` | where a wanted position becomes a yaw and a speed, and forgets it again the same tick | server main |
Nothing here is asynchronous. There is not a future, an executor or a thread
anywhere in the pathfinder, and every entry point takes a `ServerLevel` or a
`Mob` on one. Nothing crosses the network either, except when a debug client
has subscribed.
## The pipeline
```mermaid
flowchart TB
WANT["a goal or a behaviour calls PathNavigation.createPath or moveTo"]
GATE["four early exits: no targets, mob below the world floor, canUpdatePath false, or a live path to the same target"]
REGION["PathNavigationRegion: a cube of chunks fetched with ChunkSource.getChunkNow"]
EVAL["NodeEvaluator turns each candidate block into a PathType, through PathTypeCache"]
SEARCH["PathFinder: A* over a BinaryHeap, bounded by maxVisitedNodes"]
PATH["a Path — reached, or the best node it found"]
FOLLOW["PathNavigation.tick advances the node index"]
CTRL["MoveControl.setWantedPosition, re-issued every tick"]
GIVEUP["stuck check every 100 ticks, node timeout at three times the budget"]
WANT --> GATE
GATE -- "returns null, or the existing path unchanged" --> WANT
GATE --> REGION
REGION --> EVAL
EVAL --> SEARCH
SEARCH --> PATH
PATH --> FOLLOW
FOLLOW --> CTRL
FOLLOW --> GIVEUP
GIVEUP -- "stop, and the behaviour is told the path is done" --> WANT
```
## Asking: the four ways a search does not happen
`PathNavigation.createPath` refuses before it does anything expensive. It
returns null on an empty target set, on a mob below `Level.getMinY`, and on
`PathNavigation.canUpdatePath` — which for `GroundPathNavigation` means *on
the ground, in liquid, or riding something*, so an airborne mob simply cannot
ask. The fourth exit is the interesting one: if there is already a path that
is not done and the requested target is among its targets, **the existing
path is returned unchanged**. Re-asking for a destination you are already
walking to costs nothing and changes nothing.
`PathNavigation.recomputePath` is the other entrance, and it is rate-limited
rather than refused. More often than every twenty game ticks, or with
`PathNavigation.canUpdatePath` false, it sets a flag instead and the next
`PathNavigation.tick` tries again. That deferral is what keeps a mob standing
in a doorway from re-searching twenty times a second.
## The budget, which is also the map
One number governs both how hard the search may work and how much world it
may look at. `PathNavigation` builds its `PathFinder` with
`Attributes.FOLLOW_RANGE`'s **base** value times sixteen, and
`PathNavigation.updatePathfinderMaxVisitedNodes` later recomputes it as
sixteen times the larger of the *modified* follow range and
`PathNavigation.setRequiredPathLength` — 16 by default, and raised by seven
classes: 48 for `Villager`, `Allay`, `Bee`, `CopperGolem` and `HappyGhast`,
40 for `Llama`, 32 for `Fox`. `PathNavigation.setMaxVisitedNodesMultiplier`
scales the result, and keeps scaling it until
`PathNavigation.resetMaxVisitedNodesMultiplier` puts it back: `Bee` is the
only class that touches either.
The same maximum path length becomes the **radius of the
`PathNavigationRegion`**, plus an offset of 8 or 16 depending on which
`PathNavigation.createPath` overload was used — and inside the search it appears twice more,
as a test on the current node's distance from the start and on each
neighbour's walked distance. A villager can find a bed 48 blocks away because its *required path length*
is 48 — it never touches `Attributes.FOLLOW_RANGE` at all, so its follow
range is `Mob`'s default 16 and the larger of the two is the number it set
itself; it cannot find one 60 blocks away no matter how open the ground is.
That region is built by asking `ChunkSource.getChunkNow` for every chunk in
the cube. **A path search never loads a chunk and never blocks** — an absent
chunk is a null entry that reads as air. This is the same discipline
`Entity.move` uses for collisions, and it is why AI cannot stall a tick.
## What a block is
`NodeEvaluator` answers *what is this position, to this mob* with a
`PathType`: 27 constants, each carrying a default cost, and **a negative cost
means impassable, not expensive**. Nine of the twenty-seven are −1 —
`PathType.BLOCKED`, `PathType.LAVA`, `PathType.FENCE`, `PathType.LEAVES`,
`PathType.POWDER_SNOW`, `PathType.DAMAGING`, `PathType.UNPASSABLE_RAIL` and
the two closed doors; `PathType.WATER` is 8, `PathType.FIRE` 16,
`PathType.OPEN` and `PathType.WALKABLE` 0. A mob overrides any of them for
itself with `Mob.setPathfindingMalus`, read back through
`Mob.getPathfindingMalus` — which is how the same lava is free ground to a
`Strider`, merely expensive to a `ZombifiedPiglin`, and a wall to the
ordinary `Piglin` that never overrides it.
Four evaluators in the pathfinder package cover the movement modes:
`WalkNodeEvaluator`, and the three that specialise it or replace it —
`FlyNodeEvaluator` and `AmphibiousNodeEvaluator` extend the walker,
`SwimNodeEvaluator` extends `NodeEvaluator` directly. Two mobs subclass one
further for themselves, `Frog` and `Creaking`. Each `PathNavigation` subclass chooses one:
`GroundPathNavigation`, `FlyingPathNavigation`, `WaterBoundPathNavigation`,
`AmphibiousPathNavigation`, and `WallClimberNavigation` on top of the ground
one.
Classifying a block is expensive enough to memo. `PathTypeCache` is a fixed
4,096-entry table owned by `ServerLevel`, consulted through
`PathfindingContext` — which attaches it **only** on a `ServerLevel` and
falls back to computing the type from scratch otherwise. It is invalidated
one position at a time from `ServerLevel.sendBlockUpdated`, which is the
subject of the last section.
## The search
`PathFinder.findPath` is plain A\* over a `BinaryHeap`, and three of its
details decide what mobs feel like.
**It is bounded twice: by a node count and by a distance.** The loop breaks
as soon as its visit counter reaches the budget —
`PathFinder.setMaxVisitedNodes`, scaled by the multiplier — and inside the
loop the maximum path length is the second bound, gating which nodes are
expanded and which neighbours are admitted at all. Under the node budget
alone a search through open ground would reach much further than the same
budget spends in a maze; the distance bound is what stops it.
**The heuristic is inflated.** Each neighbour's *h* is the best straight-line
estimate multiplied by 1.5, which makes the search greedy: it finds a route
sooner and the route it finds is not guaranteed to be the shortest. When
several targets were reached, the winner is simply the path with the fewest
nodes; when none was, it is the path that ends closest to a target, with
fewest nodes as the tie-break.
**A failed search still returns a path.** If no target came within
the *reach range* — measured as a Manhattan distance from the popped node — the
finder reconstructs a path to the closest node it managed to reach and marks
it *not reached*. That is what `Path.canReach` reports, and it is the number
that matters rather than "was a path found": `AcquirePoi` tests it before
claiming a point of interest, and `MoveToTargetSink` turns a false into a
*cannot reach* memory. A null from `PathNavigation.createPath` means one of
the four early exits rather than a search that came back empty-handed — the
finder always has a best node to reconstruct towards.
One more thing the search does not do unless asked: it accumulates its closed
set, and attaches it to the `Path`, only while something is subscribed to
`DebugSubscriptions.ENTITY_PATHS`. `PathNavigation` installs that predicate
in its constructor, off the server's `ServerDebugSubscribers`. `Path` has a
stream codec for exactly this and no gameplay reason.
```mermaid
sequenceDiagram
participant MTS as MoveToTargetSink
participant PN as PathNavigation
participant PNR as PathNavigationRegion
participant NE as NodeEvaluator
participant PF as PathFinder
participant MoveC as MoveControl
MTS->>PN: createPath to the walk target, then moveTo with a speed modifier
PN->>PN: four early exits, then push the pathfind profiler section
PN->>PNR: build a cube of maxPathLength plus the offset, with getChunkNow
PN->>PF: findPath — region, mob, targets, maxPathLength, reachRange, multiplier
PF->>NE: getStart, then getNeighbors per popped node
NE->>PNR: getPathTypeFromState, through PathTypeCache on a server level
PF-->>PN: a Path, reached or best-effort, with canReach set accordingly
PN->>PN: trimPath, record the stuck-check position, keep the node index
Note over PN,MoveC: every tick from here
PN->>MoveC: setWantedPosition for the next node, at speedModifier
MoveC->>MoveC: tick — reset the operation to WAIT first, then set the yaw and the speed
```
## Following it, one tick at a time
`PathNavigation.tick` advances the node index and calls
`MoveControl.setWantedPosition` with the next node and the speed modifier.
It has to do that **every** tick, because `MoveControl.tick` sets its own
`MoveControl.Operation` back to `MoveControl.Operation.WAIT` as it handles
the move — the control is a one-shot instruction, not a destination it
remembers.
`MoveControl.setWantedPosition` is the main way a decision, goal or brain,
becomes movement, but it is neither the only method nor a single call site.
`MoveControl.strafe` is a second entrance, used by `RangedBowAttackGoal` and
the brain behaviour `BackUpIfTooClose`; `MoveControl.setWait` is a third.
`MoveControl.setWantedPosition` itself has twelve callers outside the navigations, in
eight classes: the shared goals `TemptGoal.ForNonPathfinders` and
`TryFindWaterGoal`, the per-mob goals of `Bee`, `Blaze`, `Ghast` and `Vex`,
`Rabbit` from the mob itself rather than from a goal, and `Fox` to pin a
sleeping fox where it lies. The first of those is why a happy ghast drifts
towards you in a straight line through terrain a path search would have
routed around — an ordinary tempted cow runs the base `TemptGoal`, which
calls the navigation like anything else.
Three more controls sit beside it and are the rest of what turns a decision
into a pose. `LookControl` aims the head, `JumpControl` fires a jump the mover
then executes, and `BodyRotationControl` swings the body to follow the head
the moment the head is more than fifteen degrees off — it is the *reverse*
move, easing the head back towards the front, that waits for ten stable
ticks. `BodyRotationControl.clientTick` is a leftover
name: `LivingEntity.tick` calls `Mob.tickHeadTurn` with no side check at all,
so it runs on both sides every tick.
The mover itself is Part VI's [movement and
collision](movement-and-collision.md) — the control sets the yaw and calls
`Mob.setSpeed`, which writes `LivingEntity.zza` with it, and
`LivingEntity.travel` does the rest. `LivingEntity.xxa` is written only by
the strafe branch, which pathfinding never takes.
## Giving up
Two independent timers, and they answer different questions.
`PathNavigation.doStuckDetection` runs its first half **every hundred ticks**:
it compares where the mob is with where it was at the last check, against a
threshold of the mob's effective speed times 100 times 0.25 — a quarter of
the ground the speed claims. Below that, `PathNavigation.isStuck` is set and
the path is stopped. The effective speed is the speed itself at 1.0 or above
and the *square* of it below, which quietly makes the threshold far more
forgiving for slow mobs.
The second half is per node. When the next node changes, the navigation
computes a time budget for it — the distance to that node divided by the
mob's speed, times twenty — and accumulates real ticks against it. Past
**three times** that budget, `PathNavigation.timeoutPath` resets the counters
and stops. This is the one that catches a mob whose path is fine and whose
route is blocked by something the search could not see.
Both endings are the same ending from outside: `PathNavigation.isDone`
becomes true, and whichever behaviour or goal was waiting on the path finds
out on its next evaluation.
## The one place the world pushes back
Everything above is AI asking the world questions. `ServerLevel.sendBlockUpdated`
is the single call in the other direction. It invalidates the changed
position in the path-type cache **unconditionally**, and then — only if
`Shapes.joinIsNotEmpty` says the collision shape actually changed — walks
`ServerLevel.navigatingMobs`, asks each navigation
`PathNavigation.shouldRecomputePath` about the position, and calls
`PathNavigation.recomputePath` on the ones that say yes. That last loop —
and only that loop — is wrapped in a re-entrancy flag, because a recompute
can itself change blocks; a call that arrives while the flag is set logs and,
in a development environment, pauses.
That is why closing a door in front of a mob re-routes it and repainting a
block does not.
## Why mobs look stupid
**Why do mobs take silly routes?** The heuristic is multiplied by 1.5, so the
search is deliberately greedy — it stops at the first route that reaches,
not the best one. Cost is a per-block malus, not a distance, so a mob will
happily walk three blocks further to avoid a `PathType.WATER` node worth 8.
**Why does a mob stop dead at the edge of my render distance?** It did not.
The search only sees chunks already loaded on the server, and a target
outside them is simply not reachable; the path comes back with
`Path.canReach` false and the behaviour that asked gives up.
**Why does a villager find a bed across the village but not one behind a
wall?** Because 48 is `Villager`'s required path length, so distance is
rarely the limit — and because `AcquirePoi` runs a real path search before it
claims anything, so unreachable is invisible rather than merely far.
## Where to look
`PathNavigation` · `PathNavigation.createPath` · `PathNavigation.moveTo` ·
`PathNavigation.tick` · `PathNavigation.recomputePath` ·
`PathNavigation.canUpdatePath` · `PathNavigation.doStuckDetection` ·
`PathNavigation.updatePathfinderMaxVisitedNodes` ·
`PathNavigation.setRequiredPathLength` · `GroundPathNavigation` ·
`FlyingPathNavigation` · `WaterBoundPathNavigation` ·
`AmphibiousPathNavigation` · `WallClimberNavigation` ·
`PathNavigationRegion` · `PathFinder.findPath` · `BinaryHeap` · `Node` ·
`Target` · `Path.canReach` · `NodeEvaluator` · `WalkNodeEvaluator` ·
`SwimNodeEvaluator` · `FlyNodeEvaluator` · `AmphibiousNodeEvaluator` ·
`PathType` · `PathTypeCache` · `PathfindingContext` ·
`Mob.getPathfindingMalus` · `MoveControl.setWantedPosition` · `LookControl` ·
`JumpControl` · `BodyRotationControl` · `ServerLevel.sendBlockUpdated`
---
*Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes `tools/verify_names.py`.*
---
# Damage and death
> Verified against **Minecraft 26.2** · Part VI · An arrow hits a player in full iron with Protection II: six damage becomes two, and if it kills, a message, a loot drop and a death screen.
The arrow lands, the screen goes red, the hearts drop by one, and a
notification sound plays somewhere behind you. Six damage left the bow and
about two reached your health, which is the part everybody knows. The part
nobody sees is what happens when a second arrow arrives four ticks later,
inside the flash. If more than ten ticks of invulnerability remain, that hit
is worth only its *excess* over `LivingEntity.lastHurt`: a weaker one returns
immediately, having done nothing at all, and a stronger one takes a partial
branch that clears an internal *took full damage* flag. That one flag gates
the damage-event broadcast, the knockback, the hurt sound and the red flash
alike. Health goes down and nothing else happens — which means neither
`ClientboundDamageEventPacket` nor the red flash is a reliable "was hit"
signal. It is all in `LivingEntity.hurtServer`.
## The cast
| class | what it decides | thread |
|---|---|---|
| `DamageSource` | *what* hit and *who*: a direct entity, a causing entity, and — rarely — a position instead | built wherever the hit starts, server main |
| `DamageType` | the message id, the difficulty scaling, the food cost, the hurt sound and the kind of death message | a dynamic registry entry, loaded from a data pack and synced to clients |
| `DamageTypeTags` | almost every behavioural branch on the path below | read on the server main thread |
| `LivingEntity` | the whole reduction pipeline, the i-frames, the flash, the two attribution references and death | server main thread |
| `CombatRules` | the two pieces of arithmetic — armour and enchantment protection | stateless statics |
| `CombatTracker` | what killed you, and the sentence that says so | server main, cleared on its own timer |
| `ServerPlayer` | PvP, the death screen packet, the death message, the inventory drop | server main thread |
| `Entity` | that `Entity.hurtServer` is **abstract** — there is no default behaviour to inherit | — |
Everything above is server-side, and `LivingEntity` declares no
`Entity.hurtClient` at all: no damage is ever *calculated* on a client. A
client does set health — from `LivingEntity.DATA_HEALTH_ID`, from
`ClientboundSetHealthPacket`, and to zero on the death event — but it never
decides a number. See
[authority](authority.md) for why `Entity.hurtServer` takes a `ServerLevel`,
so the compiler enforces that and not a convention.
## The number the arrow decides
`AbstractArrow.onHitEntity` builds the *source* first, because the number
needs it: `EnchantmentHelper.modifyDamage` takes the `DamageSource` as an
argument. It starts from `AbstractArrow.baseDamage`, 2.0 for an ordinary
arrow, runs
`EnchantmentHelper.modifyDamage` over the bow that fired it so Power raises
the *base*, then multiplies by the arrow's current speed and rounds up with
`Mth.ceil` — so the damage that leaves the bow is an integer, and a slowing
arrow does less. `AbstractArrow.isCritArrow` adds a random bonus of up to
half the damage plus one. Six is a fresh arrow at full draw.
`DamageSources.arrow` then names two entities: the arrow as the *direct*
entity, the shooter as the *causing* one — or, when the arrow has no owner
left, the arrow itself in both roles. `DamageSource.getWeaponItem` reaches
through the direct entity, which is how Breach on the attacker's weapon
reaches the victim's armour calculation. A source can carry a position
instead, though far less often than you would guess: ordinary explosions do
not, and fall back to the direct entity in `DamageSource.getSourcePosition`.
Exactly three sources are genuinely positional, and only they put a position
on the wire through `DamageSource.sourcePositionRaw` —
`DamageSources.badRespawnPointExplosion`, `/damage … at` (`DamageCommand`),
and `ExplodeEffect` unattributed.
The `DamageType` behind the source is five fields — message id,
`DamageScaling`, food exhaustion, `DamageEffects` (which picks the hurt
*sound*, and only for a `Player` — `Player.getHurtSound` is its one caller)
and `DeathMessageType` — and it lives in a data pack. There are 51
keys in `DamageTypes` and 35 tags in `DamageTypeTags`, and **almost every
behavioural branch below is tag-driven rather than type-driven**. The
exceptions are worth listing because they are the whole list: thorns picks
its own secondary sound in `LivingEntity.playSecondaryHurtSound`, wind charge
is excluded from mob-anger attribution against an
`EntityTypeTags.NO_ANGER_FROM_WIND_CHARGE` entity, and a `ServerPlayer`
mid-dimension-change is invulnerable to all but `DamageTypes.ENDER_PEARL`.
## Three gates before anything is computed
`LivingEntity.hurtServer` opens with three early returns: already
invulnerable, already dying, or a `DamageTypeTags.IS_FIRE` source against
`MobEffects.FIRE_RESISTANCE` — a mob-effect immunity sitting *outside* the
reduction pipeline entirely. Invulnerability itself is
`Entity.isInvulnerableToBase` (removed, the invulnerable flag, fire immunity,
fall immunity) **or** an enchantment-granted one through
`EnchantmentHelper.isImmuneToDamage`, which is how Frost Walker makes its
wearer immune to magma blocks.
Two subclasses get there first. `ServerPlayer.hurtServer` adds the PvP and
team check — `ServerPlayer.canHarmPlayer` refuses when the level forbids PvP
or the two share a team that disallows friendly fire — testing both a `Player`
causing entity directly and an `AbstractArrow` causing entity by asking it for
its owner. `ServerPlayer.isInvulnerableTo` adds the two conditions that have
no tag: mid-dimension-change, and a client that has not finished loading. Then
`Player.hurtServer` refuses a creative player unless the source is
`DamageTypeTags.BYPASSES_INVULNERABILITY`, and applies **difficulty
scaling** — halved and offset on easy, one and a half times on hard, zero on
peaceful — but only when `DamageSource.scalesWithDifficulty` says so. That
reads `DamageScaling` off the type, and `DamageTypes.ARROW` is
*when_caused_by_living_non_player*: a skeleton's arrow scales and a player's
does not. `Player.isInvulnerableTo` is also where `GameRules.DROWNING_DAMAGE`,
`GameRules.FALL_DAMAGE`, `GameRules.FIRE_DAMAGE` and
`GameRules.FREEZE_DAMAGE` live — switching one off makes a player *immune*
rather than making the damage smaller.
## One number, a dozen owners
Past the gates the number goes down a chain in which every link owns one
arithmetic step — five multiplications and three subtractions — and knows
about none of the others.
```mermaid
flowchart TB
N0["6.0 leaves the bow — Mth.ceil(speed times baseDamage), Power already folded into the base"]
N1["LivingEntity.applyItemBlocking — subtract what BLOCKS_ATTACKS resolves for this angle and this damage type"]
N2["freezing times 5 on a FREEZE_HURTS_EXTRA_TYPES entity, helmet times 0.75 on a DAMAGES_HELMET source"]
N3["the i-frame window — over ten ticks left, only the excess over lastHurt survives"]
N4["CombatRules.getDamageAfterAbsorb — armour, floored at a fifth and capped at 20"]
N5["Resistance — five points of twenty-five per level, total immunity at amplifier four"]
N6["CombatRules.getDamageAfterMagicAbsorb — protection points, capped at 20 as well"]
N7["absorption hearts, spent before health is, then CombatTracker.recordDamage and LivingEntity.setHealth"]
N0 -- "6.0 — Player.hurtServer scales by difficulty, but not a player's arrow" --> N1
N1 -- "6.0 — nothing raised" --> N2
N2 -- "6.0 — neither applies" --> N3
N3 -- "6.0 — the window was clear" --> N4
N4 -- "3.12 — 15 armour, no toughness, 48 per cent off" --> N5
N5 -- "3.12 — no Resistance" --> N6
N6 -- "2.12 — 8 protection points, 32 per cent off" --> N7
```
The first link is blocking, and **shields are no longer a mechanism, only a
vocabulary**. `LivingEntity.applyItemBlocking` asks the item being used —
through `LivingEntity.getItemBlockingWith`, which enforces
`BlocksAttacks.blockDelayTicks` — for a `DataComponents.BLOCKS_ATTACKS`
component, checks the damage type against `BlocksAttacks.bypassedBy`,
computes the angle between the source position and the victim's head
rotation, and lets `BlocksAttacks.resolveBlockedDamage` pick a reduction from
the component's own list. `BlocksAttacks.hurtBlockingItem` then charges
durability — for a blocking `Player` only; a mob's item never wears — and a
non-projectile block sends the *blocker* reeling through
`LivingEntity.blockedByItem`, which is how a `Hoglin` throws whoever blocked
it and a `Ravager` stuns itself while shoving them. An arrow with any piercing level skips all of
it before the angle is computed. `ShieldItem` still exists, the statistic is
still `Stats.DAMAGE_BLOCKED_BY_SHIELD`, and the axe-disables-shield rule
survives as `LivingEntity.getSecondsToDisableBlocking` — which `Warden`
overrides — feeding `Player.blockUsingItem` and `BlocksAttacks.disable`. None
of it is shield-specific code any more.
The two multipliers after it almost never fire and are both pure tag lookups:
`DamageTypeTags.IS_FREEZING` against an entity in
`EntityTypeTags.FREEZE_HURTS_EXTRA_TYPES` multiplies by five, and
`DamageTypeTags.DAMAGES_HELMET` against a helmeted victim calls
`LivingEntity.hurtHelmet` and multiplies by 0.75.
## Ten ticks in which nothing shows
`Entity.invulnerableTime` is set to 20 by a hit that lands in full and counted
down once a tick — from `LivingEntity.baseTick` for everything except a
`ServerPlayer`, and from `ServerPlayer.tick` for players, earlier in the tick.
`LivingEntity.hurtDuration` and `LivingEntity.hurtTime` are set to 10, so the
red flash is only half the window.
The other half is the silent one. Inside it, unless the type carries
`DamageTypeTags.BYPASSES_COOLDOWN`, the incoming damage is compared against
`LivingEntity.lastHurt` — what the last hit was worth — and only the excess
is applied. A hit that is not bigger returns before any sound, packet,
knockback or combat entry. A hit that *is* bigger applies the difference and
clears a local *took full damage* flag, and everything downstream sits inside
a test of that flag: no `ServerLevel.broadcastDamageEvent`, no
`Entity.markHurt`, no `LivingEntity.dealDefaultKnockback`, no hurt sound, no
reset of the flash. So the strongest hit in each window is the only one
anyone can see, and the rest are free damage that leaves no trace on the wire.
`LivingEntity.resolveMobResponsibleForDamage` and
`LivingEntity.resolvePlayerResponsibleForDamage` sit outside that test and run
on the silent *stronger* partial hit as well as the full one — only the
weaker hit, which returns before them, leaves no trace. They write
`LivingEntity.lastHurtByMob` and `LivingEntity.lastHurtByPlayer` (with its
hundred-tick `LivingEntity.lastHurtByPlayerMemoryTime` countdown), crediting a
tamed `Wolf`'s work to its owner. `LivingEntity.lastDamageSource` and
`LivingEntity.lastDamageStamp` are set beside them, but only when the hit
counted for something.
## Armour, and why big hits punch through it
`LivingEntity.actuallyHurt` is where the number meets the victim's gear.
`LivingEntity.getDamageAfterArmorAbsorb` first calls `LivingEntity.hurtArmor`
— which is **empty on `LivingEntity`** and overridden only by `Player`,
`Horse` and `Wolf`. A skeleton in full iron never wears its armour out. Where
it is implemented it routes to `LivingEntity.doHurtEquipment`: one durability
point per four damage, minimum one, per piece, and each piece must separately
be `Equippable.damageOnHurt`, damageable, and pass `ItemStack.canBeHurtBy`.
Then `CombatRules.getDamageAfterAbsorb` does the arithmetic. Effective armour
is the armour points *minus the incoming damage divided by two plus a quarter
of toughness*, clamped between `CombatRules.MIN_ARMOR_RATIO` of nominal and
`CombatRules.MAX_ARMOR`, and the reduction is that over
`CombatRules.ARMOR_PROTECTION_DIVIDER`. **Big hits punch through armour by
design**: the subtraction is what makes a 40-damage hit see less armour than a
6-damage one, and `Attributes.ARMOR_TOUGHNESS` is exactly the term that slows
it down. Full iron is 15 points and no toughness, so a 6-damage hit sees 12
effective armour, 48 per cent off, 3.12 left. Breach moves the resulting
fraction through `EnchantmentHelper.modifyArmorEffectiveness` first.
`LivingEntity.getDamageAfterMagicAbsorb` then applies Resistance and sums
`EnchantmentHelper.getDamageProtection` across every equipment slot.
Protection is not a class: it is
`EnchantmentEffectComponents.DAMAGE_PROTECTION` holding a conditional value
effect, confined to armour by its own *slots* declaration in the JSON rather
than by the helper, worth one point per level per piece — so Protection II on
four pieces is 8. `CombatRules.getDamageAfterMagicAbsorb` caps that sum at 20
and takes *sum over 25* off. 3.12 becomes about 2.12.
**Armour is capped twice and floored once.** Effective armour never drops
below a fifth of nominal and never counts above 20, and protection points cap
at 20 as well — so armour alone tops out at 80 per cent reduction, protection
at 80 per cent of what is left, 96 per cent combined — so armour alone can
never take a hit to nothing. An effect can: Resistance at amplifier four
multiplies by zero.
What survives comes off absorption first and health second, at which point
`CombatTracker.recordDamage` files a `CombatEntry`. `Player.actuallyHurt`
overrides the whole method to add food exhaustion (`DamageType.exhaustion`,
0.1 for an arrow) and the damage statistics.
## Telling everyone, and what a block replaces
```mermaid
sequenceDiagram
participant AA as AbstractArrow
participant SP as ServerPlayer
participant LE as LivingEntity
participant CT as CombatTracker
participant SL as ServerLevel
participant CPL as ClientPacketListener
AA->>SP: hurtOrSimulate(arrow source, 6.0)
SP->>SP: hurtServer — PvP and teams, then Player's creative gate and difficulty scaling
SP->>LE: three gates, blocking, the two odd multipliers
LE->>LE: i-frames — a partial hit clears the took-full-damage flag here
LE->>CT: actuallyHurt — armour, protection, absorption, then recordDamage and setHealth
LE->>LE: resolveMobResponsibleForDamage, resolvePlayerResponsibleForDamage
LE->>SL: broadcastDamageEvent — full hits only, and only when nothing was blocked
SL-->>CPL: ClientboundDamageEventPacket — every tracker, and the victim
LE->>LE: markHurt, then dealDefaultKnockback
LE->>SP: indicateDamage — skipped entirely if anything was blocked
SP-->>CPL: ClientboundHurtAnimationPacket — that one player, nobody else
LE->>LE: dead? checkTotemDeathProtection, else the death sound, then die
SP->>SP: die — message, loot, byte 3, and no call up to LivingEntity
SP-->>CPL: ClientboundPlayerCombatKillPacket — the death screen opens
Note over LE,CPL: twenty ticks later, for a mob — tickDeath broadcasts byte 60 and removes it
```
`ServerLevel.broadcastDamageEvent` sends the type, the three entity ids and an
optional position to every tracking player *and the victim*, and it runs
**before** the knockback, not after. A successful block replaces it entirely:
if the blocking component absorbed anything, `BlocksAttacks.onBlocked` plays
the block sound and **no damage event is broadcast at all**, so a blocked hit
puts no flash on anyone's screen. Only then does `Entity.markHurt` queue the
velocity packet, and `LivingEntity.dealDefaultKnockback` compute a direction
(from the projectile for a projectile, from the source position otherwise)
for `LivingEntity.knockback`, which scales it by one minus
`Attributes.KNOCKBACK_RESISTANCE`. `ServerPlayer.indicateDamage` ends that
call, and is skipped when anything was blocked. The hurt sound comes after
all of it — after the death check, not with the flash.
Six packets carry the hit itself. `ClientboundDamageEventPacket` (type holder,
causing and direct entity ids, optional position) and
`ClientboundSetEntityMotionPacket` go to every tracker and the victim,
`ClientboundHurtAnimationPacket` and `ClientboundSetHealthPacket` only ever to
one player about themselves, `ClientboundSetEntityDataPacket` carries
`LivingEntity.DATA_HEALTH_ID` for a mob, and `ClientboundEntityEventPacket`
carries one byte — 3 for death, 60 for the poof, 35 for a totem. Inbound,
only the respawn command.
**The damage amount never crosses the wire.** The client picks a sound and a
flash from the type and infers magnitude from health — and only for your own
player, in `LocalPlayer.hurtTo`, the one place a hit is deduced from a health
*drop*. `LivingEntity.handleDamageEvent` sets `Entity.invulnerableTime` to 20
and the flash to 10 and plays the sound, touching health not at all.
## Death, or not
If health has reached zero, `LivingEntity.checkTotemDeathProtection` looks for
`DataComponents.DEATH_PROTECTION` in either hand — unless the source is
`DamageTypeTags.BYPASSES_INVULNERABILITY`, which is why `/kill` cannot be
totemed — and on a hit consumes one, sets health to one, applies
`DeathProtection`'s effects and broadcasts byte 35.
Otherwise `LivingEntity.die` runs, and its order matters. Kill credit is read
from the attribution references written a few lines earlier,
`LivingEntity.handleKillingBlow` sets `LivingEntity.dead`, and
`CombatTracker.recheckStatus` runs. Then — with a causing entity, **only if
`Entity.killedEntity` on it agrees**; with none, unconditionally — the death
game event fires,
`LivingEntity.dropAllDeathLoot` runs and the wither rose is planted. The
entity-event byte sits *outside* that veto and goes out to every watcher
**before** `Pose.DYING` is set — the byte first, the pose after.
Loot needs a *recent* player. `LivingEntity.dropAllDeathLoot` reads *killed by
a player* off `LivingEntity.lastHurtByPlayerMemoryTime` still being above
zero, so an attribution older than a hundred ticks costs the loot context its
`LootContextParams.LAST_DAMAGE_PLAYER` and its luck, and costs the experience
drop entirely unless the mob is an always-dropper. `LivingEntity.shouldDropLoot`
gates loot on `GameRules.MOB_DROPS` and on not being a baby — but
`Monster.shouldDropLoot` drops the baby condition, which is why baby zombies
and piglins do drop. `LivingEntity.shouldDropExperience` mirrors it. Only
`LivingEntity.dropEquipment` is outside both.
## The death screen, and what the client does alone
**`ServerPlayer.die` never calls up.** It reimplements the sequence, so
`LivingEntity.handleKillingBlow` never runs and `LivingEntity.dead` stays
**false** on a dead player for the whole death screen. It reads the message
from the combat log at the top and calls `CombatTracker.recheckStatus` at the
bottom, so the log survives long enough to be read.
`GameRules.SHOW_DEATH_MESSAGES` gates more than the chat line: with the rule
off the message is never assembled and `ClientboundPlayerCombatKillPacket`
carries an **empty** component, so the death screen still opens and says
nothing. `ServerPlayer.die` also forgives neutral mobs under
`GameRules.FORGIVE_DEAD_PLAYERS`, drops the inventory through
`Player.dropEquipment` unless `GameRules.KEEP_INVENTORY`, and calls
`ServerGamePacketListenerImpl.markClientUnloadedAfterDeath`. `DeathScreen`
opens unless `GameRules.IMMEDIATE_RESPAWN`, and
[player anatomy](../player/player-anatomy.md) owns the object that comes back.
Two things then happen without a packet. **The client kills mobs on its own,
from one byte**: `LivingEntity.handleEntityEvent` for byte 3 sets a non-player
entity's health to zero and runs `LivingEntity.die` locally, so the twenty
tick animation in `LivingEntity.tickDeath` is client-driven, not a
consequence of a health update. And **`CombatTracker` clears itself** — after
`CombatTracker.RESET_DAMAGE_STATUS_TIME` out of combat or
`CombatTracker.RESET_COMBAT_STATUS_TIME` in it — from four places: a twenty
tick timer in `LivingEntity.tick`, the top of `CombatTracker.recordDamage`,
and both of `LivingEntity.die` and `ServerPlayer.die`, so a hit after a long lull discards the old log before
filing its entry.
## Everything that calls it
`Entity.hurt` and `Entity.hurtOrSimulate` are deprecated final wrappers over
the abstract method: the second picks a side and returns `Entity.hurtClient`
off a client level, the first does nothing there. `Entity.hurtClient` has
nine declarations counting the base, and every one only ever answers *did
this connect* — including `RemotePlayer`, which returns true unconditionally
so a client-side arrow can play its own effects without knowing any numbers.
Environmental damage is ticked from more places than *base tick* suggests.
Fire (once per twenty fire ticks) is in `Entity.baseTick`, lava (four at a
time) in `Entity.lavaHurt`, and suffocation, world-border and drowning damage
in `LivingEntity.baseTick`. **Freezing and cramming are not**: freezing is in
`LivingEntity.aiStep` every forty ticks, and cramming inside
`LivingEntity.pushEntities`, called at the end of the same method under
`GameRules.MAX_ENTITY_CRAMMING`.
## The five families of non-living damage
`Entity.hurtServer` is abstract: there is no default behaviour at all, so
every branch of the tree answers for itself, and the answers have nothing to
do with anything above. **Fifty-four** files override it — a fifty-fifth,
`Entity` itself, only declares it — thirty-three
of them `LivingEntity` descendants — `ArmorStand` among them, a
`LivingEntity` despite having no AI — and **twenty-one** not. Those
twenty-one never touch armour, i-frames, absorption or the combat tracker,
and fall into five families:
- **Nothing happens.** `Entity.hurtServer` returns false, no side effect:
`AreaEffectCloud`, `Display`, `Interaction`, `LightningBolt`, `Marker`,
`OminousItemSpawner`, `PrimedTnt`, `EvokerFangs`, `EyeOfEnder` and
`AbstractHurtingProjectile`.
- **A flinch and nothing else.** `FallingBlockEntity` and `Projectile` call
`Entity.markHurt` so the client sees the shove, then still return false.
- **An int of health, no armour, no i-frames.** `ItemEntity` and
`ExperienceOrb` keep a plain integer and subtract the damage from it.
`ItemEntity` adds two rules: a `Mob` source is refused under
`GameRules.MOB_GRIEFING`, and the stack itself is asked
`ItemStack.canBeHurtBy`.
- **One hit destroys.** `BlockAttachedEntity` kills itself and drops its item
(mob-griefing gated too), `ShulkerBullet` is destroyed with particles
without checking invulnerability at all, `EndCrystal` explodes and is
**immune to the `EnderDragon` that eats it**, and `EnderDragonPart` forwards
the whole call to `EnderDragon.hurt` on its parent. `ItemFrame` takes two
hits — the first pops the item, the second falls through to
`BlockAttachedEntity` and breaks the frame — while a *fixed* frame answers
only to `DamageTypeTags.BYPASSES_INVULNERABILITY` or a creative player.
- **An accumulator.** `VehicleEntity` adds *damage × 10* to a field through
`VehicleEntity.setDamage` and destroys itself past 40. A creative player
gets the hurt direction, the hurt timer and the accumulator all the same;
the flag only redirects the destruction to `Entity.discard`, which drops
nothing. `MinecartTNT` adds one rule on top: a *burning* `AbstractArrow`
explodes it before the accumulator is even reached.
The per-class detail is in [the non-living damage
table](../../reference/non-living-damage.md).
## Where to look
`DamageSource` · `DamageType` · `DamageTypes` · `DamageSources` ·
`DamageTypeTags` · `Entity.hurtServer` · `Entity.isInvulnerableToBase` ·
`ServerPlayer.hurtServer` · `Player.hurtServer` · `LivingEntity.hurtServer` ·
`LivingEntity.applyItemBlocking` · `BlocksAttacks` ·
`LivingEntity.actuallyHurt` · `LivingEntity.getDamageAfterArmorAbsorb` ·
`LivingEntity.getDamageAfterMagicAbsorb` · `CombatRules` ·
`LivingEntity.dealDefaultKnockback` · `ServerLevel.broadcastDamageEvent` ·
`LivingEntity.checkTotemDeathProtection` · `LivingEntity.die` ·
`ServerPlayer.die` · `LivingEntity.dropAllDeathLoot` · `Entity.killedEntity` ·
`LivingEntity.tickDeath` · `CombatTracker` · `CombatEntry` · `FallLocation` ·
`ClientboundDamageEventPacket` · `ClientboundPlayerCombatKillPacket` ·
`VehicleEntity` · `ItemFrame` — and [attributes](attributes.md) for armour,
toughness and knockback resistance as attribute values.
---
*Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes `tools/verify_names.py`.*
---
# VII · Items and inventories
> Verified against **Minecraft 26.2** · Part VII · The things you carry: what a stack is, what happens when you use one, how two machines agree about a chestful of them, and the three engines that make them out of data.
A block is a position in a grid and an entity is a thing in the world. An
item is neither: it is a *stack*, and a stack only exists inside something
else — a hand, a slot, a chest, a recipe grid, a dropped `ItemEntity`, a
packet. That makes this part the one where the two programs disagree most
often and most cheaply, because almost everything a player does with items
is predicted locally and confirmed afterwards. A player recognises the part
by the small lies: the sword that swings before the server has heard about
it, the chest whose contents appear a tick late, the bow that fires when you
let go rather than when it finished drawing, and the dungeon chest that is
empty until the moment somebody opens it.
## The shape of the part
Part VII is **two tiers**, not a chain. The first three pages are the
vocabulary — what a stack is, what using one does, and how a set of them is
kept in agreement across the wire. The last five are three independent
data-driven engines that produce or decorate stacks. Recipes, enchanting and
loot tables lean on the vocabulary and hand each other nothing;
*contexts and predicates* is the outlier, because its subject is not a stack
at all — it is the question engine the other engines happen to run on, and it
can be watched first.
```mermaid
flowchart TD
IS["Items and stacks — what a stack is"]
UI["Using an item — what holding the button does"]
CM["Containers and menus — how two machines agree about a set of them"]
RE["Recipes — an arrangement of stacks becomes another stack"]
EN["Enchantments — a named modifier other systems ask about"]
EC["Enchanting — how one lands on an item"]
CP["Contexts and predicates — the engine that answers questions about the world"]
LO["Loot tables — its worked example"]
IS -- "a stack is a diff over a prototype" --> UI
UI -- "and a slot is where one lives" --> CM
CM -- "an arrangement in a grid" --> RE
IS -- "a modifier on the stack" --> EN
EN --> EC
CP -- "which needs no stack at all" --> LO
```
## Before you start
[Data components](../foundations/data-components.md) is the hard
prerequisite: a stack *is* an item plus a component patch, and this part
never re-teaches the component system. [Codecs, NBT and
JSON](../foundations/codecs-nbt-json.md) for the four ways one stack is
serialised, and [identifiers and
registries](../foundations/identifiers-and-registries.md) and [the resource
system](../foundations/resource-system.md) for where recipes, enchantments
and loot tables come from and when. They come from three different places, and
the difference bites: recipes are a reload listener and loot tables a
reloadable registry layer, so `/reload` rebuilds both — while enchantments are
a world-load dynamic registry that `/reload` never re-reads at all.
Three ordering facts matter more than they look. [The server
tick](../server/server-tick.md) drains the packet queue before any level ticks,
which is why a click and its correction land in the same tick; [the level
tick](../server/server-level-tick.md) decides *when* a menu's changes are
broadcast, which is what makes a hopper's delivery visibly late; and [block
interaction](../blocks/block-interaction.md)
is how a chest gets opened in the first place, which is where two of these
pages start.
## Watch in this order
The first three in order, then the engines in any order you like.
1. [Items and stacks](items-and-stacks.md) — an `Item` holds almost no
data, and an `ItemStack` holds a *diff*. The prototype it is a diff
against does not exist until the first data-pack load.
2. [Using an item](using-an-item.md) — a meal and a bow, which are one
machine read two ways. The client's countdown never stops at zero: the
meal ends when a single byte arrives, and the bow ends when you let go.
3. [Containers and menus](containers-and-menus.md) — a shift-click out of a
chest. One packet goes up, nothing comes back, and agreement is silence,
because the server adopted the client's *claim* as its new baseline.
4. [Recipes](recipes.md) — eight planks and an empty middle. No recipe ever
crosses the wire, and yet the client holds the whole contents of every
recipe it has unlocked.
5. [Enchantments](enchantments.md) — there are no enchantment subclasses.
Fire Aspect is a data-pack record whose "melee only" rule is one loot
condition, and the burn that follows belongs to something else entirely.
6. [Enchanting](enchanting.md) — the five paths that change what an item is
enchanted with, one of which runs backwards. The
seed is per player, saved, and sent to the client, which is why the
Standard Galactic gibberish is stable and why an anvil never changes
what the table is offering.
7. [Contexts and predicates](contexts-and-predicates.md) — the engine that
answers *is this true here*. Twelve of its twenty-six parameter sets
never roll a loot table at all: `/execute if predicate`, entity
selectors, advancement triggers and villager trades all run on it.
8. [Loot tables](loot-tables.md) — the worked example, and the part's
closer. A dungeon chest is genuinely empty on disk, and the first thing
to touch it — a hopper will do — commits the roll with no luck at all.
Watched as lectures, five and six are the pair to keep together: *what an
enchantment is* and *how you get one*. Seven and eight are the other pair,
and seven is the one Part XIII comes back for.
## Reference this part uses
Two were written for it. [Enchantment
hooks](../../reference/enchantment-hooks.md) — every `EnchantmentHelper`
entry point with the classes that call it, which is the enchantment
system's real interface. [Loot context parameter
sets](../../reference/loot-context-params.md) — all twenty-six, with the
keys each one requires and allows. Then [data
components](../../reference/components.md),
[packets](../../reference/packets.md),
[registries](../../reference/registries.md) and [diagram
lanes](../../reference/lanes.md).
The part stops at the slot. What a player's own inventory is, and how the
hand relates to the equipment slots, is [player
anatomy](../player/player-anatomy.md) in Part VIII; how a held stack picks
the model you actually see is Part XI's, in [models and
atlases](../rendering/models-and-atlases.md#how-an-item-picks-its-model). The
container click is *not* on the prediction ledger — it carries no sequence
number and opens no window — and [prediction and
acknowledgement](../client/prediction-and-acks.md) in Part X is where that
distinction is drawn.
---
*Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes `tools/verify_names.py`.*
---
# Items and stacks
> Verified against **Minecraft 26.2** · Part VII · A diamond pickaxe sits in a hotbar slot, is compared against its neighbours, is sent to a client, and finally loses its last point of durability.
A diamond pickaxe is in your hotbar. The `Item` behind it,
`Items.DIAMOND_PICKAXE`, is a single object shared by every diamond pickaxe
that has ever existed on this server, and it holds four fields: a description
id, a crafting remainder, a feature-flag set, and its own registry holder. Not
the stack size. Not the mining speed. Not the durability. All of that is data
components ([data components](../foundations/data-components.md)) — and they
do not live on the `Item` either. They live on the item's `Holder.Reference`
in `BuiltInRegistries.ITEM`, and the *stack* borrows that map as a read-only
prototype and stores only the ways it differs from it. Two things follow, and
they run through the whole page. **That prototype does not exist until the
first data-pack load**: `Item.components` throws until a reload binds it, and
`Item.CODEC_WITH_BOUND_COMPONENTS` exists purely to refuse an item whose
components are not bound yet. And **a stack whose components equal its item's
defaults carries an empty patch** — every item's prototype contains
`DataComponents.ENCHANTMENTS` set to `ItemEnchantments.EMPTY`, so *enchanted
with nothing* and *never enchanted* are not merely equal, they are the same
object state, indistinguishable on disk and on the wire.
## The cast
| class | what it decides | thread |
|---|---|---|
| `Item` | the behaviour hooks, and four fields that are not components | both main threads |
| `Item.Properties` | the builder — which produces an *initializer*, never a component map | class-init, wherever the bootstrap runs |
| `Holder.Reference` | where an item's default components actually live, and whether they exist yet | written on a main thread at reload |
| `DataComponentInitializers` | the pile of pending default maps, one entry per registered item | built on the background executor |
| `ItemStack` | a holder, a count, a pop time and a patched map — the mutable thing in a slot | both main threads |
| `PatchedDataComponentMap` | prototype plus patch, and the copy-on-write flag that makes copying a stack free | wherever its stack is |
| `ItemStackTemplate` | the immutable stack: what a stack looks like inside a component, a particle or a recipe | both |
| `ItemEntity` | a stack that is an entity, with a five-minute clock and a merge rule | server main, mirrored on the client |
## Four fields, and only one of them is really data
`ItemStack` is a final class holding exactly four things: two ints, a registry
holder, and the interesting one.
```mermaid
flowchart LR
subgraph S["ItemStack — the object in the slot"]
CNT["count"]
POP["popTime"]
HOL["item, a Holder of Item"]
MAP["components"]
end
MAP --> PDM["PatchedDataComponentMap"]
PDM --> PATCH["the patch: only what differs, plus a tombstone per removal"]
PDM -. "prototype, borrowed and never written" .-> DEF
HOL --> HR["Holder.Reference in BuiltInRegistries.ITEM"]
HR --> IT["Item — descriptionId, craftingRemainingItem, requiredFeatures, its own holder"]
HR --> DEF["DataComponentMap — the item's defaults, bound at reload"]
```
The dotted arrow is the shape of the whole system. A stack does not own its
defaults and cannot change them: it points at a holder, and the holder owns
one `DataComponentMap` shared by every stack of that item in both programs.
`ItemStack.typeHolder` answers `Items.AIR`'s holder rather than null for an
empty stack, which is why `ItemStack.getItem` never returns null either. The
pop time is the odd one out: it is the five-tick squeeze the hotbar icon does
when something lands in it, set to 5 by `Inventory` when a stack grows and by
`ClientPacketListener.handleContainerSetSlot` when a slot update makes a
hotbar stack larger, counted down by `ItemStack.inventoryTick` on **both**
sides, and read by `Hud.extractSlot`, which scales the icon while it is above
zero and hands the drawing to `GuiGraphicsExtractor` either way. It is
ordinary shared state that only the client has any use for — and
`ItemStack.copy` carries it across, so the animation survives being copied
into a menu slot.
`Item.Properties` is the builder used at class-init, and its output is not a
component map. `Item.Properties.component` and every convenience over it —
`Item.Properties.stacksTo`, `Item.Properties.durability`,
`Item.Properties.food`, `Item.Properties.tool`, `Item.Properties.spear`,
`Item.Properties.equippable`, `Item.Properties.useCooldown` — fold one more
step onto a `DataComponentInitializers.Initializer`, a function that will be
run against a `DataComponentMap.Builder` later, with a
`HolderLookup.Provider` in hand.
## The map that does not exist yet
Registration and definition happen at completely different times, on
different threads, in different phases of the program.
```mermaid
sequenceDiagram
participant Boot as Bootstrap
participant Items as Items
participant Item as Item
participant BIR as BuiltInRegistries
participant Worker as Worker
participant MS as MinecraftServer
Boot->>BIR: bootStrap, then createContents touches Items.AIR
BIR->>Items: the class loads, running 1177 static initialisers
Items->>Item: one constructor per field, each given an Item.Properties
Item->>BIR: DATA_COMPONENT_INITIALIZERS.add, one Initializer per item
Note over Item: every item now exists and NO item has components
Note over MS: much later — a world load, or a reload command
MS->>Worker: ReloadableServerResources.loadResources
Worker->>BIR: DataComponentInitializers.build against the reloaded registries and tags
Note over Worker: DataComponentMap.Builder.build runs each item validator here
Worker-->>MS: a PendingComponents per registry
Note over MS: back on the main thread
MS->>Item: PendingComponents.apply, then bindComponents on every holder
```
Between those two halves an `Item` is a live object whose `Item.components`
call ends in a null check reading *Components not bound yet*;
`Holder.Reference.areComponentsBound` is the polite way to ask, and
`Item.CODEC_WITH_BOUND_COMPONENTS` refuses to name an unbound item at all. The
client goes through the same gate on its own registries, in
`RegistryDataCollector` while `ClientConfigurationPacketListenerImpl` finishes
configuration — so a client on the title screen has componentless items too.
Almost none of that map actually varies with the data pack. Every initializer
starts by copying `DataComponents.COMMON_ITEM_COMPONENTS` — ten entries every
item in the game gets, including the stack size of 64 and the empty
enchantment list the hook rests on — and `Item.Properties.component` bakes
literal Java values on top. Only `Item.Properties.delayedComponent` and
`Item.Properties.delayedHolderComponent` read the context, and there are
**twenty** call sites between them in the entire game, all in `Item` and
`Items`: fire resistance resolving `DamageTypeTags.IS_FIRE`, the banner
patterns, the goat horn, the jukebox songs, the egg variants, the spear's
damage type. `Item.Properties.repairable` is the near miss — it takes a tag
and is still eager, because it takes a lookup from
`BuiltInRegistries.acquireBootstrapRegistrationLookup` at class-init and
stores an unresolved `HolderSet` rather than waiting.
## A patch with tombstones, and a copy that copies nothing
`PatchedDataComponentMap` holds three things: the prototype, a patch map, and
a copy-on-write flag. Reads consult the patch and fall through to the
prototype. Writes are where the design shows.
`PatchedDataComponentMap.set` compares the new value against the prototype's
and, if they are equal, **removes** the entry rather than storing it — the
patch never contains a value the item already had.
`PatchedDataComponentMap.remove` does the opposite trick: when the prototype
has the component, it cannot simply drop the key, so it writes an empty
optional as a tombstone meaning *this one is deliberately gone*. A patch is
therefore a diff in both directions, which is exactly what
`DataComponentPatch` serialises: additions, then removals.
`ItemStack.copy` and `PatchedDataComponentMap.asPatch` both hand out the
*same* patch map and set the copy-on-write flag on it, and every mutating
method calls `PatchedDataComponentMap.ensureMapOwnership` first, which forks
the map on the first write and clears the flag. Copying a stack — which menus,
recipes, hover text and `ServerPlayerGameMode.destroyBlock` all do constantly
— allocates one small object and copies no component data at all.
The wire form falls straight out of it. `ItemStack.OPTIONAL_STREAM_CODEC`
writes the count, then the item holder, then `PatchedDataComponentMap.asPatch`
— **the patch only, never the prototype**, because the receiver already has
the same prototype bound to the same holder. A count of zero is the whole
encoding of an empty stack.
## When two stacks are the same stack
Five static methods on `ItemStack` answer five different versions of that
question, and menus, recipes and the renderer each want a different one.
| method | compares | used for |
|---|---|---|
| `ItemStack.isSameItem` | the item holder only | *is this the same kind of thing* |
| `ItemStack.isSameItemSameComponents` | the item, then the whole `PatchedDataComponentMap` | stacking, and every *are these interchangeable* test |
| `ItemStack.matches` | the count as well | container synchronisation ([containers and menus](containers-and-menus.md)) |
| `ItemStack.matchesIgnoringComponents` | everything except the component types a predicate excuses | the held-item swap animation |
| `ItemStack.hashItemAndComponents` | the item's hash and the effective component map's | keying stacks in maps |
The second row is where the hook pays off. `PatchedDataComponentMap` compares
by prototype **and** patch, so for two stacks of the same item it amounts to
comparing the patches — and since a patch can never hold a value equal to the
prototype's, two pickaxes with the same damage are equal whether one reached
that state by being set explicitly or by never being touched.
The fourth row exists for one component. `DataComponents.DAMAGE` is the only
component type in the game declared with
`DataComponentType.Builder.ignoreSwapAnimation`, and
`ItemInHandRenderer.shouldInstantlyReplaceVisibleItem` passes exactly that
flag as the predicate — which is why a pickaxe losing a point of durability
does not re-play the lower-and-raise animation. And a trap sits under all five
rows: `ItemStack.EMPTY` is a singleton but is not identified by reference,
because `ItemStack.isEmpty` also answers true for `Items.AIR` and for any
count at or below zero.
## Two validators, one rule, two spellings
Durability and stackability are mutually exclusive, and the game says so
twice — in different places, at different times, against different
components.
| | the reload validator | `ItemStack.validateStrict` |
|---|---|---|
| installed by | `Item.Properties.finalizeInitializer` | — |
| rejects | `DataComponents.DAMAGE` on a stackable item | `DataComponents.MAX_DAMAGE` on a stackable item, and a count over the maximum |
| runs inside | `DataComponentMap.Builder.build` | `ItemInput`, `ItemStackTemplate.create`, `ItemStack.applyComponentsAndValidate` |
| when | at reload, on the background executor | when a command, a template or a component patch builds a stack |
| on failure | throws, failing the reload | depends on the caller: `ItemInput` throws a command syntax error, the other two log and yield `ItemStack.EMPTY` or restore the previous patch |
Neither is reached from a network decode, and the client's stacks are proved a
third way instead. Exactly **one** serverbound packet in the protocol carries
an `ItemStack`: `ServerboundSetCreativeModeSlotPacket`, whose
`ItemStack.OPTIONAL_UNTRUSTED_STREAM_CODEC` is wrapped in
`ItemStack.validatedStreamCodec` — which runs no validator at all, but
re-encodes the decoded stack through `ItemStack.CODEC` and throws if that
fails. The contents of a container stack are checked one level down, but on
the other path: `ItemStack.validateContainedItemSizes` runs inside
`ItemStack.validateStrict`, over `DataComponents.CONTAINER`,
`DataComponents.BUNDLE_CONTENTS` and `DataComponents.CHARGED_PROJECTILES` — so
a shulker box full of impossible stacks is caught by a command, not at the
creative slot's door.
## An item, a count, some components — said three ways
`ItemInstance` is the read-only contract those validators are written
against: `ItemInstance.count`, `ItemInstance.getMaxStackSize`, and — through
`TypedInstance` and `DataComponentGetter` — five `TypedInstance.is` overloads
for tags, holder sets, raw items, holders and resource keys, to which
`ItemStack.is` adds a sixth taking a predicate. Its default
`ItemInstance.getMaxStackSize` answers **1** when
`DataComponents.MAX_STACK_SIZE` is missing — which for a bound item never
happens, because the common set puts 64 there.
Two classes implement it. `ItemStack` is the mutable one that lives in slots.
`ItemStackTemplate` is an immutable record of a `Holder- `, a count and a
raw `DataComponentPatch` — what a stack becomes when it is stored *inside*
something else: `ItemContainerContents` (so a shulker box's contents are
templates, not stacks), `BundleContents`, `ChargedProjectiles`,
`ItemParticleOption`, `HoverEvent`, `UseRemainder`, the recipe classes, and
`Item`'s own crafting remainder. Its constructor refuses a count of zero or
`Items.AIR`, so there is no empty template; but
`ItemStackTemplate.create` and `ItemStackTemplate.apply`, which materialise a
real stack, run `ItemStack.validateStrict` on the result and answer
`ItemStack.EMPTY` with a log line rather than throwing.
## A pickaxe's last point of durability
Durability is not one component: `ItemStack.isDamageableItem` demands
`DataComponents.MAX_DAMAGE` present, `DataComponents.UNBREAKABLE` absent, and
`DataComponents.DAMAGE` present. Take a diamond pickaxe one block short of
breaking, and mine that block.
`ServerPlayerGameMode.destroyBlock` copies the held stack *before* touching it
— that copy is what `Block.playerDestroy` later hands the loot table, so the
drops are decided by the tool as it was — then calls `ItemStack.mineBlock`,
which delegates to `Item.mineBlock` and awards `Stats.ITEM_USED` if the item
claims the block. The base `Item.mineBlock` is pure component work: it reads
`DataComponents.TOOL`, does nothing without one, and damages the stack by
`Tool.damagePerBlock` only on a server level, only when that number is above
zero, and only when the block's destroy speed is not zero — so instant-break
plants cost a tool nothing, and neither does a tool that declares no per-block
damage.
`ItemStack.hurtAndBreak` is the way in for almost everything, and the overload
that does the work demands a `ServerLevel` outright. The overloads taking a `LivingEntity`
pattern-match on the entity's level and **silently do nothing** on the client,
which is why a client never predicts durability. The amount then goes through
`EnchantmentHelper.processDurabilityChange`
([enchantments](enchantments.md)), which is how Unbreaking turns a point of
damage into no damage at all, and a player with `Player.hasInfiniteMaterials`
short-circuits to zero before even that. If anything survives, the stack fires
`CriteriaTriggers.ITEM_DURABILITY_CHANGED` for a real player, writes the new
`DataComponents.DAMAGE`, and — this being the last point — finds
`ItemStack.isBroken` true, **shrinks itself by one**, and calls the break hook
it was handed. For equipment that hook is `LivingEntity.onEquippedItemBroken`,
which strips the item's attribute modifiers and broadcasts an entity event
(47 for the main hand); `ServerPlayer.onEquippedItemBroken` adds
`Stats.ITEM_BROKEN` on top.
The client is told in one byte. `LivingEntity.handleEntityEvent` turns event
47 into `LivingEntity.breakItem`, which plays `DataComponents.BREAK_SOUND`
from the stack still in that slot and spawns five item particles; the empty
slot itself arrives separately, as a container update. Two siblings round the
family out: `ItemStack.hurtWithoutBreaking` clamps one short of the maximum,
and `ItemStack.hurtAndConvertOnBreak` transmutes rather than vanishing.
What the player actually watched was three methods on `Item`.
`Item.isBarVisible` is *is this stack damaged*, `Item.getBarWidth` scales the
damage over thirteen pixels — the width `Item.MAX_BAR_WIDTH` names, though
`Item.getBarWidth` spells the number out and no reader of the constant
survives the decompile — and
`Item.getBarColor`
sweeps a hue from green to red. `GuiGraphicsExtractor` draws the two-pixel bar
under the icon from those three answers and nothing else.
## The tick a stack gets, and the stack that is an entity
`ItemStack.inventoryTick` runs on both sides and does exactly one thing there:
decrement the pop time. It forwards to `Item.inventoryTick` only for a
`ServerLevel` — the hook's parameter is declared as one, so it cannot be
otherwise — and exactly two items override that hook, `CompassItem` and
`MapItem`. It has two callers: `Inventory.tick`, from `Player.aiStep`, walks
the thirty-six ordinary slots and tells the selected one it is the main hand;
`EntityEquipment.tick`, from `LivingEntity.aiStep`, walks the worn and held
slots of every living entity. A stack that leaves an inventory altogether
becomes an `ItemEntity`, which keeps it in a synched data entry
([synched entity data](../entities/synched-entity-data.md)) rather than a
plain field, counts up to a 6000-tick lifetime, and folds itself into
neighbours through `ItemEntity.mergeWithNeighbours` — a merge that keeps the
*smaller* of the two ages, so a fresh drop rejuvenates an old one. Blocks
reach it through `Block.popResource`
([block breaking](../blocks/block-breaking.md)).
## What this page hands off
Everything a stack *does* when a player holds down the use key — the
prediction, the countdown, the consumable and cooldown components, the
completion packet — is the next lecture: [using an item](using-an-item.md).
How two machines agree about a set of stacks in a screen is
[containers and menus](containers-and-menus.md); the component system itself
is [data components](../foundations/data-components.md), catalogued in the
[components reference](../../reference/components.md). And how an item picks
the model, texture and tint you see in the slot is **not** this part's subject
at all: it is Part XI's, in [models and atlases](../rendering/models-and-atlases.md#how-an-item-picks-its-model).
## Where to look
`Item` · `Item.Properties` · `Items` · `BuiltInRegistries.ITEM` ·
`DataComponentInitializers` · `Holder.Reference` ·
`ReloadableServerResources.loadResources` · `ItemStack` ·
`PatchedDataComponentMap` · `DataComponentPatch` · `ItemInstance` ·
`TypedInstance` · `ItemStackTemplate` · `ItemContainerContents` ·
`ServerPlayerGameMode.destroyBlock` · `ItemEntity` · `Inventory.tick` ·
`EntityEquipment.tick` · `ServerboundSetCreativeModeSlotPacket`
---
*Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes `tools/verify_names.py`.*
---
# Using an item
> Verified against **Minecraft 26.2** · Part VII · A player holds the use key on a piece of cooked beef, then holds it on a bow — one countdown, two endings.
You hold the use button on cooked beef and thirty-two ticks later you have
eaten it. You hold the same button on a bow and nothing happens at all until
you let go. These are the same machine: `Item.use` starts both,
`LivingEntity.useItemRemaining` counts down on the client *and* the server
for both, `ItemStack.onUseTick` runs every tick on both sides, and
`LivingEntity.stopUsingItem` ends both. What differs is whether the count is
allowed to mean anything — and the consequence is stranger than the
difference. **The client's countdown does not stop at zero.** The meal ends
because one byte arrives from the server, a `ClientboundEntityEventPacket`
carrying `EntityEvent.USE_ITEM_COMPLETE`. The bow's countdown starts at
72000 and would take an hour to expire, so the shot is fired by a
`ServerboundPlayerActionPacket` instead — the same packet, carrying the same
action, that the meal would read as *the player changed their mind*.
## The cast
| class | what it decides | thread |
|---|---|---|
| `Minecraft` | which key edge starts a use, and which one ends it | client main |
| `MultiPlayerGameMode` | the client's own copy of the use, and the packet that reports it | client main |
| `ServerGamePacketListenerImpl` | which handler each packet reaches, and whether the rotation is snapped first | server main |
| `LivingEntity` | the countdown, the synched flags, and both endings | both main |
| `ItemStack` | the dispatch into the item, and the after-use side effects | both |
| `Item` | `Item.use`, `Item.getUseDuration`, `Item.releaseUsing`, `Item.useOnRelease` | both |
| `Consumable` | everything the meal does, on both sides | both |
| `ProjectileWeaponItem` | ammo, spread and the arrow — the counting on both sides, the spawning only on the server | both, but only the server's counts |
The block-target branch of a use — right-clicking a chest rather than the
air — leaves at `MultiPlayerGameMode.useItemOn` and belongs to
[block interaction](../blocks/block-interaction.md); the stack itself is
[items and stacks](items-and-stacks.md).
## The two paths, side by side
| | the meal | the bow |
|---|---|---|
| what `Item.use` does | the default body finds `DataComponents.CONSUMABLE` and calls `Consumable.startConsuming` | `BowItem.use` overrides it, checks `Player.getProjectile`, calls `LivingEntity.startUsingItem` |
| the refusal | `Consumable.canConsume` asks `Player.canEat`, for food only | no arrow anywhere and no infinite materials — `InteractionResult.FAIL` |
| `Item.getUseDuration` | `Consumable.consumeTicks`, **32** for `Consumables.DEFAULT_FOOD` | **72000**, `Item.APPROXIMATELY_INFINITE_USE_DURATION` in all but name |
| what `ItemStack.onUseTick` does | particles and the chew sound, on both sides | nothing — `BowItem` does not override `Item.onUseTick` |
| how it ends | the count reaches zero on the server | the use key comes up on the client |
| the packet that ends it | `ClientboundEntityEventPacket`, downward | `ServerboundPlayerActionPacket`, upward |
| is the ending acknowledged | yes — that *is* the acknowledgement | **no**, nothing answers a release |
| `Item.releaseUsing` | not overridden, returns false, so letting go simply abandons the meal | `BowItem.releaseUsing` is where the whole shot lives |
| `ItemStack.useOnRelease` | false | **also false** |
| what the client predicts | the entire meal, twice over | the animation, and nothing else |
The last-but-one row is the one that looks wrong. `ItemStack.useOnRelease`
is the third term of the completion guard and it is the obvious name for
"this item is finished by letting go" — but **`CrossbowItem.useOnRelease` is
its only override in the tree**. The bow and the trident return
`Item.useOnRelease`'s default false. They are release-ended not because a
predicate says so, but because their `Item.getUseDuration` is an hour long and
their `Item.releaseUsing` does the work. The spyglass returns the default too
and is not release-ended at all: at 1200 ticks its countdown really can run
out.
## Starting: the client finishes before it speaks
`Minecraft.handleKeybinds` sees the press and calls `Minecraft.startUseItem`,
which sets the four-tick `Minecraft.rightClickDelay` itself, refuses while
`LocalPlayer.isHandsBusy`, and walks both hands for a target. With nothing
under the crosshair it reaches `MultiPlayerGameMode.useItem`, which opens a prediction window first
(`MultiPlayerGameMode.startPrediction`,
[prediction and acks](../client/prediction-and-acks.md)) and does everything
else inside it: it builds the `ServerboundUseItemPacket` — hand, sequence
number and both rotations — then consults the client's own `ItemCooldowns`,
and runs `ItemStack.use` **locally first** only if the item is off cooldown.
The packet goes up either way; the cooldown suppresses the prediction, not the
report of it. The prediction is complete before a byte leaves the client, for
the meal and the bow alike. Both answer `InteractionResult.CONSUME`, whose
`InteractionResult.SwingSource.NONE` is why neither swings the arm, although
`ItemInHandRenderer.itemUsed` still runs — the small dip the item makes as
the use begins. An *instant* use, one whose `ItemStack.getUseDuration` is
zero, returns its outcome the other way instead, through
`InteractionResult.Success.heldItemTransformedTo`, which both game modes
unwrap and write back into the hand.
On the server, `ServerGamePacketListenerImpl.handleUseItem` acknowledges the
sequence number, **snaps the player's rotation to the one in the packet**,
and calls `ServerPlayerGameMode.useItem`. That snap is what makes the
release strange later, because the release packet does no such thing. And
`ServerPlayerGameMode.useItem` ends on a deliberate omission: it normally
re-sends the player's inventory with
`AbstractContainerMenu.sendAllDataToRemote`
([containers and menus](containers-and-menus.md)), but not when the use it
just ran started a multi-tick one. **While you are eating or drawing, the
server declines to correct your inventory.**
## While it runs: the flag on the wire and the flag the client believes
`LivingEntity.startUsingItem` writes two bits of
`LivingEntity.DATA_LIVING_ENTITY_FLAGS`
([synched entity data](../entities/synched-entity-data.md)) — bit one for
*using*, bit two *assigned* the hand, so a main-hand use clears it — and
only on the server. `LivingEntity.isUsingItem` and
`LivingEntity.getUsedItemHand` read those bits, which is how every other
client knows your arm is up.
Your own client does not read them. `LocalPlayer.isUsingItem` overrides the
base and returns a private local flag set by `LocalPlayer.startUsingItem`;
`LocalPlayer.getUsedItemHand` likewise answers from a local field.
Reconciliation happens afterwards, in **both** directions:
`LocalPlayer.onSyncedDataUpdated` compares the arriving bits with the local
flag and will start a use the client never predicted, or stop one it did.
The base `LivingEntity.onSyncedDataUpdated` makes the matching repair on
every *other* entity's copy — it adopts the held stack and re-derives
`LivingEntity.useItemRemaining` from `ItemStack.getUseDuration`, which is why
a remote player drawing a bow animates correctly although your client never
saw the press.
One abandonment rule is shared, and it lives a level above the countdown.
`LivingEntity.tick` calls the private `LivingEntity.updatingUsingItem`,
which compares the hand's current stack with the remembered one using
`ItemStack.isSameItem` and calls `LivingEntity.stopUsingItem` if they
differ; only on a match does it call `LivingEntity.updateUsingItem`, the
countdown proper. The comparison is item identity, not components, so
**swapping a bowl for a stew aborts the meal while a durability tick on the
bow does not abort the draw.**
## Every tick, and what the bow does instead
`LivingEntity.updateUsingItem` offers `ItemStack.onUseTick` the count
*before* it decrements, so a thirty-two-tick meal is offered 32 down to 1
and never 0. For the meal that one call is the whole visible experience:
`Consumable.shouldEmitParticlesAndSounds` is true once more than
`Consumable.CONSUME_EFFECTS_START_FRACTION` of the duration has elapsed and
the remaining count is a multiple of `Consumable.CONSUME_EFFECTS_INTERVAL`,
and `Consumable.emitParticlesAndSounds` then spawns five item particles
through `LivingEntity.spawnItemParticles` — behind
`Consumable.hasConsumeParticles`, which drinks turn off — and plays the chew
sound, which they do not. `Level.addParticle` does nothing on the server, so
the crumbs are pure client simulation.
For the bow the call does nothing whatever: `BowItem` does not override
`Item.onUseTick`, and the base body is empty. Everything you see while
drawing is the renderer reading the same counter the logic is decrementing.
`ItemInHandRenderer` computes the draw curve for `ItemUseAnimation.BOW` from
`LivingEntity.getUseItemRemainingTicks`, and the three-stage bow texture is
not code at all: *items/bow.json* is a *condition* on *using_item* wrapping
a *range_dispatch* on the *use_duration* property (`UseDuration`), scaled so
its thresholds are fractions of `BowItem.MAX_DRAW_DURATION`.
The crossbow is the exception that makes the rule legible. It *does*
override `Item.onUseTick`, and that body is entirely server-side: it plays
the three `CrossbowItem.ChargingSounds` at fixed fractions of
`CrossbowItem.getChargeDuration` and, on reaching one, writes
`DataComponents.CHARGED_PROJECTILES` onto the stack. Its client half is a
render-thread computation — `CrossbowPull` and `ItemInHandRenderer` both
call `CrossbowItem.getChargeDuration`, which calls
`EnchantmentHelper.modifyCrossbowChargingTime`
([enchantments](enchantments.md)). **An enchantment hook, evaluated on the
render thread, once per frame, to pick one of three textures.**
> **For a 1.21-era reader.** There is no bow-pull item property class left
> to hunt for. The old *pulling* / *pull* pair is now the shared
> `UseDuration` range-select property plus a *using_item* condition, both
> declared in the item's JSON; the crossbow keeps a bespoke one,
> `CrossbowPull`, only because its denominator is enchantable.
## Moving while you use
Neither path slows you through movement code — both read one component.
`LocalPlayer.modifyInput` scales the movement input by
`LocalPlayer.itemUseSpeedMultiplier`, which reads
`UseEffects.speedMultiplier` off the stack, unless the player is riding, and
`LocalPlayer.isSlowDueToUsingItem` blocks sprinting because
`UseEffects.canSprint` is false. The famous twenty per cent is the default
in `UseEffects.DEFAULT`, which sits in
`DataComponents.COMMON_ITEM_COMPONENTS`, so every item has one — and neither
cooked beef nor the bow overrides it, which is why **drawing a bow slows you
by exactly as much as eating does, through exactly the same field.**
`Item.Properties.spear` is the definition that overrides it outright, with a
`UseEffects` that permits sprinting, suppresses vibrations and multiplies
speed by one; the attack that ends *that* use is a different packet again
([the sword swing](../player/the-sword-swing.md)).
## The ending, in one picture
```mermaid
flowchart TD
T["LivingEntity.updateUsingItem, both sides, every tick"]
T --> A["ItemStack.onUseTick with the count before the decrement"]
A --> B["decrement LivingEntity.useItemRemaining"]
B --> C{"reached zero"}
C -- no --> T
C -- yes --> D{"on the server"}
D -- "no, this is the client" --> W["keep counting into the negatives and wait"]
D -- yes --> E{"ItemStack.useOnRelease"}
E -- "false, everything but a crossbow" --> F["LivingEntity.completeUsingItem"]
E -- "true, a crossbow" --> T
F --> G["ServerPlayer.completeUsingItem sends ClientboundEntityEventPacket 9 first"]
G --> H["ItemStack.finishUsingItem, then LivingEntity.stopUsingItem"]
R["the use key comes up, Minecraft.handleKeybinds"] --> S["MultiPlayerGameMode.releaseUsingItem sends RELEASE_USE_ITEM, then releases locally"]
S --> P["ServerGamePacketListenerImpl.handlePlayerAction, no ack, no sequence"]
P --> Q["LivingEntity.releaseUsingItem, each side on its own copy"]
Q --> K["ItemStack.releaseUsing, then LivingEntity.stopUsingItem either way"]
K -- "Item.releaseUsing returned true" --> L["the shot, then the after-use side effects"]
K -- "returned false" --> M["the meal is simply abandoned"]
```
The **client's branch has no exit**. Nothing on the client ever reaches
`LivingEntity.completeUsingItem` from the countdown — it is called from
`Player.handleEntityEvent` when event 9 arrives, and nowhere else on that
side. The counter meanwhile keeps falling past zero and only the renderer
notices: `ItemInHandRenderer` draws a use pose solely while
`LivingEntity.getUseItemRemainingTicks` is positive, so the arm drops at
tick 32 whether or not the packet has landed.
And **`ItemStack.useOnRelease` does not mean "ends on release"**. It means
*do not let the countdown finish this, and give it one more tick when the
key comes up*: `LivingEntity.releaseUsingItem` calls
`LivingEntity.updatingUsingItem` again when it is true, so a crossbow gets a
final `CrossbowItem.onUseTick` in which it can still latch the charge. No
other item asks for that. Release is also not only a key-up —
`LivingEntity.releaseUsingItem` has five other call sites:
`LivingEntity.completeUsingItem` itself, when the stack turned out not to
match the hand; `CrossbowAttack` and `RangedCrossbowAttackGoal`, which is
how a pillager fires
([AI goals and brains](../entities/ai-goals-and-brains.md)); and
`BrushItem.onUseTick` twice, ending its own use from inside the tick.
## The meal, tick by tick
```mermaid
sequenceDiagram
participant MC as Minecraft
participant LP as LocalPlayer
participant MPGM as MultiPlayerGameMode
participant Wire as the network
participant SGPL as ServerGamePacketListenerImpl
participant SP as ServerPlayer
participant Cons as Consumable
Note over MC,Cons: tick 0, the press
MC->>MPGM: startUseItem, nothing under the crosshair
MPGM->>Cons: ItemStack.use, the default Item.use finds DataComponents.CONSUMABLE
Cons->>LP: startConsuming, canConsume asks Player.canEat, then startUsingItem
MPGM->>Wire: ServerboundUseItemPacket, hand and sequence and both rotations
Wire->>SGPL: handleUseItem acks the sequence and snaps the rotation
SGPL->>SP: the same Item.use, remaining = 32, the two flag bits are written
Note over MC,Cons: ticks 1 to 31, both sides
LP->>LP: ItemStack.onUseTick, five particles every fourth tick and the chew sound
SP->>SP: the same call, particles discarded, sound broadcast to everyone else
Note over MC,Cons: tick 32, the count reaching zero on the server alone
SP->>Wire: ClientboundEntityEventPacket, EntityEvent.USE_ITEM_COMPLETE
SP->>Cons: ItemStack.finishUsingItem, Consumable.onConsume, FoodData.eat
Wire->>LP: Player.handleEntityEvent replays completeUsingItem locally
SP->>Wire: ClientboundSetHealthPacket, same tick, overwrites the prediction
Note over MC,Cons: a later tick
SP->>Wire: broadcastChanges corrects the stack count
```
The replay is the interesting half. `Consumable.onConsume` runs on **both**
sides and three parts of it do not: the `Stats.ITEM_USED` award and
`CriteriaTriggers.CONSUME_ITEM` need a `ServerPlayer`, the `ConsumeEffect`s
in `Consumable.onConsumeEffects` sit behind a server-side guard, and
`GameEvent.EAT` is a no-op because `ClientLevel.gameEvent` has an empty
body. Particles, sound, the `ConsumableListener` walk that finds
`FoodProperties`, and the `ItemStack.consume` shrink all run on both — and
every one of those client mutations is then overwritten. That is why a
chorus fruit's teleport is never predicted while the hunger bar's jump is
([hunger and experience](../player/hunger-and-experience.md)).
One meal, two exactly-once sound strategies. The chew sound goes through
`Player.playSound`, which names the eater as the entity to *exclude*, so the
server broadcasts it to everyone else and the eater's client plays it
locally. The `FoodProperties` eat and burp sounds pass no exclusion at all —
and `ClientLevel.playSeededSound` plays a sound only when the excluded
entity *is* the local player — so those reach the eater as the server's
broadcast alone.
## The bow, tick by tick
```mermaid
sequenceDiagram
participant MC as Minecraft
participant LP as LocalPlayer
participant MPGM as MultiPlayerGameMode
participant Wire as the network
participant SGPL as ServerGamePacketListenerImpl
participant SP as ServerPlayer
participant BowI as BowItem
Note over MC,BowI: tick 0, the press
MC->>MPGM: startUseItem, nothing under the crosshair
MPGM->>BowI: ItemStack.use, BowItem.use asks Player.getProjectile
BowI->>LP: startUsingItem, remaining = 72000
MPGM->>Wire: ServerboundUseItemPacket, hand and sequence and both rotations
Wire->>SGPL: handleUseItem acks the sequence and snaps the rotation
SGPL->>SP: the same BowItem.use, remaining = 72000, the two flag bits are written
Note over MC,BowI: every tick after that, both sides
LP->>LP: the count falls, onUseTick is empty, the model reads the use duration
SP->>SP: the count falls, and nothing else happens at all
Note over MC,BowI: the tick the key comes up
MC->>MPGM: releaseUsingItem
MPGM->>Wire: ServerboundPlayerActionPacket, RELEASE_USE_ITEM, sequence zero
MPGM->>BowI: LivingEntity.releaseUsingItem, BowItem.releaseUsing on the client
BowI->>LP: no ServerLevel, so no ammo and no arrow, then stopUsingItem
Wire->>SGPL: handlePlayerAction, the rotation is whatever the server last heard
SGPL->>SP: LivingEntity.releaseUsingItem
SP->>BowI: BowItem.releaseUsing, ProjectileWeaponItem.draw then shoot
BowI->>Wire: Projectile.spawnProjectile, then ClientboundAddEntityPacket
SGPL-->>MPGM: nothing acknowledges the release itself
Note over MC,BowI: a later tick
SP->>Wire: the container sync corrects the arrow count and the bow's damage
```
`BowItem.releaseUsing` runs on both sides and gets nowhere on one of them.
It measures the draw as `BowItem.getUseDuration` minus the remaining count,
puts it through `BowItem.getPowerForTime` — a curve on the draw time in
seconds, clamped at one after `BowItem.MAX_DRAW_DURATION` ticks — and
returns false below a tenth of full power, which is why a tap of the button
neither shoots nor costs durability. Above it, `ProjectileWeaponItem.draw`
decides how many arrows leave the string
(`EnchantmentHelper.processProjectileCount`) and
`ProjectileWeaponItem.useAmmo` decides whether an arrow is actually spent
(`EnchantmentHelper.processAmmoUse`). Both consult a `ServerLevel` and fall
back to one and zero otherwise, and `ProjectileWeaponItem.shoot` is itself
inside a `ServerLevel` test — so **on the client the draw produces a single
phantom arrow marked `DataComponents.INTANGIBLE_PROJECTILE`, spends nothing
and shoots nothing.**
On the server it is five enchantment hooks and one ordering worth
remembering. `EnchantmentHelper.processProjectileSpread` fans a multishot
volley, and each arrow is aimed by `BowItem.shootProjectile` through
`Projectile.shootFromRotation` using the **server's** rotation — which the
release packet never updated, so the shot goes where the last movement
packet said you were looking. `Projectile.spawnProjectile` aims, adds the
entity to the level, and only *afterwards* calls
`Projectile.applyOnProjectileSpawned`, which runs
`EnchantmentHelper.onProjectileSpawned` **twice** when ammo and weapon are
different items: once for the arrow's stack and once for the bow's.
`ItemStack.hurtAndBreak` takes the durability after each arrow, and the
volley breaks off if the bow dies mid-flight.
Only when `Item.releaseUsing` returns true does `ItemStack.releaseUsing` run
`ItemStack.applyAfterUseComponentSideEffects` — the same private step
`ItemStack.finishUsingItem` runs for the meal, converting
`DataComponents.USE_REMAINDER` into the empty bowl and starting
`DataComponents.USE_COOLDOWN`. (For an instant use it runs from
`ItemStack.use` instead, and only for a zero-duration success.) Cooldowns
are grouped rather than per-item: `ItemCooldowns.getCooldownGroup` returns
`UseCooldown.cooldownGroup` when the component names one and the item's
`Identifier` otherwise, both sides own an `ItemCooldowns` — the client's is
a real prediction, consulted before it will even attempt a use — and
`ServerItemCooldowns.onCooldownStarted` mirrors the server's as one
`ClientboundCooldownPacket` naming a *group*.
## What the ending never carries
The release is the least-answered packet in the pipeline.
`ServerGamePacketListenerImpl.handlePlayerAction` treats
`ServerboundPlayerActionPacket.Action.RELEASE_USE_ITEM` in one line — call
`LivingEntity.releaseUsingItem`, return — with no ack, no sequence number
consumed, not even a spectator check. Everything the client learns about its
own shot arrives as ordinary world traffic. The arrow is the quick one: adding
it to the level starts its tracking inside the same call, so the spawn packet
leaves on that tick. The spent ammo and the bow's damage wait for a container
slot update ([containers and menus](containers-and-menus.md)) and the cleared
using-flag for entity data it has already acted on, both a tick or more
later. The shoot sound is broadcast with no
exclusion, so — by the same rule as the burp — the shooter hears the
server's copy and never their own.
The completion is barely richer. There is no *you ate this* packet: event 9
tells the client to re-derive the outcome from components it already holds,
and `ClientboundSetHealthPacket` corrects whatever it got wrong. The single
override of `Item.finishUsingItem` in the whole tree is
`SpyglassItem.finishUsingItem`, which plays a sound — the spyglass being the
one item whose completion is worth a sound of its own — reached either at its
1200-tick duration or, like any use, the moment you let go.
## Where to look
`Minecraft.handleKeybinds` · `Minecraft.startUseItem` ·
`MultiPlayerGameMode.useItem` · `MultiPlayerGameMode.releaseUsingItem` ·
`ServerGamePacketListenerImpl.handleUseItem` ·
`ServerGamePacketListenerImpl.handlePlayerAction` ·
`ServerPlayerGameMode.useItem` · `ItemStack.use` · `Item.use` ·
`LivingEntity.startUsingItem` · `LivingEntity.updatingUsingItem` ·
`LivingEntity.updateUsingItem` · `LivingEntity.completeUsingItem` ·
`LivingEntity.releaseUsingItem` · `ItemStack.releaseUsing` ·
`ItemStack.useOnRelease` · `Consumable` · `UseEffects` · `UseCooldown` ·
`BowItem` · `CrossbowItem` · `TridentItem` · `ProjectileWeaponItem` ·
`SpyglassItem` · `ItemInHandRenderer` · `UseDuration`
---
*Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes `tools/verify_names.py`.*
---
# Containers and menus
> Verified against **Minecraft 26.2** · Part VII · A player shift-clicks a stack out of a chest: one packet goes up, the server re-runs the same code against the real container, and nothing comes back down.
You are standing at a chest with a stack of cobblestone in the top-left
slot, and you shift-click it. Your machine moves the stack into your hotbar
immediately, and one `ServerboundContainerClickPacket` leaves for the
server. The server runs the *same* method against the real
`ChestBlockEntity`, gets its own answer, compares the two — and if they
agree, sends nothing at all. That is the steady state: **one packet up, zero
packets down.** There is no transaction acknowledgement in this protocol;
agreement is silence. What makes silence safe is the part nobody expects.
The click packet does not carry the stacks the client thinks it produced, it
carries a CRC32C *hash* per changed slot, and before comparing anything the
server **installs that claim as its own belief about the client**. It adopts
the client's belief object, never the client's data.
## The cast
| class | what it decides | thread |
|---|---|---|
| `Container` | storage, and nothing about who is looking at it — `Container.getItem`, `Container.setItem`, `Container.stillValid` | wherever its owner runs |
| `AbstractContainerMenu` | the slot list, the cursor, the state id, the click state machine, and the two baselines (one for listeners, one for the wire) | both main threads |
| `Slot` | GUI policy — `Slot.mayPlace`, `Slot.mayPickup`, `Slot.getMaxStackSize` — and the guarded mutations a click goes through | both main threads |
| `Inventory` | the player's own storage, present as slots in nearly every menu that opens — the lectern's one book slot is the exception | both main threads |
| `MenuType` / `MenuProvider` | the registry entry with the *screen-side* constructor, and the server-side factory a block hands to `ServerPlayer.openMenu` | client / server main |
| `ContainerSynchronizer` | the diffing channel to the connection, and every menu's writer but one — one per `ServerPlayer`, shared by every menu that player opens | server main |
| `RemoteSlot` | what the server believes the client is holding in one slot, as either a stack or a hash | server main (`RemoteSlot.PLACEHOLDER` on the client) |
| `HashedStack` | the client's claim about one slot after its own click | created on the client, matched on the server |
## The chest you see is not the chest
The server's `ChestMenu` is built from a `MenuProvider` that
`ChestBlock.getMenuProvider` produces and `ChestBlock.useWithoutItem` hands
to `ServerPlayer.openMenu` ([block interaction](../blocks/block-interaction.md)),
and its `Container` is the real `ChestBlockEntity` — or a
`CompoundContainer` over both halves, for a double chest, from a separate
anonymous provider. The client's `ChestMenu` is built
from `MenuType`'s screen-side constructor by `MenuScreens`, and that
constructor makes a **fresh `SimpleContainer`**. The client's chest contents
have no connection to the block entity beyond the packet stream. Its
`AbstractContainerMenu.stillValid` would be a lie for the same reason —
`SimpleContainer.stillValid` returns true unconditionally — but the question is
never put to it: every call site of `AbstractContainerMenu.stillValid` in the
game is on the server. A client never closes its own menu because it walked
away.
The other half of that asymmetry is the synchronizer. Only
`ServerPlayer.initMenu` ever calls
`AbstractContainerMenu.setSynchronizer`, so a client menu has none, and
every one of its `RemoteSlot`s stays `RemoteSlot.PLACEHOLDER`, whose
`RemoteSlot.matches` always answers true, so the *wire* half of
`AbstractContainerMenu.broadcastChanges` is inert on the client by
construction. The listener half is not: `BeaconScreen`, `ItemCombinerScreen`,
`LecternScreen` and the creative inventory all register a real
`ContainerListener`, which is how the anvil's rename field repopulates itself
after a slot changes.
Three smaller facts about the model that a reader will otherwise trip over.
A menu with a null `MenuType` cannot be opened over the network at all —
`AbstractContainerMenu.getType` throws — which is why the player's own
`InventoryMenu` is pinned to `InventoryMenu.CONTAINER_ID`, zero, and built
independently on both sides, and why the mount menus need a
`ClientboundMountScreenOpenPacket` of their own instead of
`ClientboundOpenScreenPacket`. Every other menu takes an id from
`ServerPlayer.nextContainerCounter`, which cycles 1 to 100 and never reaches
zero. And `Slot.mayPlace` and `Container.canPlaceItem` are unrelated
questions: the first is GUI policy and defaults to true without consulting
the container at all, the second is what hopper automation reads. A player
and a hopper can have different rights over the same slot. So can a player
and an item: `Container.getMaxStackSize` defaults to **99**, and the
familiar 64 comes only from the per-stack overload, which takes the minimum
with the item's own maximum.
## One shift-click, end to end
A three-row `ChestMenu` numbers slots 0–26 for the chest, 27–53 for the
player's three main inventory rows and 54–62 for the hotbar, because
`AbstractContainerMenu.addStandardInventorySlots` adds the main rows before
the hotbar. Watch what the wire carries, and what it does not.
```mermaid
sequenceDiagram
participant MPGM as MultiPlayerGameMode
participant ChestM as ChestMenu
participant Wire as the network
participant SGPL as ServerGamePacketListenerImpl
participant ACM as AbstractContainerMenu
participant RemS as RemoteSlot
participant CSync as ContainerSynchronizer
MPGM->>MPGM: copy every slot's stack, before touching anything
MPGM->>ChestM: clicked with QUICK_MOVE, predicted on the twin
ChestM->>ChestM: quickMoveStack, then moveItemStackTo backwards
MPGM->>Wire: ServerboundContainerClickPacket, state id plus changed slots as hashes
Wire->>SGPL: handleContainerClick, at the top of the server tick
SGPL->>ACM: suppressRemoteUpdates, then the same clicked on the real chest
SGPL->>ACM: setRemoteSlotUnsafe per claimed hash, then setRemoteCarried
ACM->>RemS: receive, which throws away any concrete stack it held
SGPL->>ACM: resumeRemoteUpdates, then broadcastChanges
ACM->>RemS: matches?
RemS->>RemS: the hash agrees, so adopt the server's stack as the copy
RemS-->>CSync: nothing, sendSlotChange is never reached
CSync-->>Wire: nothing goes down
```
**The press.** `AbstractContainerScreen.mouseClicked` resolves the hovered
slot, sees an empty `AbstractContainerMenu.getCarried` and a held shift, and
chooses `ContainerInput.QUICK_MOVE` on the press. Two near neighbours are
release-path instead, from `AbstractContainerScreen.mouseReleased`: a
shift-click with a *non-empty* cursor, and the shift-double-click sweep,
which issues a separate `ContainerInput.QUICK_MOVE` — and a separate packet
— for every matching slot in the same container.
**The snapshot, then the prediction.**
`MultiPlayerGameMode.handleContainerInput` copies every slot's stack before
touching anything, because that copy is what the packet's diff is built
against. Then it runs `AbstractContainerMenu.clicked` on the client's own
menu. The `ContainerInput.QUICK_MOVE` branch loops
`AbstractContainerMenu.quickMoveStack` while the clicked slot keeps yielding
the same item, and it runs only for button 0 or 1, a non-negative slot index
and a slot that `Slot.mayPickup` allows.
**The move, and its two passes.** `ChestMenu.quickMoveStack` sees the index
is inside the chest and calls `AbstractContainerMenu.moveItemStackTo` over
the player's range with the backwards flag, which scans from the last slot
down — **the hotbar first, from the right**. That method makes two passes.
The first runs only if the stack is stackable at all, and merges into every
existing compatible stack across the whole range
(`ItemStack.isSameItemSameComponents`, topped up to `Slot.getMaxStackSize`).
The second runs only if something is left, finds the first empty slot that
`Slot.mayPlace` accepts, places what that slot's cap allows and **breaks**.
One empty slot per call — which is why the caller loops. Note which pass
consults policy: **the merge pass never asks `Slot.mayPlace`**, so a slot
that would refuse the item on placement can still be topped up by a
shift-click when it already holds a matching stack. Note also what
`ChestMenu.quickMoveStack` never calls: `Slot.onTake`.
`InventoryMenu.quickMoveStack` does. Shift-clicking out of a chest and out
of the crafting result are structurally different operations.
**The packet.** The client diffs the post-click slots against its snapshot,
hashes each changed stack with `HashedStack.create`, and sends
`ServerboundContainerClickPacket` with the container id, the client's
*last-known* state id, the slot, the button, the `ContainerInput`, up to
**128** changed slots as hashes, and the cursor as one more.
**The real move.** The server takes the ladder below, then — with
`AbstractContainerMenu.suppressRemoteUpdates` held — runs the identical
`AbstractContainerMenu.clicked` path against the real `ChestBlockEntity` and
the real `Inventory`. `Slot.setChanged` reaches `BlockEntity.setChanged`,
which calls `Level.blockEntityChanged` to mark the chunk and
`Level.updateNeighbourForOutputSignal` to re-derive the comparator output
([block entities](../blocks/block-entities.md)). The whole click is wrapped
in a try/catch that builds a crash report category naming the menu class,
the slot and the button.
**Installing the claim, then comparing.**
`AbstractContainerMenu.setRemoteSlotUnsafe` writes each hash the client sent
into the matching `RemoteSlot`; `RemoteSlot.Synchronized` **discards any
concrete stack it was holding** and keeps the hash alone. An out-of-range
index is logged at debug and ignored rather than rejected. Then
`AbstractContainerMenu.setRemoteCarried`, then
`AbstractContainerMenu.resumeRemoteUpdates`, then
`AbstractContainerMenu.broadcastChanges`, which makes one pass over the
slots offering each to `AbstractContainerMenu.triggerSlotListeners` for
advancements and to `AbstractContainerMenu.synchronizeSlotToRemote` for the
wire, then the cursor, then the data slots. Where the hash agrees,
`RemoteSlot.Synchronized` **promotes it to a concrete copy of the server's
own stack** and nothing is sent.
The advancement channel sees one state per click, not one per slot touched:
`AbstractContainerMenu.triggerSlotListeners` runs only from
`AbstractContainerMenu.broadcastChanges` and
`AbstractContainerMenu.broadcastFullState` — and for a chest nothing calls
back into the menu mid-click to reach either. That is a fact about the chest,
not about menus: `CrafterSlot`, the anvil's and the smithing table's
`ItemCombinerMenu` slots and the crafting grid all call
`AbstractContainerMenu.slotsChanged` from `Container.setChanged`, and the base
`AbstractContainerMenu.slotsChanged` is a bare
`AbstractContainerMenu.broadcastChanges`. The click's
own broadcast runs
*after* `AbstractContainerMenu.resumeRemoteUpdates`, so suppression is not
in force by then, and `ServerPlayer`'s `ContainerListener` filters to slots
that are not a `ResultSlot` and whose container is the player's own
`Inventory` before firing `CriteriaTriggers.INVENTORY_CHANGED` anyway.
## The ladder the server climbs before it believes you
`ServerGamePacketListenerImpl.handleContainerClick` is four tests and a
fork, and the interesting thing about it is how much of it ends in *nothing
sent* rather than a correction.
```mermaid
flowchart TD
P["ServerboundContainerClickPacket, on the server main thread via PacketUtils.ensureRunningOnSameThread"] --> ID{"does containerId match the open menu?"}
ID -->|"no"| D1["dropped in total silence, nothing logged, nothing sent"]
ID -->|"yes"| SPEC{"spectator, or dead or dying?"}
SPEC -->|"yes"| D2["sendAllDataToRemote, a full resync, and the click never runs"]
SPEC -->|"no"| SV{"AbstractContainerMenu.stillValid"}
SV -->|"fails"| D3["logged at debug, nothing sent, the menu is not closed here"]
SV -->|"passes"| IX{"AbstractContainerMenu.isValidSlotIndex"}
IX -->|"fails"| D4["logged at debug, nothing sent, nothing corrected"]
IX -->|"passes"| ST["compare the packet's state id with the menu's, BEFORE anything is applied"]
ST --> AP["suppressRemoteUpdates, run clicked, install the claimed hashes, resumeRemoteUpdates"]
AP --> Q{"was that state id stale?"}
Q -->|"stale"| FULL["broadcastFullState, ending in sendAllDataToRemote, one ClientboundContainerSetContentPacket with a fresh state id"]
Q -->|"current"| BC["broadcastChanges, every slot against its RemoteSlot"]
BC --> AG{"RemoteSlot.matches"}
AG -->|"agrees"| SIL["the hash becomes a concrete copy of the server's stack, and nothing is sent"]
AG -->|"disagrees"| ONE["one ClientboundContainerSetSlotPacket for that slot, with a fresh state id"]
```
The order of the last two boxes is the load-bearing part: **the state id is
compared before the click is applied and acted on after**, so a click that
quotes a stale id still runs, and still runs first. Its result is simply
published wholesale instead of diffed.
`AbstractContainerMenu.isValidSlotIndex` deserves suspicion. It accepts −1,
accepts `AbstractContainerMenu.SLOT_CLICKED_OUTSIDE`, and otherwise only
asks whether the index is below the slot count — so **every negative index
passes it**. The branches that need a floor test for it themselves; the two
that do not, `ContainerInput.SWAP` and the painting phase of
`ContainerInput.QUICK_CRAFT`, index the list directly, and the click's own
try/catch turns the failure into a `ReportedException` rather than swallowing
it: what swallows it is `PacketProcessor`, which logs a game-listener error and
carries on. An out-of-range click is therefore neither corrected nor fatal, but
it is loudly logged, and it closes nothing:
`ServerGamePacketListenerImpl.handleContainerClose` for its part validates
nothing at all, not even the container id, and goes straight to
`ServerPlayer.doCloseContainer`.
## Why hashes, and why only in one direction
`HashedStack.create` produces either `HashedStack.EMPTY` or a
`HashedStack.ActualItem` of item holder, count and a `HashedPatchMap` — one
CRC32C integer per *added* component plus the plain set of *removed* ones.
Each integer comes from running the component's own codec into
`HashOps.CRC32C_INSTANCE`, a `DynamicOps` whose output **is** the hash
([codecs, NBT and JSON](../foundations/codecs-nbt-json.md) owns that
mechanism). Nothing is serialised on the way and there is no intermediate
byte form. `HashedPatchMap.matches` then checks the removed set, the added
count, and each component's hash in turn.
Two things follow. The client is *asserting a belief*, not authoring state —
a hash cannot be turned back into an item, so a client that lies here can
only fail to match — and the traffic is 128 integers rather than 128 full
`DataComponentPatch`es. The asymmetry is exact: **only the client ever calls
`HashedStack.create`, and only the server ever calls `HashedStack.matches`.**
Hashing is not free-standing on the server either: `ServerPlayer`'s
`ContainerSynchronizer` carries a 256-entry component-hash cache shared by
every menu that player opens, and `ContainerSynchronizer.createSlot` hands
each new `RemoteSlot.Synchronized` a `HashedPatchMap.HashGenerator` backed
by it.
One channel is deliberately outside all of this. `DataSlot` and
`ContainerData` — furnace progress, enchanting cost, lectern page — travel
as `ClientboundContainerSetDataPacket`, whose id and value are written as
**shorts**, and they carry two independent baselines:
`DataSlot.checkAndClearUpdateFlag` for the listeners and
`AbstractContainerMenu.remoteDataSlots` for the network, compared
separately. The network comparison is a plain integer test, so a furnace's
progress bar is not covered by the hash-agreement silence and is re-sent
every time it changes.
## The state id, and the three places it moves
`AbstractContainerMenu.incrementStateId` is a wrapping 15-bit counter — it
masks to 32767 — and it answers exactly one question: *has the client
applied every slot correction I have sent?* It has three call sites in the
whole game. Two are inside `ServerPlayer`'s synchronizer, behind
`ContainerSynchronizer.sendInitialData` and
`ContainerSynchronizer.sendSlotChange`;
`ContainerSynchronizer.sendCarriedChange` and
`ContainerSynchronizer.sendDataChange` do not bump it — and
`ClientboundSetCursorItemPacket` carries no id whatever, while
`ClientboundContainerSetDataPacket` carries the container's but never a state
id. The third is `CraftingMenu.slotChangedCraftingGrid`,
below. The client never generates one: `AbstractContainerMenu.setItem` and
`AbstractContainerMenu.initializeContents` simply store whatever arrived and
quote it back on the next click. A click quoting a stale id means
corrections are still in flight, and the server stops diffing and resends
everything.
## Where in the tick a broadcast happens
Not one place, and the difference is observable. Packets are drained
**before any level ticks** ([the server tick](../server/server-tick.md)), so
a click and any correction it produces both happen at the top of the tick
and reach the client the same tick. `ServerPlayer.tick` then calls
`AbstractContainerMenu.broadcastChanges` again from the level's **entity**
phase, which runs *before* the block-entity phase
([the level tick](../server/server-level-tick.md)). And `ServerPlayer.doTick`
— driven by the connection after every level has finished — repeats only the
`AbstractContainerMenu.stillValid` distance test, without a broadcast.
So the distance test happens twice a tick and only the first is accompanied
by a broadcast. A hopper that pushes an item into a chest whose menu is open
therefore runs in the block-entity phase, after that tick's only broadcast,
and **nothing calls back into the menu to say so** —
`SimpleContainer.setChanged` is empty, `BlockEntity.setChanged` marks the chunk
and re-derives the comparator output and stops there,
`Inventory.setChanged` only bumps a counter. You see the hopper's
item one tick late.
Closing has its own surprise. The cursor belongs to the menu, not the
player, so closing one would destroy it: `AbstractContainerMenu.removed`
rescues it explicitly, dropping it in the world if the player has been
removed or has disconnected and calling `Inventory.placeItemBackInInventory`
otherwise, the whole method gated on being a `ServerPlayer`, which is what
makes that safe. The rescue sends one `ClientboundSetPlayerInventoryPacket`
per slot it fills, and runs *before* `AbstractContainerMenu.transferState`,
which copies both the listener baseline and the remote beliefs across every
container-and-slot pair the closing menu and `InventoryMenu` share. For a
chest that is the 36 main and hotbar slots — not armour, not the offhand, not
the 2×2 grid, not the crafting result — so changes to those four are re-sent
and nothing else is.
## The seven click kinds
`ContainerInput` has seven values, and the button number means something
different in every one. `ClickAction` — `ClickAction.PRIMARY` and
`ClickAction.SECONDARY` — is *not* on the wire; it is derived inside
`AbstractContainerMenu.doClick` on both sides and handed to the item
override hooks `ItemStack.overrideStackedOnOther` and
`ItemStack.overrideOtherStackedOnMe`, which is how a `BundleItem`
intercepts a click before ordinary slot logic runs.
| value | the gesture | what the button number means | packets per gesture |
|---|---|---|---|
| `ContainerInput.PICKUP` | the ordinary click | 0 left, 1 right — and on `AbstractContainerMenu.SLOT_CLICKED_OUTSIDE`, drop all or drop one | 1 |
| `ContainerInput.QUICK_MOVE` | shift-click | 0 or 1, the same in a real slot — and outside the window, the drop-all and drop-one of `ContainerInput.PICKUP` | 1, or one per matching slot for the shift-double-click sweep |
| `ContainerInput.SWAP` | a hotbar key, or `Inventory.SLOT_OFFHAND` for the offhand key | the destination index: 0–8, or 40 | 1 |
| `ContainerInput.CLONE` | creative middle-click | ignored, but the player must pass `Player.hasInfiniteMaterials` | 1 |
| `ContainerInput.THROW` | Q over a slot, cursor empty — and also a click *outside* the window with an empty cursor, which the server's branch then ignores because it demands a non-negative index | 0 drops one, 1 (control-Q) drops the stack and keeps going while the slot yields the same item | 1 |
| `ContainerInput.QUICK_CRAFT` | drag-painting | a packed mask that `AbstractContainerMenu.getQuickcraftHeader` and `AbstractContainerMenu.getQuickcraftType` split into a phase (start, continue, end) and a mode (even split, one each, clone) | **painted slots plus two** — `AbstractContainerScreen.quickCraftToSlots` sends a start, one continue per slot, and an end |
| `ContainerInput.PICKUP_ALL` | double-click to collect | 0 scans forwards, 1 backwards, over two passes that skip already-full stacks first | 1 |
An unknown id on the wire is not rejected. `ContainerInput`'s id mapper is
built with a zero-out-of-bounds strategy, so a malformed click **decodes as
`ContainerInput.PICKUP`**.
## Two paths that are not this protocol at all
**Creative mode is a parallel protocol, not a variation.**
`CreativeModeInventoryScreen` overrides
`AbstractContainerScreen.slotClicked` and drives a menu of its own, and its
writes go up as `ServerboundSetCreativeModeSlotPacket`. That is the one
packet in the game whose *item* data the server adopts — a rename, a sign and a
jigsaw block are adopted as text:
`ServerGamePacketListenerImpl.handleSetCreativeModeSlot` takes the client's
`ItemStack` verbatim into the slot through `Slot.setByPlayer`, behind only a
`Player.hasInfiniteMaterials` check, a feature-flag check, a slot range of
1 to 45 and a count cap — then writes the same stack into the remote belief
with `AbstractContainerMenu.setRemoteSlot` to suppress the echo. A negative
slot number means *drop it in the world instead*, and that branch is the one
place here with a rate limiter on it.
**The crafting result is a second, unsuppressed channel.**
`CraftingMenu.slotChangedCraftingGrid` recomputes the result slot and then
sends a `ClientboundContainerSetSlotPacket` **straight down the connection**,
bumping the state id itself and bypassing `ContainerSynchronizer` entirely.
It is reached from `CraftingMenu.slotsChanged`,
`CraftingMenu.finishPlacingRecipe` and `InventoryMenu.slotsChanged`, and the
first of those fires mid-click, because `TransientCraftingContainer` calls
`AbstractContainerMenu.slotsChanged` from its own `Container.setItem` and
from any `Container.removeItem` that took something. So the one path that
transmits *during* a click is also the one path
`AbstractContainerMenu.suppressRemoteUpdates` does not cover — that flag
guards only `AbstractContainerMenu.synchronizeSlotToRemote` and its two
siblings. [Recipes](recipes.md) is where the recomputation itself lives.
Everything else on the wire is bookkeeping around those two:
`ClientboundOpenScreenPacket` and `ClientboundContainerClosePacket` for the
lifetime, `ClientboundContainerSetContentPacket` and
`ClientboundSetCursorItemPacket` for a resync,
`ServerboundContainerButtonClickPacket` for the lectern, enchanting, loom and
stonecutter buttons,
`ServerboundContainerSlotStateChangedPacket` for crafter toggles,
`ServerboundSelectBundleItemPacket` for a bundle, and
`ServerboundSetCarriedItemPacket` with `ClientboundSetHeldSlotPacket` for the
hotbar selection — which, despite the name, has nothing to do with the
cursor. A structure chest fills itself on first open through
`RandomizableContainer.unpackLootTable` ([loot tables](loot-tables.md)), and
`ContainerLevelAccess` — `ContainerLevelAccess.NULL` on the client — is the
position capability a block-anchored menu tests distance against. None of
the click protocol is data-driven: `BuiltInRegistries.MENU` supplies only
the type.
## Where to look
`ServerGamePacketListenerImpl.handleContainerClick` ·
`MultiPlayerGameMode.handleContainerInput` ·
`AbstractContainerMenu.doClick` · `AbstractContainerMenu.moveItemStackTo` ·
`AbstractContainerMenu.broadcastChanges` ·
`AbstractContainerMenu.setRemoteSlotUnsafe` · `RemoteSlot.Synchronized` ·
`HashedStack` · `HashedPatchMap` · `HashOps` · `ContainerSynchronizer` ·
`ContainerListener` · `Container` · `Slot` · `ChestMenu` · `InventoryMenu` ·
`CraftingMenu.slotChangedCraftingGrid` · `ContainerInput` · `ClickAction` ·
`DataSlot` · `MenuType` · `MenuProvider` · `MenuScreens` ·
`ServerPlayer.openMenu` ·
`ServerGamePacketListenerImpl.handleSetCreativeModeSlot` ·
`CreativeModeInventoryScreen`
---
*Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes `tools/verify_names.py`.*
---
# Recipes
> Verified against **Minecraft 26.2** · Part VII · Eight planks go around the empty centre of a crafting table, a chest appears in the result slot, and the client is never told which recipe it was.
You lay eight planks around the empty middle square of a crafting table and a
chest appears in the slot on the right. What the server did was take a trimmed
copy of the grid, scan every crafting recipe it has loaded in alphabetical
order until one matched, call a `Recipe.assemble` that ignored your planks
entirely, and push the answer down the wire in a packet written by hand,
outside the menu's ordinary bookkeeping. What it never did was tell you *which*
recipe matched — and it could not have, because **no `Recipe` ever crosses the
wire**. Every `RecipeSerializer` is a record of a `MapCodec` and a
`StreamCodec`, and no game packet uses the stream half: the only reference to
`Recipe.STREAM_CODEC` anywhere in the tree is inside `RecipeHolder.STREAM_CODEC`,
which has no call sites at all. For the recipes it has unlocked the client
holds the whole *contents* — pattern, dimensions, ingredients, result — as a
`RecipeDisplay`. What it is denied is the **identity**, plus every recipe it has
not unlocked, plus any authority over the outcome.
## The cast
| class | what it decides | thread |
|---|---|---|
| `Recipe` | ten methods and no result getter — a result leaves through `Recipe.assemble`, `Recipe.display`, or a stonecutter's `StonecutterRecipe.resultDisplay` | data, and a `Recipe` object never leaves the server |
| `RecipeManager` | the loaded set, four indexes derived from it, and the server's `RecipeAccess` | the background executor for the scan, server main for everything after |
| `RecipeMap` | the immutable store, holding exactly two indexes: `RecipeMap.byType` and `RecipeMap.byKey` | built off-thread, swapped in and read on server main |
| `Ingredient` | whether one stack satisfies one slot — a `HolderSet` of items wearing a predicate face | both sides |
| `CraftingInput` | the trimmed grid a crafting recipe is matched against, and the presence index a shapeless one is matched with | server main |
| `ResultSlot` | the order of the endgame: award first, then look the recipe up again, then decrement | server main, mirrored on the client's own menu |
| `ServerRecipeBook` | which recipes this player has unlocked, and which of them still glow | server main |
| `ClientRecipeContainer` | everything the client knows about recipes *as recipes*: seven `RecipePropertySet`s and the stonecutter's input set | client, rebuilt wholesale from each packet |
## Loading: one scan, one swap, and four indexes built later
`RecipeManager` is a `SimplePreparableReloadListener` over a `RecipeMap`, so it
takes the ordinary two-phase shape of
[the resource system](../foundations/resource-system.md) — and then does
something unusual with the second phase.
```mermaid
flowchart TD
W["Worker: RecipeManager.prepare scans data/ns/recipe with SimpleJsonResourceReloadListener.scanDirectory, parsing each file through Recipe.CODEC into a sorted map"] --> M["RecipeMap.create, one RecipeHolder per file, keyed by a ResourceKey in Registries.RECIPE"]
M --> A["server main: RecipeManager.apply swaps the field and logs a count. That is all it does."]
A --> GAP["until the next call, the four indexes below still describe the PREVIOUS recipe set"]
GAP --> F["MinecraftServer calls RecipeManager.finalizeRecipeLoading itself, in its constructor and again at the end of reloadResources"]
F -->|"an ingredient is dropped unless every item in it is enabled"| P1["seven RecipePropertySets, one per key in RECIPE_PROPERTY_SETS"]
F -->|"input and result display both enabled"| P2["SelectableRecipe.SingleInputSet, the stonecutter's own index"]
F -->|"result and crafting station both enabled"| P3["allDisplays: a flat list whose POSITION is the RecipeDisplayId"]
P3 --> P4["recipeToDisplay: recipe key to the displays it produced"]
```
Three things in that picture are worth saying out loud.
**The scan is sorted, and the sort is the whole ordering story.**
`RecipeManager.prepare` accumulates into a sorted map keyed by `Identifier`,
and `Identifier.compareTo` compares the *path* first and the namespace only to
break a tie. So *foo:acacia_boat* sorts ahead of *minecraft:zzz*, matching is
deterministic across restarts, and the same order fixes the numbering of every
display id below.
**The indexes are not built by `RecipeManager.apply`.**
`RecipeManager.finalizeRecipeLoading` has exactly two call sites, both of them
in `MinecraftServer`, and neither is inside the reload listener. Between the
swap and that call the four derived indexes are **empty** — a reload builds a
fresh `RecipeManager`, whose constructor sets all four to their empty values —
so the recipe book, the property sets and the stonecutter index describe
nothing at all. Nothing can catch the game in that state: the swap and the
call are five statements apart in one lambda on the server thread.
**A `RecipeDisplayId` is a list index, not an identifier.** It is a record
wrapping a single int, and the int is the position the entry took in the flat
display list. Reload an unchanged pack and every id comes back identical; add
one recipe near the front and everything after it shifts. The server does not
try to work out which ids moved — `ServerRecipeBook.sendInitialRecipeBook`
re-sends the player's whole book with the replace flag set. Nor is the list one
entry per recipe: `Recipe.display` returns a *list*, and `TransmuteRecipe`
returns one display per legal material count — up to
`TransmuteRecipe.MAX_MATERIAL_COUNT`, eight — so a single recipe can occupy
eight consecutive ids.
The same walk decides what a recipe *is*, and it forgives more than you would
expect. A non-special recipe whose `PlacementInfo.isImpossibleToPlace` — the
usual cause being an ingredient tag that resolved to nothing, which
`Ingredient.CODEC` cannot reject because only a *literal* empty list is illegal
— is logged as unplaceable and then **kept**. It stays in `RecipeMap`, it still
matches a manual craft, and it still gets a `RecipeDisplayEntry`, one whose
ingredient list is present but empty; and `RecipeDisplayEntry.canCraft` on an
empty ingredient list is trivially *true*, so the book paints it as craftable
out of thin air. Only gate six of the auto-fill, below, stops the click.
### It is not only shaped and shapeless
`RecipeSerializers` registers twenty-one serializers and fourteen of them are
crafting-table recipes. **Nine** of those fourteen are `CustomRecipe`s — Java,
not data. `CustomRecipe` hard-codes `Recipe.isSpecial` true, `Recipe.group`
empty and `PlacementInfo.NOT_PLACEABLE`, and not one of the nine overrides
`Recipe.display`, so a special recipe contributes nothing to the display list in
the first place. Only eight of the nine are *named* special: `DecoratedPotRecipe`
registers as *crafting_decorated_pot* and is a `CustomRecipe` all the same. Of
the five that remain, `ShapedRecipe` and `ShapelessRecipe` are the pair everyone
knows, and `DyeRecipe`, `ImbueRecipe` and `TransmuteRecipe` are genuine
`NormalCraftingRecipe`s — data-driven, with hand-written matching, hand-built
placement info, and the exotic `SlotDisplay` variants to draw themselves with
(`SlotDisplay.OnlyWithComponent` and `SlotDisplay.DyedSlotDemo` for the first,
`SlotDisplay.WithAnyPotion` for the second). So *special* and *neither shaped nor
shapeless* are not the same set, and the difference matters, because it is
*special* that the book cannot show.
## Eight planks: the trace
```mermaid
sequenceDiagram
participant CraftM as CraftingMenu
participant CI as CraftingInput
participant RM as RecipeManager
participant ResultC as ResultContainer
participant Wire as the network
participant ResultS as ResultSlot
participant SRB as ServerRecipeBook
Note over CraftM: the tick the eighth plank lands
CraftM->>CI: asCraftInput, trimming the empty border rows and columns
CraftM->>RM: getRecipeFor CRAFTING, this input, this level, no hint
RM-->>CraftM: the first RecipeHolder that matches, in id order, or nothing
CraftM->>ResultC: setRecipeUsed, refused under LIMITED_CRAFTING if the book has not unlocked it
CraftM->>ResultC: setItem 0, the assembled stack
CraftM->>Wire: ClientboundContainerSetSlotPacket, written by hand, bumping the state id
Note over ResultS: some later tick, the player clicks the result
ResultS->>ResultC: checkTakeAchievements first, then awardUsedRecipes on the container
ResultC->>SRB: addRecipes, and then the container nulls its stored holder
SRB->>Wire: ClientboundRecipeBookAddPacket
ResultS->>RM: getRecipeFor again, which it does whether or not the holder survived
ResultS->>CraftM: removeItem one per occupied cell, then place the remainders
Note over CraftM: every one of those removals re-enters slotsChanged
```
**The grid changes.** Writing a plank into the menu's
`TransientCraftingContainer` calls `AbstractContainerMenu.slotsChanged`, and both
crafting overrides of it end in the same static,
`CraftingMenu.slotChangedCraftingGrid`. They reach it differently:
`CraftingMenu.slotsChanged` goes through `ContainerLevelAccess.execute`, while
`InventoryMenu.slotsChanged` calls the static directly, gated only on having a
`ServerLevel`. A `CraftingMenu` built with the two-argument constructor holds
`ContainerLevelAccess.NULL`, whose `ContainerLevelAccess.execute` runs nothing — and that instance is
the client's copy, which is why the client never matches anything.
**Trimming.** `CraftingContainer.asPositionedCraftInput` produces a
`CraftingInput` with the empty border rows and columns removed *and* the offset
beside it, which is why a shaped recipe works anywhere in the grid;
`CraftingContainer.asCraftInput` is the same call with the offset thrown away,
and it is what matching uses. Its constructor also fills a
`StackedItemContents`, but accounts every stack as **one** item: that index is a
presence set for shapeless matching, not the arithmetic the auto-fill does.
**Matching is a linear scan.** `RecipeMap.getRecipesFor` exits immediately on an
empty input, then streams the recipes of that `RecipeType` and filters them
through `Recipe.matches`, and `RecipeManager.getRecipeFor` takes the first.
`ShapedRecipePattern.matches` compares the ingredient count, then demands exactly
matching trimmed dimensions, then tries the **mirrored** layout before the
straight one — unless the pattern is symmetrical, which `Util.isSymmetrical`
settles once in the constructor. The chest's ring of planks is symmetrical, so
only the straight pass ever runs, and a one-column pattern is always symmetrical
too. `ShapelessRecipe.matches` rejects on count, short-circuits the single-slot
case, and otherwise hands the presence index to the bipartite search in
`StackedContents`, which it reaches through
`StackedItemContents.canCraft`.
Three accelerations sit on top of that scan, and each belongs to a different
caller. `RecipeManager.getRecipeFor` takes an optional **hint** and tests it
before scanning; the auto-fill supplies one through
`CraftingMenu.finishPlacingRecipe`, so the recipe it has just laid out is
the first thing re-matched — and the base
`AbstractCraftingMenu.finishPlacingRecipe` is a no-op, so the player's own
2×2 grid never gets one. `RecipeManager.CachedCheck` remembers the last
successful key and re-hints with it — `AbstractFurnaceBlockEntity` holds one per
block entity and `CampfireBlock` hands one to `CampfireBlockEntity.cookTick`. And
`RecipeCache`, ten entries held statically by `CrafterBlock` and keyed on the
grid contents, caches **misses** as well as hits, and invalidates on object
identity: it keeps a weak reference to the manager and wipes itself the moment
the level hands back a different one, which works because a reload builds a new
`RecipeManager`.
**The gate, and then the result.** `RecipeCraftingHolder.setRecipeUsed` returns
false — leaving the result slot empty — when `GameRules.LIMITED_CRAFTING` is on,
the recipe is not special, and `ServerRecipeBook.contains` says no. Limited
crafting is enforced *here*, in the result slot, not in matching. Past it,
`Recipe.assemble` produces the stack — for everything but the hand-written
recipes it ignores its input entirely and materialises a stored
`ItemStackTemplate` ([items and stacks](items-and-stacks.md)) —
`ItemStack.isItemEnabled` filters that against the level's feature flags, and
`ResultContainer.setItem` stores it.
**Pushing it.** `AbstractContainerMenu.setRemoteSlot` forces the server's belief
about slot zero and then a `ClientboundContainerSetSlotPacket` is sent by hand,
outside the diffing that [containers and menus](containers-and-menus.md)
describes, incrementing the state id on its own way past. It is sent even when
the result is empty.
**Taking it, in an order that surprises.** `ResultSlot.remove` counts what was
taken, and then `ResultSlot.onTake` runs `ResultSlot.checkTakeAchievements`
*before* anything is consumed. That calls `ItemStack.onCraftedBy`, which awards
`Stats.ITEM_CRAFTED` and runs `Item.onCraftedBy`, and then
`RecipeCraftingHolder.awardUsedRecipes`, which fires
`CriteriaTriggers.RECIPE_CRAFTED` for *every* recipe, special ones included, and
then — for a non-special recipe only — calls `ServerPlayer.awardRecipes` and so
`ServerRecipeBook.addRecipes`, unlocking it, firing
`CriteriaTriggers.RECIPE_UNLOCKED`, sending `ClientboundRecipeBookAddPacket`, and
**nulling the stored holder**. The next thing `ResultSlot.onTake` does is look
the recipe up all over again — not because of that null, but because
`ResultSlot.getRemainingItems` never consults the stored holder on any path,
special recipes included. `CraftingRecipe.getRemainingItems` — whose
default implementation is spelled `CraftingRecipe.defaultCraftingReminder`, the
typo Mojang's — maps each slot through `Item.getCraftingRemainder`, an
`ItemStackTemplate` or nothing. Then one item leaves each occupied cell and the
remainder goes back into the emptied slot, or merges with what is left there, or
goes to the inventory, or is dropped. Every one of those removals re-enters
`AbstractContainerMenu.slotsChanged`, so the result slot is recomputed eight
times on the way out.
A crafter block runs the same machinery through `RecipeCache` and fires its own
advancement trigger, `CriteriaTriggers.CRAFTER_RECIPE_CRAFTED`, not the player's.
## What the client actually gets
`ClientboundUpdateRecipesPacket` goes out twice: once from `PlayerList` as a
player joins, once to everybody from `PlayerList.reloadResources`. It carries two
things. The `RecipePropertySet`s are flat sets of items, and
`RecipePropertySet.test` is **item identity only** — the client's slot predicate
ignores components entirely. The stonecutter's
`SelectableRecipe.SingleInputSet` is written by
`SelectableRecipe.SingleInputEntry.noRecipeCodec`, which serialises the input
`Ingredient`'s contents and the option's `SlotDisplay` and drops the recipe on
the floor: `SelectableRecipe.noRecipeCodec` decodes an empty optional in its
place.
Those sets exist so that menus can answer *may this item go in this slot* — and
route a shift-click on the strength of it — without knowing a single recipe.
`SmithingMenu` builds its three input slots out of the three smithing sets,
`AbstractFurnaceMenu` holds whichever of `RecipePropertySet.FURNACE_INPUT`,
`RecipePropertySet.BLAST_FURNACE_INPUT` and `RecipePropertySet.SMOKER_INPUT`
its type was constructed with — the fourth set,
`RecipePropertySet.CAMPFIRE_INPUT`, reaches no menu at all and is read by
`CampfireBlock` on a right-click — and
`StonecutterMenu` asks the stonecutter set the same question through
`SelectableRecipe.SingleInputSet.acceptsInput`.
The book gets something far richer and still anonymous: a `RecipeDisplayEntry`
per display, carrying the display id, the `RecipeDisplay` itself, a group index,
a `RecipeBookCategory`, and an optional ingredient list used for one thing only —
deciding whether the entry lights up. `RecipeDisplay` and `SlotDisplay` are
dispatched registries of their own (`Registries.RECIPE_DISPLAY`,
`Registries.SLOT_DISPLAY`, and `Registries.RECIPE_BOOK_CATEGORY` for the
categories `RecipeBookCategories` fills), and *their* stream codecs are very much
used: five `RecipeDisplay` types, one per station shape, and eleven registered
`SlotDisplay` variants, which `SlotDisplay.resolve` turns into concrete stacks
against a `SlotDisplayContext`. The chest's single ingredient reaches the client
as a `SlotDisplay.TagSlotDisplay` over *minecraft:planks*
([tags](../foundations/tags.md)) — everything needed to draw the recipe, and
nothing at all about its name.
## The recipe book: unlocked, glowing, and filled in for you
`ServerRecipeBook` stores four things: a display resolver, the settings,
`ServerRecipeBook.known` —
an *identity* set of recipe keys — and `ServerRecipeBook.highlight`, the subset
still new enough to glow. It is saved in the player NBT as
`ServerRecipeBook.Packed` and read back by `ServerRecipeBook.loadUntrusted`,
which validates every key against the live `RecipeManager` and logs and drops the
ones that no longer resolve ([codecs](../foundations/codecs-nbt-json.md)).
`ClientRecipeBook` never sees any of that. It holds `RecipeDisplayEntry`s by
display id, and `ClientRecipeBook.rebuildCollections` groups them into
`RecipeCollection`s by category and then by group index, which is why one button
in the book cycles through all twelve kinds of plank.
The tabs are narrower than the recipe types. `RecipeBookType` has four values —
crafting, furnace, blast furnace and smoker — and exactly five menus extend
`RecipeBookMenu` to claim them, `CraftingMenu` and `InventoryMenu` both answering
`RecipeBookType.CRAFTING`. `StonecutterMenu` and `SmithingMenu` are not
`RecipeBookMenu`s at all, so the stonecutter and the smithing table have no book
and no auto-fill, even though `RecipeBookCategories.STONECUTTER` and
`RecipeBookCategories.SMITHING` exist to categorise them — and the anvil and the
grindstone were never recipes at all ([enchanting](enchanting.md)).
Craftability is decided on the client and then decided again on the server.
`RecipeCollection.selectRecipes` asks `RecipeDisplayEntry.canCraft` against a
`StackedItemContents` that `RecipeBookComponent` fills from the player's
inventory, purely to choose which entries glow. A lying client gains nothing by
it, because the placement re-checks and `CraftingMenu.slotChangedCraftingGrid`
runs a full `Recipe.matches` afterwards regardless.
Auto-fill itself runs entirely on the server. Clicking an entry sends
`ServerboundPlaceRecipePacket` carrying nothing but a container id, a
`RecipeDisplayId` and a shift flag, and
`ServerGamePacketListenerImpl.handlePlaceRecipe` puts it through seven gates:
1. the player is not a spectator, and the packet's container id is the open menu's;
2. `AbstractContainerMenu.stillValid` still holds for the open menu;
3. `RecipeManager.getRecipeFromDisplay` resolves the index to a `RecipeManager.ServerDisplayInfo`;
4. `ServerRecipeBook.contains` says this player has unlocked the parent recipe;
5. the open menu really is a `RecipeBookMenu`;
6. the recipe's `PlacementInfo.isImpossibleToPlace` says no — which catches
`PlacementInfo.NOT_PLACEABLE` and any placement whose ingredients came out
empty;
7. and only then does `RecipeBookMenu.handlePlacement` run.
`AbstractCraftingMenu.handlePlacement` raises the flag that suppresses
re-matching while it shuffles — a `CraftingMenu` override, so the 2×2 grid inside
`InventoryMenu` re-matches on every single write — and calls
`ServerPlaceRecipe.placeRecipe`, which **counts before it clears**: a dry run of
emptying the grid back into the inventory, a tally of what the player has, a
calculation of how many crafts that allows, clamped to the smallest stack size
among the items chosen, and only then the real clear and the layout through
`PlaceRecipeHelper.placeRecipe`. Whether it may drop items in order to clear the
grid is simply `Player.isCreative`, so for a survival player with a full
inventory the whole call returns `RecipeBookMenu.PostPlaceAction.NOTHING` — no
fill *and* no ghost. When the ingredients merely are not there it returns
`RecipeBookMenu.PostPlaceAction.PLACE_GHOST_RECIPE` instead, and the server sends
`ClientboundPlaceGhostRecipePacket` carrying the `RecipeDisplay` itself.
One filter runs underneath all of that and catches players out.
`Inventory.isUsableForCrafting` rejects any stack that is damaged, enchanted or
renamed, and it gates both halves of the auto-fill: the tally, through
`StackedItemContents.accountSimpleStack`, and the actual pull, through
`Inventory.findSlotMatchingCraftingIngredient`. `CraftingInput`'s constructor
deliberately goes around it by calling `StackedItemContents.accountStack`
directly. So the book can grey out a recipe that a manual craft with those very
items would have accepted without complaint.
## Where to look
`Recipe` · `RecipeType` · `RecipeSerializer` · `RecipeSerializers` ·
`RecipeHolder` · `RecipeManager` · `RecipeMap` · `RecipeAccess` ·
`ClientRecipeContainer` · `Ingredient` · `PlacementInfo` · `RecipePropertySet` ·
`RecipeInput` · `CraftingInput` · `ShapedRecipePattern` · `ShapelessRecipe` ·
`NormalCraftingRecipe` · `AbstractCookingRecipe` · `CustomRecipe` ·
`RecipeDisplay` · `SlotDisplay` · `RecipeDisplayEntry` · `RecipeDisplayId` ·
`AbstractCraftingMenu` · `CraftingMenu` · `RecipeBookMenu` · `ResultSlot` ·
`ResultContainer` · `RecipeCraftingHolder` · `ServerRecipeBook` ·
`ClientRecipeBook` · `ServerPlaceRecipe` · `StackedItemContents`
Before this page: [containers and menus](containers-and-menus.md), for the
synchroniser that the result slot goes around, and [items and
stacks](items-and-stacks.md) for `ItemStackTemplate` and crafting remainders.
---
*Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes `tools/verify_names.py`.*
---
# Enchantments
> Verified against **Minecraft 26.2** · Part VII · A player hits a zombie with a Fire Aspect sword, and everything that makes the zombie burn is data.
You swing a Fire Aspect sword at a zombie and the zombie catches fire. Go
looking for the class that does that — the one with the *on hit, set them
alight* method — and there is nothing to find. There are no enchantment
subclasses. The forty-three vanilla enchantments are JSON files in the
built-in data pack, `Enchantments` is a bag of forty-three registry keys
that only four files outside the data generator ever name, and the whole
*Fire Aspect is melee only* rule is **one loot condition**, sitting on one
entry of a `DataComponentMap` that no item ever holds.
That is the pattern the rest of the page is about. An enchantment is a
named modifier that holds no code; every behaviour in the game an
enchantment can change is a component key that some other system asks
about at a well-defined moment.
## The cast
| class | what it decides | thread |
|---|---|---|
| `Enchantment` | the record — a description, a definition, an exclusive set and a map of effects. No behaviour of its own | built at data-pack load, read on both sides |
| `Enchantment.EnchantmentDefinition` | what it can go on, what it costs, which slots it counts in | data-pack load |
| `EnchantmentEffectComponents` | the thirty-one keys that name every moment an enchantment can change | static, both sides |
| `EnchantmentHelper` | the static hook surface — walks stacks and slots, calls into the record, holds no state | server main for every effect, with some read-only entry points on the client |
| `ConditionalEffect` / `TargetedConditionalEffect` | whether this effect fires here, and on whom | server main |
| `EnchantmentEntityEffect` | the thing that finally happens | server main only — the signature demands a `ServerLevel` |
| `ItemEnchantments` | the id-to-level map on the stack, under one of two components | both sides |
| `EnchantedItemInUse` | the stack, its slot, its owner and a break callback, built by the slot-aware walk and handed to the effects that act on the world | server main |
## A record with a definition and a bag of components
`Enchantment` is a record of four things: a description `Component`, an
`Enchantment.EnchantmentDefinition`, a `HolderSet` of enchantments it is
exclusive with, and a `DataComponentMap` of effects. `Enchantment.getEffects`
looks a component type up in that map and returns an empty list if it is
absent, which is the whole dispatch mechanism.
The definition is where the enchanting rules live. It carries the supported
items and a *narrower, optional* set of primary items — both normally item
tags — so `Enchantment.isPrimaryItem` falls back to the supported set when
the second is absent, while `Enchantment.isSupportedItem` only ever asks the
first. It carries the weight, the maximum level, two `Enchantment.Cost`
curves (a base plus a per-level-above-first increment, read by
`Enchantment.Cost.calculate`), the anvil cost, and a list of
`EquipmentSlotGroup` — the slots in which this enchantment counts at all,
tested by `Enchantment.matchingSlot`. The codec bounds the weight to 1–1024
and the maximum level to 1–255, which is `Enchantment.MAX_LEVEL`;
`Enchantment.getMinLevel` is the one number that is not data, hardcoded to 1.
What a stack carries is not that record but `ItemEnchantments`, an
immutable id-to-level map under `DataComponents.ENCHANTMENTS` — or, for an
enchanted book's inert set, under `DataComponents.STORED_ENCHANTMENTS`. It
is changed only through `ItemEnchantments.Mutable`, whose
`ItemEnchantments.Mutable.upgrade` merges by maximum and clamps at 255.
`EnchantmentInstance`, a holder-and-level pair with a
`EnchantmentInstance.weight` delegate, is what the weighted selection path
is built on — and that path, along with the table, the anvil, the
grindstone, the providers and `/enchant`, is a separate machine with its own
arithmetic, on [enchanting](enchanting.md).
## Thirty-one keys, three registries and one curve
`EnchantmentEffectComponents` registers thirty-one component types into
`BuiltInRegistries.ENCHANTMENT_EFFECT_COMPONENT_TYPE`. Twenty-four hold a
list of `ConditionalEffect` (or `TargetedConditionalEffect`); the other
seven are plain — `EnchantmentEffectComponents.ATTRIBUTES`, two sound
lists, two unconditional values, and the two `Unit`-valued flags
`EnchantmentEffectComponents.PREVENT_EQUIPMENT_DROP` and
`EnchantmentEffectComponents.PREVENT_ARMOR_CHANGE`, true by being present.
Three registries supply the effect objects, and **they are not disjoint**.
`EnchantmentValueEffect` has six shapes and modifies a running number
(`AddValue`, `MultiplyValue`, `SetValue`, `RemoveBinomial`,
`ScaleExponentially` and `AllOf.ValueEffects`). `EnchantmentEntityEffect`
has fifteen and does something to an entity — `Ignite`, `DamageEntity`,
`ApplyMobEffect`, `SummonEntityEffect`, `AllOf.EntityEffects` and ten more.
And it *extends* `EnchantmentLocationBasedEffect`, whose registry has
sixteen entries: those same fifteen ids plus one. The odd one out is
*attribute*, `EnchantmentAttributeEffect`, which installs an
`AttributeModifier` ([attributes](../entities/attributes.md)) and is the
only effect that is location-based without also being an entity effect.
`LevelBasedValue` turns a level into a number, and six shapes are in its
dispatch registry (`LevelBasedValue.Linear`, `LevelBasedValue.Clamped`,
`LevelBasedValue.Fraction`, `LevelBasedValue.LevelsSquared`,
`LevelBasedValue.Exponent`, `LevelBasedValue.Lookup`).
`LevelBasedValue.Constant` is deliberately **not** among them: it is the
other arm of an either-codec, which is what makes a bare float legal
anywhere a curve is expected.
`ConditionalEffect` is an effect plus an optional `LootItemCondition`.
`TargetedConditionalEffect` adds two `EnchantmentTarget` fields — which side
of the fight the enchantment *lives on* (`TargetedConditionalEffect.enchanted`)
and which side it *lands on* (`TargetedConditionalEffect.affected`) —
except for the equipment-drops variant, whose codec reads only the first and
pins the second to `EnchantmentTarget.VICTIM`.
Both implement `Validatable`, and this is where the effect codecs do
something unusual: those twenty-four list components wrap their element
codec in `Validatable.listValidatorForContext`, so a condition is checked
**at decode time** against the parameter set the hook will actually supply.
A *post_attack* effect asking about a block state fails to load rather than
failing quietly at runtime.
## Seven families of moment
Everything above is inert until something calls `EnchantmentHelper`. The
enchantment package barely calls anything and everything calls it, so the
artefact worth keeping is a table of who calls what: every entry point with
its callers is [the enchantment hook
table](../../reference/enchantment-hooks.md), and these are the seven kinds
of moment it falls into.
| family | a representative hook or two | who makes it real |
|---|---|---|
| damage and protection | `EnchantmentHelper.modifyDamage`, `EnchantmentHelper.getDamageProtection` | `ServerPlayer.getEnchantedDamage`, `LivingEntity.getDamageAfterMagicAbsorb` |
| post-attack effects | `EnchantmentHelper.doPostAttackEffectsWithItemSource`, `EnchantmentHelper.doPostPiercingAttackEffects` | `Player.itemAttackInteraction`, `LivingEntity.postPiercingAttack` |
| durability and drops | `EnchantmentHelper.processDurabilityChange`, `EnchantmentHelper.processEquipmentDropChance` | `ItemStack.hurtAndBreak`, `Mob.dropCustomDeathLoot` |
| projectiles and the weapon in hand | `EnchantmentHelper.processProjectileCount`, `EnchantmentHelper.getPiercingCount` | `ProjectileWeaponItem.draw`, the `AbstractArrow` constructor |
| location and tick effects | `EnchantmentHelper.runLocationChangedEffects`, `EnchantmentHelper.tickEffects` | `LivingEntity.onChangedBlock`, `LivingEntity.baseTick` |
| experience and repair | `EnchantmentHelper.processBlockExperience`, `EnchantmentHelper.modifyDurabilityToRepairFromXp` | `Block.tryDropExperience`, `ExperienceOrb.repairPlayerItems` |
| the flag questions | `EnchantmentHelper.has`, `EnchantmentHelper.hasTag` | `ArmorSlot.mayPickup` for Curse of Binding, four blocks for the four *prevents* tags |
The location row is the one that keeps state:
`LivingEntity.activeLocationDependentEnchantments` remembers which
location-based effects are running in which slot, so the system can tell
*became active* from *still active*, and so an attribute effect can be taken
off cleanly by `EnchantmentHelper.stopLocationBasedEffects` when the armour
comes off.
## How one hook fires
Six of those seven rows are the same shape underneath. A system reaches a
moment and asks `EnchantmentHelper`; the helper picks which stacks to walk,
filtering by slot if it was given one; the record picks which effect entries
apply; a loot condition decides whether this one fires. Only then does
anything happen. The flag row is the exception and is the reason the shape is
worth drawing: `EnchantmentHelper.has` and `EnchantmentHelper.hasTag` build no
context and run no condition — they ask the record whether the key is present
at all.
```mermaid
flowchart TD
Caller["a system reaches a moment: Player.itemAttackInteraction, Block.tryDropExperience, LivingEntity.baseTick"]
Caller --> EH["an EnchantmentHelper entry point"]
EH --> Walk["walk one stack, or every EquipmentSlot of one entity"]
Walk --> Slot["on the slot-aware walk, keep entries whose Enchantment.matchingSlot accepts this slot"]
Slot --> Comp["read the list under one EnchantmentEffectComponents key"]
Comp --> Target["for the targeted keys, keep entries whose enchanted target matches this pass"]
Target --> Ctx["build a LootContext on that hook's parameter set"]
Ctx --> Cond["ConditionalEffect.matches runs the LootItemCondition"]
Cond -- "no" --> Drop["nothing happens"]
Cond -- "yes" --> Apply["apply: fold a value, or run the effect on the affected entity"]
```
The `LootContext` in the middle is the same machinery loot tables and
`/execute if predicate` use, and `Enchantment` builds five of them —
`Enchantment.damageContext` plus four private siblings for items, entities,
locations and block hits, one per parameter set the components name.
[Contexts and predicates](contexts-and-predicates.md) owns that half.
## Fire Aspect, from the click to the flame
Fire Aspect as data is forty-three lines of JSON: supported and primary items
are tags, weight 2, maximum level 2, anvil cost 4, dynamic cost curves, one
slot (*mainhand*), and **one effect** — a
`EnchantmentEffectComponents.POST_ATTACK` entry whose enchanted target is
the attacker, whose affected target is the victim, whose effect is `Ignite`
with a linear duration of four seconds per level, behind one damage-source
predicate requiring a *direct* hit.
```mermaid
sequenceDiagram
participant SGPL as ServerGamePacketListenerImpl
participant Player as Player
participant EH as EnchantmentHelper
participant Ench as Enchantment
participant Ignite as Ignite
participant Entity as Entity
participant SED as SynchedEntityData
SGPL->>Player: handleAttack passes the range checks, then Player.attack
Player->>Player: createAttackSource, whose direct and causing entity are one
Player->>EH: on a hit that landed, itemAttackInteraction calls doPostAttackEffectsWithItemSource
EH->>EH: the victim's whole equipment first, then the attacker's main hand
EH->>Ench: doPostAttack for the main-hand stack, in the ATTACKER pass
Ench->>Ench: damageContext, then TargetedConditionalEffect.matches asks is the hit direct
Ench->>Ignite: apply, with the victim as the affected target
Ignite->>Entity: igniteForSeconds, raised only if the new value is larger
Entity->>SED: baseTick sets shared flag zero
Note over SED: the flame travels as ClientboundSetEntityDataPacket, the enchantment never does
Note over Entity: one point of fire damage every twentieth tick from here on
```
**The swing, and the source that is the whole melee rule.**
`ServerGamePacketListenerImpl.handleAttack` runs the range checks and calls
`Player.attack` — unless the main-hand item has
`DataComponents.PIERCING_WEAPON`, in which case the packet is dropped and
none of this happens (see [the sword swing](../player/the-sword-swing.md)
for the spear's path). `Player.createAttackSource` then asks
`ItemStack.getDamageSource`, and every branch of it reaches the
single-entity `DamageSource` constructor — the one that sets the direct
entity and the causing entity to the same object, making
`DamageSource.isDirect` true. An arrow's source has an arrow as the direct
entity and a player as the causing entity, so it is false. That one
comparison, read by one loot condition in one JSON file, is why Fire Aspect
never fires through a bow.
**The damage, then the hook.** `ServerPlayer.getEnchantedDamage` — the
override, not `Player`'s base version, which returns its argument unchanged
— folds Sharpness and friends in through `EnchantmentHelper.modifyDamage`,
and the hit goes through `Entity.hurtOrSimulate` to `LivingEntity.hurtServer`
([damage and death](../entities/damage-and-death.md)). Only if that returned
true does `Player.itemAttackInteraction` call
`EnchantmentHelper.doPostAttackEffectsWithItemSource`.
**Three branches, not two.** The helper walks the *victim's* whole
equipment first, with `EnchantmentTarget.VICTIM` as the pass — that is
Thorns' lane, and why Thorns works from a chestplate while Fire Aspect does
not work from boots. Then, for a living causing entity, it walks the
attacker's **main hand only**, in the `EnchantmentTarget.ATTACKER` pass,
keeping enchantments whose declared slots include that slot. A third branch
handles a causing entity that is not living: a slotless pass with no filter
at all, reached through
`EnchantmentHelper.doPostAttackEffectsWithItemSourceOnBreak` with a break
callback, whose only vanilla caller is `ThrownTrident`. **An attacker's
armour can never contribute a post-attack effect.**
**The condition and the target.** `Enchantment.doPostAttack` keeps the
entries whose enchanted target matches the pass, builds the
enchanted-damage context — victim, level, origin, damage source, and both
attacking entities as optional parameters — and runs the predicate. The
*affected* field then picks who receives the effect: attacker, direct
entity, or victim. Fire Aspect says victim, and if that works out to null
the effect is dropped in silence.
**The burn.** `Ignite.apply` calls `Entity.igniteForSeconds`, which floors
to ticks and hands `Entity.igniteForTicks` a number it applies **only if it
is larger than the counter already there** — while clearing any freeze
regardless, so re-hitting a burning target with a weaker Fire Aspect does
nothing except thaw it. No damage happens here. `Entity.baseTick` deals it,
one point every twentieth tick, on a `ServerLevel`, skipped in lava and
replaced by `Entity.clearFire` for a fire-immune entity. The same method
sets shared flag zero through `Entity.setSharedFlagOnFire`
([synched entity data](../entities/synched-entity-data.md)), which becomes
`Entity.displayFireAnimation` on the client — whose own `Entity.baseTick`,
finding no `ServerLevel`, clears the fire counter instead of burning.
## Questions the pattern raises
**Where does Fortune live, if not in a hook?** In the loot table.
`ApplyBonusCount` and `BonusLevelTableCondition` read the *tool* parameter
out of a `LootContext` and call `EnchantmentHelper.getItemEnchantmentLevel`
on it — a level, not an effect. Looting is the same trick from the other
end: `EnchantedCountIncreaseFunction` reads the *attacking entity* parameter
and calls `EnchantmentHelper.getEnchantmentLevel`, the overload that walks a
`LivingEntity`'s equipment and keeps the best. Fortune has no effect
component whatsoever, and Looting's only one is an *equipment_drops* entry
that has nothing to do with mob loot. Mending inverts it once more:
`ExperienceOrb.repairPlayerItems` asks `EnchantmentHelper.getRandomItemWith`
for a stack carrying `EnchantmentEffectComponents.REPAIR_WITH_XP`, so the
orb, not the item, drives the repair.
**Does anything enchantment-shaped run on the client?** No *effect* can:
`EnchantmentEntityEffect` and `EnchantmentLocationBasedEffect` both demand a
`ServerLevel`. But two *values* do. `Enchantment.modifyUnfilteredValue`
takes only a `RandomSource`, and its two users are
`Enchantment.modifyCrossbowChargeTime` and
`Enchantment.modifyTridentSpinAttackStrength`.
`CrossbowItem.getChargeDuration` is called by three entity renderers, by
`ItemInHandRenderer` and by the `CrossbowPull` item property, so **Quick
Charge is evaluated on the render thread every frame a crossbow is being
drawn**; and `MultiPlayerGameMode.releaseUsingItem` runs
`TridentItem.releaseUsing` on the client's own copy, which asks
`EnchantmentHelper.getTridentSpinAttackStrength`, so **Riptide's strength is
computed client-side too** — which is what lets the riptide push be predicted
at all. Everything else the client does is drawing:
`ItemEnchantments.addToTooltip` for the tooltip, `ItemStack.hasFoil` for the
glint, and `EnchantmentHelper.forEachModifier` for the attribute lines —
which means the client evaluates the `LevelBasedValue` curve itself.
**What actually crosses the wire?** Usually an id and nothing else.
`Registries.ENCHANTMENT` is in `RegistryDataLoader.SYNCHRONIZED_REGISTRIES`
with the full `Enchantment.DIRECT_CODEC`, but
`RegistrySynchronization.packRegistry` sends a bare `Identifier` for every
element whose pack the client already has — for a vanilla client against a
vanilla server, all forty-three. Full definitions cross only for a data
pack's custom or overridden enchantments. Beyond that,
`ClientboundUpdateTagsPacket` for `EnchantmentTags` and, per stack, the
`DataComponents.ENCHANTMENTS` component: registry ids and levels.
**Why is an enchanted book inert?** Because
`EnchantmentHelper.runIterationOnItem` — the private walk under every hook —
reads `DataComponents.ENCHANTMENTS` and nothing else. A book's set lives
under `DataComponents.STORED_ENCHANTMENTS`, and the routing between the two,
in `EnchantmentHelper.getComponentType`, is keyed on the exact item
`Items.ENCHANTED_BOOK`. The *component* is not so exclusive: `ItemStack` puts
`DataComponents.STORED_ENCHANTMENTS` in any stack's tooltip, and `AnvilMenu`
decides it is being handed a book by testing for that component rather than
for the item — so a data pack can make a stick priced like one, but never make
it behave like one.
**Is the main hand really the main hand?** No — it is a label.
`EnchantmentHelper.doPostAttackEffectsWithItemSourceOnBreak` and
`EnchantmentHelper.doPostPiercingAttackEffects` both hand
`EquipmentSlot.MAINHAND` to the slot filter regardless of where the weapon
came from, and `KineticWeapon.damageEntities` reaches
`LivingEntity.stabAttack` with whichever slot the *use* was in. An off-hand
spear's enchantments are therefore tested against the main-hand slot group.
**Three more small ones.** `ItemStack.isEnchantable` reads *two*
components: `DataComponents.ENCHANTABLE` must be present, and
`DataComponents.ENCHANTMENTS` must be present *and empty*, so an item with
no enchantments component at all is not enchantable either. The two
`EnchantmentHelper.forEachModifier` overloads test different things — the
`EquipmentSlot` one asks `Enchantment.matchingSlot`, the `EquipmentSlotGroup`
one asks whether the definition declares that exact group — and
`ItemStack.forEachModifier` uses one of each. And the instance method is
spelled `Enchantment.modifyArmorEffectivness`, Mojang's typo, while the
helper beside it is `EnchantmentHelper.modifyArmorEffectiveness`.
**And does Fire Aspect cook the loot?** Not through the enchantment. That
is a loot-table condition on `EnchantmentTags.SMELTS_LOOT`, a tag whose only
member is Fire Aspect ([loot tables](loot-tables.md)). `EnchantmentTags`
holds twenty-nine tags. Twenty-five fall into five families — the seven
exclusivity sets, the tooltip order, pool membership for the table and for
mob, trade and loot equipment, the behaviour flags (curse, smelts-loot and the
four *prevents* tags), and the seven biome trade tables — and the last four
are the trading and treasure axes the villager and the loot tables sort on.
**What does a whole enchantment look like, then?** `Enchantments.LUNGE` is
the only user of `EnchantmentEffectComponents.POST_PIERCING_ATTACK` in the
game, and its single effect is an `AllOf.EntityEffects` of four — a
`ChangeItemDamage`, an `ApplyExhaustion` scaled per level, an
`ApplyEntityImpulse` forward with its vertical component scaled away, and a
`PlaySoundEffect` holding three sounds, indexed by level rather than
shuffled — behind a four-clause
condition checking that the user is not riding, not elytra-flying, not in
water, and either not a player, in creative, or fed. One JSON file, four
effect objects, four predicates, no Java. That is what an enchantment is.
## Where to look
`Enchantment` · `Enchantment.EnchantmentDefinition` · `Enchantment.Cost` ·
`EnchantmentEffectComponents` · `ConditionalEffect` ·
`TargetedConditionalEffect` · `EnchantmentTarget` · `LevelBasedValue` ·
`EnchantmentValueEffect` · `EnchantmentEntityEffect` ·
`EnchantmentLocationBasedEffect` · `EnchantmentAttributeEffect` · `AllOf` ·
`Ignite` · `EnchantmentHelper` · `EnchantedItemInUse` · `ItemEnchantments` ·
`EnchantmentInstance` · `Enchantments` · `EnchantmentTags`
---
*Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes `tools/verify_names.py`.*
---
# Enchanting: the five paths, and what each one is allowed to do
> Verified against **Minecraft 26.2** · Part VII · A player reads three offers off an enchanting table and buys one, and then the same sword picks up enchantments four other ways — an anvil, a grindstone running backwards, a spawning pillager, and a command.
A sword goes in the left slot of an enchanting table and three lapis in the
right, and the table answers with three lines of Standard Galactic Alphabet,
three level numbers, and — if you hover — one enchantment named outright.
None of that is guessed. The server has already run the entire selection,
and it ships the answer to the client as ten integers. One of those ten is
`Player.enchantmentSeed`, and it is the reason the page is worth a lecture:
**one number per player, saved in the player file, carried across death and
dimension change, sent to the client, and re-rolled by nothing in the game
except the enchanting table itself.** Spend thirty levels at an anvil and
come back and the table is offering exactly what it offered before — and the
gibberish is in the same handwriting, because the client is drawing it from
that same number.
The table is one of five paths that change what a stack is enchanted with —
four of them adding and the grindstone taking away — and they differ far more
than the shared vocabulary suggests. This page is about those differences. A
sixth writer hides outside all of them, in the crafting grid: `RepairItemRecipe`
carries every curse from both inputs onto the tool it makes
([recipes](recipes.md)). What an enchantment *is* — the record, the effect
components, the hooks that fire in combat — is
[the next page along](enchantments.md) and is not re-taught here.
## The cast
| class | what it decides | thread |
|---|---|---|
| `EnchantmentMenu` | the three offers, the clue, and what the click costs | server main, with a client copy that can only say no |
| `EnchantmentHelper` | the cost curve, the weighted selection, and the write every path ends in | whichever side asks |
| `Player` | the seed and the levels | server main |
| `AnvilMenu` | the merge arithmetic and the price | server main |
| `GrindstoneMenu` | the only removal a player can reach, and the refund | server main |
| `EnchantmentProvider` | what a mob's spawn equipment gets | server main |
| `EnchantRandomlyFunction` | chest loot and villager trades | server main |
| `EnchantCommand` | the operator's path, and the fewest checks | server main |
`EnchantWithLevelsFunction`, `SetEnchantmentsFunction` and
`EnchantedCountIncreaseFunction` are the other three loot functions, named
below where they differ; `EnchantmentScreen` is the client half of the table
and gets its own section.
## The five paths at a glance
| | enchanting table | anvil | grindstone | providers and loot | `/enchant` |
|---|---|---|---|---|---|
| **what it costs** | 1, 2 or 3 levels and the same count of lapis | the full price in levels, and a chance the anvil chips | pays *you*, in orbs at the block | nothing | nothing |
| **the gate on the item** | `ItemStack.isEnchantable` — enchantable *and* not already enchanted | `EnchantmentHelper.canStoreEnchantments` | damageable or already enchanted | `DataComponents.ENCHANTABLE`, except `SingleEnchantment` and `EnchantRandomlyFunction` | any non-empty main-hand item |
| **which item filter** | `Enchantment.isPrimaryItem` — the narrow set | `Enchantment.canEnchant` — the supported set | n/a | `Enchantment.isPrimaryItem` for the selection paths, `Enchantment.canEnchant` for `EnchantRandomlyFunction`, none at all for `SingleEnchantment` | `Enchantment.canEnchant` |
| **the level ceiling** | whatever the cost brackets allow | clamped to `Enchantment.getMaxLevel` | n/a | clamped, except `SetEnchantmentsFunction` | rejected above `Enchantment.getMaxLevel` |
| **exclusivity** | filtered out mid-selection | dropped, and it raises the price | curses survive, everything else goes | filtered, or ignored by flag | rejected with an error |
| **randomness** | the player's saved seed | none in the arithmetic; a 12% roll for the chip | none in the strip; a roll on the refund | the level's random source | none |
| **decided on** | server, with the click predicted | server, with the price synced | server | server | server |
Five paths, one row that would be the same everywhere: the last step. Not the
same method — the table, `/enchant` and `EnchantRandomlyFunction` go through
`ItemStack.enchant`, the grindstone and the providers call
`EnchantmentHelper.updateEnchantments` themselves, and the anvil writes with
`EnchantmentHelper.setEnchantments` — but the same *decision*, and that is
where the page starts.
## The one question all five ask
`EnchantmentHelper.getComponentType` is the private line under every one of
those three entry points, and it does the thing worth knowing before any of
the five paths make sense.
It **routes by item identity**: the component the write lands in is
`DataComponents.STORED_ENCHANTMENTS` if the stack is `Items.ENCHANTED_BOOK`
and `DataComponents.ENCHANTMENTS` otherwise — a hard identity test against
one item, not a tag. That is why every path that can be handed a plain
`Items.BOOK` transmutes it *first*, through `ItemStack.transmuteCopy` or by
building a fresh stack. The transmute is not cosmetic: enchant a plain book
and the levels would land in the active component and the book would start
*working*.
`EnchantmentHelper.updateEnchantments` adds one more rule of its own: it
**silently does nothing if the component is absent**, returning
`ItemEnchantments.EMPTY` on a null read. In practice every item gets
`DataComponents.ENCHANTMENTS` from `DataComponents.COMMON_ITEM_COMPONENTS`,
so the case only arises when an item definition replaces the default
component initializer or a patch removes the component from a stack — and
then the four paths through it become a no-op, with no error anywhere
([data components](../foundations/data-components.md)).
The merge itself is `ItemEnchantments.Mutable.upgrade`: keep the higher
level, cap at 255, ignore a level of zero. Only
`ItemEnchantments.Mutable.set` can lower a level, and only the anvil — where
it clamps an over-maximum level down — and `SetEnchantmentsFunction` reach for
it. The grindstone does not lower anything; it removes.
## What it costs, and who pays
The enchanting table's headline number is not its price. The level
*requirement* for a slot is the cost the table computed for it — up to
thirty at the bottom slot — but the amount `Player.onEnchantmentPerformed`
actually subtracts is the slot's index plus one, and the lapis consumed is
the same one, two or three.
| what the forum says | what the decompile does |
|---|---|
| the bottom offer costs thirty levels | `EnchantmentMenu.clickMenuButton` requires thirty levels and takes **three** |
| more bookshelves make better enchantments | more bookshelves raise the *cost*, and `EnchantmentHelper.getEnchantmentCost` floors the bottom slot at twice the shelf count |
| the anvil's "Too Expensive" is a level cap | it is a result cap — at a price of forty or more `AnvilMenu.createResult` empties the output slot unless the player has infinite materials |
The anvil is the opposite: it charges the whole displayed price, through
`Player.giveExperienceLevels` with a negative amount, in `AnvilMenu.onTake`,
and then rolls a small chance to damage or destroy the block. The price is
a prior-work tax read from `DataComponents.REPAIR_COST` on **both** inputs,
plus one per repair material consumed, plus `Enchantment.getAnvilCost` times
the resulting level for every enchantment transferred (halved with a floor
of one when the addition is a book), plus one for a rename. The **larger** of
the two inputs' `DataComponents.REPAIR_COST` is then doubled and incremented
by `AnvilMenu.calculateIncreasedRepairCost` and written onto the result — the
whole of the prior-work spiral, and the one step a pure rename skips. Two cases escape that arithmetic: an input stack of more than one
item sets the price to a flat **40** the moment any enchantment actually
transfers, which — forty being exactly the threshold at which the result is
withheld — makes enchanting a stack not expensive but forbidden outside
creative; and a rename with no other change is capped at 39, which is why
renaming never hits "Too Expensive".
The grindstone runs the transaction backwards. It strips everything not in
`EnchantmentTags.CURSE`, turns an emptied `Items.ENCHANTED_BOOK` back into a
plain `Items.BOOK` with `ItemStack.transmuteCopy`, and rebuilds
`DataComponents.REPAIR_COST` from zero, so a clean item leaves with its
prior work erased. The refund is the sum of `Enchantment.getMinCost` at each
stripped level, halved upward with a random bonus of up to one less than that
half again,
and it arrives as orbs from `ExperienceOrb.award` at the block — on the
ground, not in the player
([hunger and experience](../player/hunger-and-experience.md)).
## What each path is allowed to add
Two predicates are doing the work, and the difference between them is the
difference between the enchantments an axe is *offered* and the enchantments
an axe can *hold*.
`Enchantment.canEnchant` asks whether the item's *type* is in the
definition's supported set; `Enchantment.isSupportedItem` asks exactly the
same question of a stack and is called from nowhere but the method below.
`Enchantment.isPrimaryItem` asks the supported question **and** the narrower
primary-items question on top, falling back to the supported set when the
definition names no primary items. The narrow one lives in
`EnchantmentHelper.getAvailableEnchantmentResults`, which is
`EnchantmentHelper.selectEnchantment`'s own — so the table, the cost-based
providers and chest loot all use it, and the anvil and `/enchant` do not. In
vanilla exactly
five enchantments declare a narrower primary set than their supported one.
Three of them are melee enchantments whose supported set reaches axes and
whose primary set stops at swords and spears, which is why no enchanting
table has ever offered Sharpness on an axe while every anvil will put it
there. A fourth does the same to the mace, and the fifth is Thorns, offered
only on a chestplate and wearable anywhere.
The anvil uses `Enchantment.canEnchant`, overridden to true when the target
is an `Items.ENCHANTED_BOOK` or the player has infinite materials, so books
collect anything. Its arithmetic per transferred enchantment is short: the
same level on both sides merges to one higher, different levels take the
maximum, and the winner is clamped to `Enchantment.getMaxLevel`. An
enchantment the target cannot take is dropped and **costs nothing**; one
that conflicts with something already on the result is dropped *and* adds
one to the price per conflicting pair — the anvil's only punitive rule. If
nothing survives, the result slot is emptied.
### The ceilings, and who ignores them
`/enchant` is the shortest path and, contrary to its reputation, not the
laxest. `EnchantCommand` rejects a level above `Enchantment.getMaxLevel`
before it looks at any target, then per target requires a `LivingEntity`
whose `LivingEntity.getMainHandItem` is non-empty, then checks
`Enchantment.canEnchant` and `EnchantmentHelper.isEnchantmentCompatible`
against what the stack already carries — and from there it is the same tail
as everything else. What it skips is the primary filter, the enchantability
component and the cost, not the supported-items or level rules. It also
accepts a level of zero, which passes every check, reports success, and
changes nothing.
The genuine ceiling-breaker is elsewhere. `SetEnchantmentsFunction` writes
through `ItemEnchantments.Mutable.set`, whose only clamp is 255, with no
reference to `Enchantment.getMaxLevel` at all: a loot table can hand out
Sharpness 200 and nothing else on this page can. Exclusivity, by contrast,
is one static method everywhere — `Enchantment.areCompatible`, wrapped by
`EnchantmentHelper.isEnchantmentCompatible` and
`EnchantmentHelper.filterCompatibleEnchantments` — and it is **symmetric**,
failing if either side's exclusive set names the other, and failing an
enchantment against itself.
## Where the randomness comes from
The anvil and the grindstone roll a die each — for the chip and for the refund
— but only the table and the provider and loot paths roll one to decide *what
you get*, and they roll it in the same place:
`EnchantmentHelper.selectEnchantment`, a short method
with four distinct sources of variance stacked on one another.
```mermaid
flowchart TD
A["a cost arrives: three from the table, a sampled IntProvider from a provider, a NumberProvider from a loot table"] --> B{"does the stack have DataComponents.ENCHANTABLE"}
B -- no --> Z["empty list, and the caller adds nothing"]
B -- yes --> C["raise the cost by one plus two independent rolls scaled by the enchantability value"]
C --> D["scale by a triangular span of plus or minus 15 percent, round, clamp to at least 1"]
D --> E["getAvailableEnchantmentResults keeps an enchantment only if it is primary for this item, or the item is a plain book"]
E --> F["for each survivor, take the highest level whose min and max cost bracket contains the value"]
F --> G{"any candidates at all"}
G -- no --> Z
G -- yes --> H["weighted pick by Enchantment.getWeight"]
H --> I{"a fresh roll under 50 is at most the cost"}
I -- no --> Y["the list, out"]
I -- yes --> J["drop every candidate incompatible with the last pick"]
J --> K{"anything left"}
K -- no --> Y
K -- yes --> L["weighted pick again, then halve the cost"]
L --> I
```
The enchantability perturbation is the first place the item matters:
`Enchantable.value` — gold's is famously high — widens two independent rolls
that only ever push the cost **up**. The span that follows is triangular
rather than flat, so the extremes are rare. The bracket test in
`EnchantmentHelper.getAvailableEnchantmentResults` walks levels downward and
stops at the first fit, so a high cost buys a high level of one enchantment
rather than more of them. Buying more is the loop's job: the cost halves
after every extra pick, so by the third or fourth pass the roll is nearly
always lost — while from a cost of forty-nine up the first extra is certain.
The table adds one more layer. `EnchantmentMenu.slotsChanged` seeds its
`RandomSource` with `Player.enchantmentSeed` for the three costs, then
re-seeds it with the seed **plus the slot number** before each selection,
which is why the three offers are independent of each other and yet
reproducible. `EnchantmentHelper.getEnchantmentCost` returns zero outright
for an item with no `DataComponents.ENCHANTABLE`, and a slot whose cost came
out below its own index plus one is zeroed too.
Bookshelves reach it as a plain integer. `EnchantmentMenu` walks
`EnchantingTableBlock.BOOKSHELF_OFFSETS` — a fixed list of thirty-two
offsets, the outer ring of a five-by-five footprint at two heights — and
`EnchantingTableBlock.isValidBookShelf` requires the block at the offset to
be in `BlockTags.ENCHANTMENT_POWER_PROVIDER` **and** the block between it
and the table to be in `BlockTags.ENCHANTMENT_POWER_TRANSMITTER`
([tags](../foundations/tags.md)). That between position halves the X and Z
offsets but leaves Y alone, so the upper ring's gap is checked at the
bookshelf's own height, not the table's. The clamp to fifteen happens
inside `EnchantmentHelper.getEnchantmentCost`, not in the walk.
**Fifteen** — the shelf count above which nothing changes, and twice which
is the floor on the bottom offer (`EnchantmentHelper.getEnchantmentCost`).
## What is decided on which side
The clue you hover is not a hint about what you might get: it is a genuine
member of the exact list you *will* get. `EnchantmentMenu.slotsChanged` runs
the selection for real, shows one entry of the result at random and throws
the rest away, and `EnchantmentMenu.clickMenuButton` runs the same selection
again from the same seed and slot and applies all of it. The one wrinkle is
the plain book, which has one random entry deleted from its list before either
use — unless the list has only one entry, which survives.
```mermaid
sequenceDiagram
participant EScr as EnchantmentScreen
participant EM as EnchantmentMenu
participant EH as EnchantmentHelper
participant Player as Player
participant SGPL as ServerGamePacketListenerImpl
participant SP as ServerPlayer
Note over EM: the sword lands in slot 0 and slotsChanged runs on the server
EM->>EM: walk BOOKSHELF_OFFSETS, count the valid shelves
EM->>EH: getEnchantmentCost three times, from a stream seeded with the player seed
EM->>EH: selectEnchantment per slot, re-seeded with the seed plus the slot
EH-->>EM: a list per slot, one entry of which becomes the clue
EM->>SP: broadcastChanges
SP-->>EScr: the changed data slots, of ten: three costs, the seed, six clues
Note over EScr: EnchantmentNames.initSeed makes the alphabet stable for this seed
EScr->>EM: clickMenuButton on the client copy, whose level access is NULL
EM-->>EScr: true only if the lapis and the levels are really there
EScr->>SGPL: ServerboundContainerButtonClickPacket, via MultiPlayerGameMode
SGPL->>EM: clickMenuButton on the server copy
EM->>EH: selectEnchantment again, same seed and slot, same list
EM->>Player: onEnchantmentPerformed, take slot plus one levels, re-roll the seed
EM->>EH: updateEnchantments once per entry, through ItemStack.enchant
Note over EM,SP: consume the lapis, award Stats.ENCHANT_ITEM, fire CriteriaTriggers.ENCHANTED_ITEM
EM->>EM: slotsChanged again, three fresh offers from the new seed
SP-->>EScr: broadcastChanges, then the ten values again, all different
```
The predicted click is the sharpest thing on that diagram.
`EnchantmentScreen.mouseClicked` calls `EnchantmentMenu.clickMenuButton` on
its own local menu and only sends the packet if that call returns true. On
the client the menu's level access is `ContainerLevelAccess.NULL`, whose
evaluation returns an empty optional without running the action at all — so
the entire enchanting body is skipped, and what the client really evaluates
is the guard in front of it: the lapis count, the level requirement, and
`Player.hasInfiniteMaterials`. The affordability check is real on both
sides; the enchanting is real on one.
[Containers and menus](containers-and-menus.md) has the data-slot and
button-click machinery in general.
The ten slots are ordinary `DataSlot` entries — three `DataSlot.shared`
views onto the cost array, one `DataSlot.standalone` holding the seed — and
they reach the client one `ClientboundContainerSetDataPacket` each as
`AbstractContainerMenu.broadcastChanges` diffs them against its remote copy.
The clue slots carry a **numeric registry id** that `EnchantmentScreen`
resolves against its own registry copy — the same registry copy that
`ItemEnchantments`' stream codec needs to name the enchantments on any stack
the client is sent. The seed slot is
the only route by which `Player.enchantmentSeed` ever reaches a client: it
is written to the player file as *XpSeed*, re-rolled on read if it loads
back as zero, copied unconditionally by `ServerPlayer.restoreFrom` across
death and dimension change, and named in no packet of its own.
`EnchantmentNames.initSeed` then seeds one shared `RandomSource` with it per
frame and `EnchantmentNames.getRandomName` draws three or four words from a
fixed list in the *alt* font — same seed, same three lines, every time.
## The paths that never show a player anything
The provider path runs at spawn. `Mob.enchantSpawnedEquipment` calls
`EnchantmentHelper.enchantItemFromProvider`, which looks a provider up in
`Registries.ENCHANTMENT_PROVIDER` and hands the stack's mutable enchantment
map to `EnchantmentProvider.enchant`. `EnchantmentsByCost` and
`EnchantmentsByCostWithDifficulty` go through
`EnchantmentHelper.selectEnchantment`, so a mob's gear is rolled by exactly
the arithmetic the table uses, with the regional difficulty widening the
cost; `SingleEnchantment` skips selection entirely, upgrading one named
enchantment to a sampled level clamped only to that enchantment's own range,
never asking whether the item supports it. Six of the seven providers
`VanillaEnchantmentProviders` registers are that third kind.
The loot path runs wherever a loot table does, and villager trades are on
it: a `VillagerTrade` carries a list of `LootItemFunction`s applied to what
it gives, and the librarian's enchanted book is `EnchantRandomlyFunction`
with compatibility checking turned *off*, followed by a filter that discards
the trade if the result somehow is not an enchanted book.
`EnchantWithLevelsFunction` is the chest-loot one and calls
`EnchantmentHelper.enchantItem` — the same selection again;
`SetEnchantmentsFunction` is the deterministic one. Both random ones can set
`DataComponents.ADDITIONAL_TRADE_COST` when the context offers
`LootContextParams.ADDITIONAL_COST_COMPONENT_ALLOWED`, which is how a strong
enchantment makes a trade dearer ([loot tables](loot-tables.md),
[contexts and predicates](contexts-and-predicates.md)).
`EnchantedCountIncreaseFunction` sits in the same package and is the odd one
out: it adds nothing, reading a level off the killer with
`EnchantmentHelper.getEnchantmentLevel` to multiply a drop count. It
consumes this page's output rather than producing any.
One more producer belongs in nobody's mental model of enchanting.
`CreativeModeTabs` builds the creative enchanted books with
`EnchantmentHelper.createBook` — maximum level only in the tab, every level
in the search, through
`CreativeModeTabs.generateEnchantmentBookTypesOnlyMaxLevel` and
`CreativeModeTabs.generateEnchantmentBookTypesAllLevels`.
## Where to look
`EnchantmentMenu.slotsChanged` · `EnchantmentMenu.clickMenuButton` ·
`EnchantingTableBlock.BOOKSHELF_OFFSETS` ·
`EnchantingTableBlock.isValidBookShelf` ·
`EnchantmentHelper.getEnchantmentCost` ·
`EnchantmentHelper.selectEnchantment` ·
`EnchantmentHelper.getAvailableEnchantmentResults` ·
`EnchantmentHelper.filterCompatibleEnchantments` ·
`EnchantmentHelper.updateEnchantments` · `ItemStack.enchant` ·
`Enchantment.areCompatible` · `Enchantment.isPrimaryItem` ·
`Enchantment.canEnchant` · `AnvilMenu.createResult` · `AnvilMenu.onTake` ·
`GrindstoneMenu` · `EnchantmentProvider` · `VanillaEnchantmentProviders` ·
`EnchantRandomlyFunction` · `EnchantWithLevelsFunction` ·
`SetEnchantmentsFunction` · `EnchantCommand` ·
`Player.onEnchantmentPerformed` · `EnchantmentScreen.mouseClicked` ·
`EnchantmentNames`
---
*Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes `tools/verify_names.py`.*
---
# Contexts and predicates
> Verified against **Minecraft 26.2** · Part VII · A command asks *is this true, here, of this entity?* — and the machinery that answers is the same machinery that decides what a chest contains.
You type `/execute if predicate example:in_the_rain run say wet`. Before
anything can be tested, the server has to turn *here* into something a
data-pack file is allowed to interrogate: a position, maybe an entity,
nothing else. It does that by building a small typed map of parameters and
handing it to an object loaded out of a registry, which returns a boolean.
That machinery is usually called the loot system, because most of it lives
in `net/minecraft/world/level/storage/loot` — but the keys and the sets it
is built from live in `net/minecraft/util/context` and know nothing about
loot at all. **Twelve of the twenty-six parameter sets never roll a loot
table**, and the set that is enforced is always the *caller's*: a loot
table's own declared *type* is read exactly once, by the load-time
validator, and never consulted again while the game is running.
## Two packages, one machine
```mermaid
flowchart TD
subgraph U["net/minecraft/util/context — knows nothing about loot"]
CK["ContextKey, an Identifier plus a static type"]
CKS["ContextKeySet, the required keys and the allowed keys"]
CMap["ContextMap, the checked bag of values"]
end
subgraph L["net/minecraft/world/level/storage/loot"]
LPar["LootParams, the immutable inputs"]
LCtx["LootContext, one invocation"]
Users["LootItemCondition, NumberProvider, LootItemFunction"]
end
Slot["SlotSource, in world/item/slot"]
CK -->|"declared required or optional by ContextKeySet.Builder"| CKS
CKS -->|"ContextMap.Builder.create validates against it"| CMap
CMap -->|"wrapped, with a ServerLevel beside it"| LPar
LPar -->|"plus a random source and a resolver"| LCtx
LCtx -->|"getParameter and getOptionalParameter"| Users
LCtx -->|"the same interface, from outside the package"| Slot
```
The dividing line is the whole argument of this page. `ContextKey`,
`ContextKeySet` and `ContextMap` are three small general-purpose classes in
`net/minecraft/util/context` with no dependency on items, blocks, entities
or loot. Everything below them is in the loot package, and everything
*above* them is whoever wants a question answered. A loot table is one such
caller. The others are commands, entity selectors, advancement triggers,
villager trades, every enchantment effect — and, on the *client*,
`SlotDisplayContext`, which builds a `ContextMap` of its own so a recipe book
entry can resolve itself into stacks.
## The cast
| class | package | what it owns | when |
|---|---|---|---|
| `ContextKey` | *util/context* | one parameter's name and its Java type, and nothing else | — |
| `ContextKeySet` | *util/context* | which keys a call site must supply, and which it may | built once at class-init |
| `ContextMap` | *util/context* | the values, and the only place the contract is enforced | server main for loot; the client builds its own for a recipe display |
| `LootParams` | *storage/loot* | the `ServerLevel`, the map, the dynamic-drop callbacks, the luck | server main |
| `LootContext` | *storage/loot* | one invocation: the random source, the reference resolver, the visited stack | server main |
| `LootItemCondition` | *storage/loot/predicates* | a predicate over a `LootContext` — the boolean answer | server main |
| `NumberProvider` | *storage/loot/providers* | a float over a `LootContext` — the numeric answer | server main |
| `ValidationContext` | *storage/loot* | at load, whether an element asked for a key its set does not have | background executor |
> **For a 1.21-era reader.** *LootContextParam* is `ContextKey` and
> *LootContextParamSet* is `ContextKeySet`, both moved out to
> `net/minecraft/util/context`, and the predicate library that consumes them
> has left *critereon* for `net/minecraft/advancements/predicates` and
> `net/minecraft/advancements/triggers`.
> [The drift table](../../reference/naming-drift.md) has the rest.
## A key is a name with a type welded to it
`ContextKey` is an `Identifier` and a phantom type parameter. It has no
value, no default, no validation and no registry — `ContextKey.vanilla`
just makes one in the *minecraft* namespace, and only seventeen exist: the
fifteen static fields of `LootContextParams`, plus the two on
`SlotDisplayContext` that let a client draw a recipe. The type parameter is what
makes the rest of the system safe: `LootContextParams.ORIGIN` is a key of
`Vec3`, `LootContextParams.TOOL` a key of `ItemInstance` (the read-only
item view), `LootContextParams.ENCHANTMENT_LEVEL` a key of a boxed *int*, and
a reader gets that type back without a cast.
A data-pack author never writes a key's name directly. They write a
*target* — *this*, *attacker*, *target_entity*, *tool*, *block_entity* —
and `LootContextArg` turns it into a key, out of three enums nested in
`LootContext`: `LootContext.EntityTarget` with six entity keys,
`LootContext.BlockEntityTarget` and `LootContext.ItemStackTarget` with one
each. All three read through `LootContextArg.SimpleGetter`, which uses the
**optional** accessor — so a target the current set does not carry
evaluates to nothing rather than throwing.
## A set is a contract, and the caller signs it
`ContextKeySet` holds two sets: `ContextKeySet.required` and
`ContextKeySet.allowed`, the second the union of required and optional, and
its `ContextKeySet.Builder` refuses to make one key both in either order.
`LootContextParamSets` registers twenty-six of them into a private bi-map,
which is what `LootContextParamSets.CODEC` reads when a data pack names a
set by id — and the ids do not always match the field names:
`LootContextParamSets.PIGLIN_BARTER` is *barter* and
`LootContextParamSets.ALL_PARAMS` is *generic*. Every set's keys are
tabulated in [Loot context parameter
sets](../../reference/loot-context-params.md).
`LootContextParamSets.ALL_PARAMS` deserves its own warning, because it is
the default a loot table with no declared *type* gets
(`LootTable.DEFAULT_PARAM_SET`) and the set standalone predicate and
item-modifier files are validated against — and it is **not all of them**.
It declares eleven of the fifteen keys, all of them required, and omits
`LootContextParams.INTERACTING_ENTITY`, `LootContextParams.TARGET_ENTITY`,
`LootContextParams.ENCHANTMENT_LEVEL` and
`LootContextParams.ENCHANTMENT_ACTIVE`. The practical consequence: a
standalone predicate file that asks about the interacting or target
entity, or about whether an enchantment is active, *is* flagged at load;
one that asks about a block state or a damage source is not, even though
`LootContextParamSets.COMMAND` — the set `/execute if predicate` actually
builds — carries neither.
## Three ways a parameter can be missing
`ContextMap.Builder.create` is where the contract is enforced, and it is
the only place: it throws if the values collected include a key the set
does not allow, and again if the set requires a key that is absent. Note
what it compares — the keys the **caller** supplied against the set the
**caller** named. Nothing on this path ever sees the loot table.
1. **At build time.** `ContextMap.Builder.create` throws, naming the
offending keys. That is a programming error rather than a data one, and
it takes the tick down with it.
2. **At read time.** `LootContext.getParameter` goes to
`ContextMap.getOrThrow` and throws; `LootContext.getOptionalParameter`
returns nothing, and most conditions and functions degrade quietly
rather than fail.
3. **At load time.** `ValidationContext.validateContextUsage` compares
what an element declares it reads —
`LootContextUser.getReferencedContextParams`, overridden by
twenty-seven classes — against `ContextKeySet.allowed`, and reports the
difference.
The third is the loose one, in two ways. It checks against *allowed*, not
*required*, so an element that reads an optional key passes validation and
can still take path 2 at runtime. And what
`ReloadableServerRegistries.validateLootRegistries` does with the collected
problems is **log them as warnings**: a predicate that asks for a parameter
it cannot have loads fine and misbehaves later. The same validation is a
hard error in exactly two places, both applied by a codec —
`Validatable.validatorForContext` for `VillagerTrade`, and its list form
`Validatable.listValidatorForContext` for every conditional effect in
`EnchantmentEffectComponents`. Those two build a
`ValidationContext` with no resolver, so `ValidationContext.allowsReferences`
is false and a `ConditionReference` inside a trade or an enchantment effect
is rejected outright.
## Inputs, then one invocation
`LootParams` is the immutable half: a `ServerLevel`, the `ContextMap`, a
map of `LootParams.DynamicDrop` callbacks keyed by `Identifier`, and a
float of luck. The `ServerLevel` is the mechanical guarantee that none of
this runs on the client — `LootParams.Builder` takes one in its
constructor and `LootContext.Builder.create` dereferences the server off it
for the registries, so a `ClientLevel` cannot produce a context at all.
`LootContext` is the per-invocation half, and it adds three things
`LootParams` does not have: the chosen `RandomSource`, a
`HolderGetter.Provider` that resolves references to other loaded elements,
and a set of `LootContext.VisitedEntry` used as a recursion guard.
`LootContext.pushVisitedElement` returns false when the element is already
present, which is how `ConditionReference` detects a cycle — it logs an
infinite loop and answers false. The guard is a stack, not a ledger: the
entry is popped afterwards, so naming the same predicate twice in one
evaluation is fine, and only genuine re-entrancy trips it. Both command
call sites seed it with the top-level predicate before testing, so a
predicate that references itself by name is caught on the first hop.
**Named random sequences belong to the context, not to the table.**
`LootContext.Builder.create` takes an optional `Identifier` and picks a
random source three ways, in order: an explicit source or non-zero seed
handed to the builder, else `MinecraftServer.getRandomSequence` for the
named sequence, else `Level.getRandom`. `RandomSequences` is a `SavedData`
that derives each sequence's seed from the world seed, a salt and the
sequence id, which is what makes a named sequence reproducible across
restarts. A loot table supplies that identifier from its own field — and so
does `TradeSet.randomSequence`, which is why a villager's trade selection
is reproducible by the same mechanism and has nothing to do with loot. The
seeded-chest half of the story belongs to [loot tables](loot-tables.md).
## What reads a context
`LootContextUser` — *what did you read out of the map* — has six
sub-interfaces, and two of them carry the traffic, both reached by codec
through a registry of types. `LootItemCondition` is a predicate over a
`LootContext` with twenty registered types, from
`LootItemEntityPropertyCondition` and `LocationCheck` to
`EnchantmentActiveCheck` and `ConditionReference`; `NumberProvider`
returns a float and has eight, from `ConstantValue` and `UniformGenerator`
to `EnchantmentLevelProvider`. Their codecs are forgiving in the same
shape: `LootItemCondition.DIRECT_CODEC` accepts a bare *list* as an
implicit `AllOfCondition`, and `NumberProviders.CODEC` accepts a bare
number as a `ConstantValue` and an untagged object as a
`UniformGenerator`. A third family, `SlotSource` in
`net/minecraft/world/item/slot`, reads a context through the same
interface from outside the loot package.
`ContextAwarePredicate` is the bridge the advancement system uses: a list
of `LootItemCondition` composed into one predicate, entered through
`ContextAwarePredicate.matches`. The *player* half of every trigger goes
through it — `EntityPredicate.wrap` folds an `EntityPredicate` into a
`LootItemEntityPropertyCondition` — but the rest of
`net/minecraft/advancements/predicates` need not: a trigger instance can hold
an `ItemPredicate` or a `LocationPredicate` outright and test it with no
context at all, which `ConsumeItemTrigger` and `DistanceTrigger` both do.
## Who asks, and with which set
| caller | set | when |
|---|---|---|
| `BlockBehaviour.BlockStateBase.getDrops` | `LootContextParamSets.BLOCK` | any block break ([block breaking](../blocks/block-breaking.md)) |
| `Block.dropFromBlockInteractLootTable` | `LootContextParamSets.BLOCK_INTERACT` | beehives, cave vines, carving a pumpkin, sweet berries |
| `LivingEntity.dropFromLootTable` | `LootContextParamSets.ENTITY` | death ([damage and death](../entities/damage-and-death.md)) |
| `LivingEntity.dropFromShearingLootTable` | `LootContextParamSets.SHEARING` | sheep, mooshrooms, snow golems, bogged |
| `LivingEntity.dropFromGiftLootTable` | `LootContextParamSets.GIFT` | hero gifts, cat gifts, chicken eggs, sniffer digs |
| `LivingEntity.dropFromEntityInteractLootTable` | `LootContextParamSets.ENTITY_INTERACT` | brushing an armadillo |
| `RandomizableContainer.unpackLootTable` | `LootContextParamSets.CHEST` | first touch of a structure container |
| `ContainerEntity.unpackChestVehicleLootTable` | `LootContextParamSets.CHEST` | first touch of a chest minecart or chest boat |
| `FishingHook.retrieve` | `LootContextParamSets.FISHING` | reeling in — luck is the hook's plus the owner's |
| `BrushableBlockEntity` | `LootContextParamSets.ARCHAEOLOGY` | brushing suspicious sand |
| `PiglinAi.getBarterResponseItems` | `LootContextParamSets.PIGLIN_BARTER` | bartering |
| `Mob.createEquipmentParams` | `LootContextParamSets.EQUIPMENT` | the `EquipmentUser.equip` path, on mob spawn |
| `VaultBlockEntity` | `LootContextParamSets.VAULT` | a trial chamber vault's display item and its reward |
| `TrialSpawner.ejectReward`, `TrialSpawnerStateData.getDispensingItems` | `LootContextParamSets.EMPTY` | a trial spawner's reward and its dispensed items |
| `AdvancementRewards.grant` | `LootContextParamSets.ADVANCEMENT_REWARD` | advancement loot |
| `Enchantment.damageContext` and its four siblings | the four enchanted sets plus `LootContextParamSets.HIT_BLOCK` | [an enchantment effect's condition](enchantments.md) |
| `LootCommand` | block, entity, chest or fishing | `/loot` |
| `ItemCommands.applyModifier` | `LootContextParamSets.COMMAND` | `/item … with` |
| **`ExecuteCommand`** | `LootContextParamSets.COMMAND` | **`/execute if predicate`** |
| **`EntitySelectorOptions`** | `LootContextParamSets.SELECTOR` | **a selector's *predicate* argument** |
| **`EntityPredicate.createContext`** | `LootContextParamSets.ADVANCEMENT_ENTITY` | **every advancement trigger that tests an entity** |
| **`ItemUsedOnLocationTrigger`, `AnyBlockInteractionTrigger`** | `LootContextParamSets.ADVANCEMENT_LOCATION` | **placing or using an item on a block** |
| **`DefaultBlockInteractionTrigger`** | `LootContextParamSets.BLOCK_USE` | **right-clicking a block, for an advancement** |
| **`AbstractVillager.addOffersFromTradeSet`** | `LootContextParamSets.VILLAGER_TRADE` | **rolling and filtering a villager's offers** |
The bold rows are the ones with no loot table anywhere in sight: a boolean,
or in the villager's case a `MerchantOffers`, is the whole output.
`ValidationContextSource` even keeps a cached
`LootContextParamSets.ADVANCEMENT_ENTITY` context around, because so much
of the advancement tree validates against it.
Count *sets* rather than rows and the picture is starker. Fourteen of the
twenty-six are named above by a caller that goes on to roll a `LootTable`.
**The other twelve never do.** Six are the bold rows —
`LootContextParamSets.COMMAND` counts among them, because its other caller,
`/item … with`, applies an item modifier rather than a table. Five are the
enchantment sets, built only by `Enchantment` to decide whether an effect
fires. The twelfth is `LootContextParamSets.ALL_PARAMS`, which is never
used to build a `ContextMap` at all: it exists solely as a validation
context and as `LootTable.DEFAULT_PARAM_SET`. That is why this page is not
called *loot tables*.
## The trace: `/execute if predicate`
```mermaid
sequenceDiagram
participant ExecC as ExecuteCommand
participant LootP as LootParams
participant CMap as ContextMap
participant LootC as LootContext
participant LIC as LootItemCondition
Note over ExecC: the argument already holds a Holder, resolved at parse time
ExecC->>LootP: LootParams.Builder on the level, ORIGIN required, THIS_ENTITY optional
LootP->>CMap: ContextMap.Builder.create against LootContextParamSets.COMMAND
CMap-->>LootP: throws on an unexpected key, or on an absent required one
ExecC->>LootC: LootContext.Builder.create with no random sequence
Note over LootC: the random source is Level.getRandom, the resolver is the reloadable registries
ExecC->>LootC: pushVisitedElement, seeding the recursion guard with this predicate
ExecC->>LIC: test
LIC->>LootC: getParameter and getOptionalParameter
LIC-->>ExecC: a boolean, and the branch is taken or not
```
Four details in that picture are worth pulling out.
**The predicate is resolved before the command runs.** The argument type is
`ResourceOrIdArgument.LootPredicateArgument`, which accepts *either* a
registry id *or* an inline SNBT object and hands back a `Holder` — a
reference in the first case, a direct holder over a freshly parsed
condition in the second. An unknown id fails at parse time, as a command
syntax error, not at execution.
**`LootContextParamSets.COMMAND` is thin.** It requires
`LootContextParams.ORIGIN` and optionally allows
`LootContextParams.THIS_ENTITY`, and that is all: a predicate run from
`/execute` cannot see a block state, a damage source or a tool, whatever
the standalone-file validator let through. The selector's version,
`LootContextParamSets.SELECTOR`, differs in exactly one way — it makes the
entity *required*, because a selector always has one.
**There is no random sequence.** `ExecuteCommand` passes an empty optional,
so `Level.getRandom` is what a `LootItemRandomChanceCondition` inside the
predicate draws from — not correlated between runs, not reproducible across
restarts.
**The selector path is the same code with one difference.**
`EntitySelectorOptions` builds its context *per candidate entity*, inside
the predicate it hands the parser, and looks the condition up itself
through the reloadable registries rather than through the argument type —
so a missing predicate there is a silent false, not a syntax error.
## None of this crosses the wire
`LootDataType` is the three loot registries expressed as data:
`LootDataType.TABLE` over `Registries.LOOT_TABLE`,
`LootDataType.PREDICATE` over `Registries.PREDICATE` and
`LootDataType.MODIFIER` over `Registries.ITEM_MODIFIER`. Each pairs a
registry key with a codec and a `LootDataType.ContextGetter` — the function
that says which `ContextKeySet` an element of that type is validated
against. Predicates and item modifiers get the constant
`LootContextParamSets.ALL_PARAMS`; tables get `LootTable.getParamSet`, and that
context getter is its only caller in the game. That is the sense in which a
table's declared type is never checked at runtime.
All three live in `RegistryLayer.RELOADABLE`, loaded by
`ReloadableServerRegistries.reload` on a background executor — one task per
type, each scanning the data packs, registering, loading that registry's
tags, and only then validating ([the resource
system](../foundations/resource-system.md), [identifiers and
registries](../foundations/identifiers-and-registries.md), and
[codecs](../foundations/codecs-nbt-json.md) for the files themselves). None
of the three appears in `RegistryDataLoader.SYNCHRONIZED_REGISTRIES`, the
list `RegistrySynchronization.isNetworkable` tests, so none is ever packed
for a client. What crosses the wire is only the *result* — a container
packet, an item entity, a command's success.
The two dependants outside this part are Part XIII's commands, which own
`/execute if predicate` and the selector argument, and the advancement
system, whose triggers build a context per tested entity.
## Where to look
`ContextKey` · `ContextKeySet` · `ContextMap` · `LootContextParams` ·
`LootContextParamSets` · `LootParams` · `LootContext` · `LootContextArg` ·
`LootContextUser` · `LootItemCondition` · `LootItemConditions` ·
`ConditionReference` · `NumberProvider` · `NumberProviders` ·
`SlotSource` · `ContextAwarePredicate` · `EntityPredicate` ·
`Validatable` · `ValidationContext` · `ValidationContextSource` ·
`LootDataType` · `ReloadableServerRegistries` · `ExecuteCommand` ·
`EntitySelectorOptions` · `ResourceOrIdArgument` · `RandomSequences`
---
*Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes `tools/verify_names.py`.*
---
# Loot tables
> Verified against **Minecraft 26.2** · Part VII · A player opens a dungeon chest for the first time, and every item in it comes into existence between the click and the screen.
You break into a mossy cobblestone room, there is a chest against the wall,
and you right-click it. Between that click and the inventory screen the server
does something it will do exactly once for this chest: it looks up a loot
table, rolls it, scatters the results across the empty slots, and throws the
recipe away. Until that moment the chest is **genuinely empty on disk** — the
region file holds a table key and a seed and no items at all. And the roll is a
one-shot in the strictest sense: `RandomizableContainer.unpackLootTable` clears
the stored key *before* it rolls, and the thing that triggers it is not "a
player opens this chest" but "anything reads this container". A hopper
underneath taking one item, or a comparator behind the wall asking how full it
is, will commit the roll with **no player present, and therefore no luck, for
good**.
The typed parameters the table is handed, the predicates it tests and the sets
those belong to are not loot machinery — they are the general context engine
that enchantment effects, advancement triggers, `/execute if predicate` and
villager trade filters all run on, and
[contexts and predicates](contexts-and-predicates.md) is where they are
explained. Loot is that engine's oldest and largest client. This page is the
worked example: what a table *is*, how one draw picks an item, and why the
chest was empty.
## The cast
| class | what it decides | thread |
|---|---|---|
| `LootTable` | the parameter set, an optional random sequence, the pools, the table's own functions, and the ways out — `LootTable.fill` into a container, `LootTable.getRandomItems` into a list, and the unsplit `LootTable.getRandomItemsRaw` a nested table uses | server main |
| `LootPool` | whether the pool runs at all, and how many draws it makes | server main |
| `LootPoolEntryContainer` | the entry algebra — `ComposableEntryContainer.expand` answers *did I contribute* | server main |
| `LootPoolSingletonContainer` | weight and quality — one of the two places luck reaches anything | server main |
| `LootItemFunction` | forty-three stack-to-stack transforms, composed into one per level | server main |
| `RandomizableContainer` | the stored table key and seed, and the one-shot unpack | server main |
| `LootContext` | which random source this draw uses, and the recursion guard | server main |
| `ReloadableServerRegistries` | loading the three loot registries and validating them | the background executor |
## The chest was empty before you got there
Nothing wrote items into that chest at world generation. `MonsterRoomFeature`
places the room, then places up to two chests, giving each up to three tries
at a position — every try needing an air block with exactly one solid
horizontal neighbour — and for each chest that lands calls
`RandomizableContainer.setBlockEntityLootTable` with
`BuiltInLootTables.SIMPLE_DUNGEON` and a fresh long from the feature's own
random. What lands on the live `ChestBlockEntity` is a **key and a seed**, and
nothing else. Structure pieces do the same through `StructurePiece.createChest`
and `StructurePiece.createDispenser`; `BuiltInLootTables` holds a hundred and
seventeen named keys plus two per-dye-colour sets for sheep, spread over
thirteen path prefixes — chests and dispensers, gameplay drops, shearing and
brushing, harvesting and carving, decorated pots, equipment, archaeology,
spawners and a charged creeper's.
The seed reaches disk on the next save, and from then on the emptiness is
self-perpetuating. `RandomizableContainer.trySaveLootTable` writes the key —
and the seed only when it is non-zero — and answers *yes, I handled this*, so
`ChestBlockEntity.saveAdditional` never writes an item list.
`RandomizableContainer.tryLoadLootTable` answers the same way on the way back
in, and `ChestBlockEntity.loadAdditional` **skips reading items entirely**. A
chest nobody has touched has no inventory in any file the game has ever
written.
## From the click to the screen
```mermaid
sequenceDiagram
participant SPGM as ServerPlayerGameMode
participant CBE as ChestBlockEntity
participant SP as ServerPlayer
participant RCont as RandomizableContainer
participant LT as LootTable
participant LPool as LootPool
participant ChestM as ChestMenu
SPGM->>CBE: ChestBlock.useWithoutItem resolves a menu provider, and a single chest is its own
CBE->>SP: openMenu, whose body is ServerPlayer's
SP->>CBE: createMenu, guarded by canOpen
CBE->>RCont: unpackLootTable, with the opening player
Note over RCont: setLootTable to null BEFORE the roll, one shot
RCont->>LT: fill, on the CHEST set, with the stored seed
LT->>LPool: addRandomItems, once per pool
LPool-->>LT: stacks, each through createStackSplitter
LT->>LT: getAvailableSlots then shuffleAndSplitItems then setItem
CBE->>ChestM: threeRows over the now-filled container
SP->>ChestM: ClientboundOpenScreenPacket goes first, then initMenu
ChestM-->>SP: sendAllDataToRemote, one ClientboundContainerSetContentPacket
```
**The click** arrives as `ServerPlayerGameMode.useItemOn` and reaches
`ChestBlock.useWithoutItem`
([block interaction](../blocks/block-interaction.md)), which asks
`ChestBlock.getMenuProvider` for something to open. A single chest *is* its own
provider — the combiner hands back the `ChestBlockEntity`. A double chest is the
interesting case: it gets an anonymous provider wrapped round a
`CompoundContainer` that requires **both** halves to pass
`RandomizableContainerBlockEntity.canOpen`, unpacks **both** loot tables
itself, and never enters either block entity's own menu factory.
**Opening** goes through `ServerPlayer.openMenu`, which closes whatever menu was
already open, allocates a container id, and calls
`RandomizableContainerBlockEntity.createMenu`. Both gates on opening are in one
predicate: `RandomizableContainerBlockEntity.canOpen` adds a spectator clause to
the lock check it inherits from `BaseContainerBlockEntity.canOpen`, and the
spectator half bites only *while a table is still pending* — so a spectator
cannot commit the roll by peering into an unopened chest, though they can open
one that has already been rolled.
**The unpack** is `RandomizableContainer.unpackLootTable`, and its order matters
more than anything else on this page. It looks the key up through
`ReloadableServerRegistries.Holder.getLootTable` — which answers
`LootTable.EMPTY` for a missing key, never null — fires
`CriteriaTriggers.GENERATE_LOOT` if a `ServerPlayer` is doing the opening, and
**then clears the stored key**, before a single die is rolled. It builds the
parameters with `LootContextParams.ORIGIN` at the block centre and, *only if a
player is present*, that player's `Player.getLuck` and
`LootContextParams.THIS_ENTITY`. Then it calls `LootTable.fill` with the
container, those parameters and the stored seed.
**The screen** comes last and is no part of the roll. `ServerPlayer.openMenu`
sends `ClientboundOpenScreenPacket` and then calls `ServerPlayer.initMenu`,
which attaches the listener and the synchronizer; attaching a synchronizer runs
`AbstractContainerMenu.sendAllDataToRemote`, and that is the single
`ClientboundContainerSetContentPacket` carrying the freshly rolled contents
([containers and menus](containers-and-menus.md)).
## One roll, drawn
`LootTable.fill` runs every pool of the table, each pool makes some number of
independent draws, and each draw picks at most one entry. That draw is the
engine's smallest complete unit, and it is a funnel with three fan-outs in it.
```mermaid
flowchart TD
A["LootPool.addRandomItems"] --> B{"the pool conditions, all of them"}
B -->|"any fails"| Z["the pool contributes nothing"]
B -->|"all pass"| C["draws equals rolls plus floor of bonusRolls times luck"]
C --> D["ONE DRAW, repeated that many times"]
D --> E["expand every entry container, in declaration order, into candidates"]
E --> F["AlternativesEntry, an or: stops at the first child that contributes"]
E --> G["SequentialEntry, an and: stops at the first child that does not"]
E --> H["EntryGroup: every child expands, contribution ignored"]
E --> I["TagEntry in expand mode: one candidate per item in the tag"]
E --> J["NestedLootTable: one candidate that will run another whole table"]
F --> K["weight is floor of weight plus quality times luck, clamped at zero"]
G --> K
H --> K
I --> K
J --> K
K --> L{"is that above zero?"}
L -->|"no"| M["dropped from this draw entirely"]
L -->|"yes"| N["kept, and added to the running total"]
N --> O{"how many candidates survived?"}
O -->|"none, or the total is zero"| Z2["this draw yields nothing"]
O -->|"exactly one"| P["taken, consuming no randomness at all"]
O -->|"two or more"| Q["one nextInt over the total, then walk subtracting weights"]
P --> R["the entry's own functions"]
Q --> R
R --> S["then the pool's functions"]
S --> T["then the table's functions"]
T --> U["once per fill: createStackSplitter drops disabled items and cuts oversized stacks"]
U --> V["getAvailableSlots shuffles the empty slot numbers"]
V --> W["shuffleAndSplitItems breaks multi-count stacks up until they roughly fill them"]
W --> X["setItem, or a logged warning and a silent discard once the slots run out"]
```
**The algebra is boolean, not weighted.** `ComposableEntryContainer.expand`
returns a plain *did I contribute*, and each composite folds its children into
one of those in its own `CompositeEntryBase.compose` — the two-child case
literally calling `ComposableEntryContainer.or` or
`ComposableEntryContainer.and`, and the longer cases a hand-written loop with
the same short-circuit. So `AlternativesEntry` behaves like a boolean or and
`SequentialEntry` like a boolean and — neither is a weighted choice between
branches, and the validator reports an `AlternativesEntry` whose non-final
children carry no conditions, because every later alternative is then
unreachable. Nine entry types are registered in `LootPoolEntries`: the leaves
`LootItem`, `EmptyLootItem`, `TagEntry`, `NestedLootTable`, `DynamicLoot` and
`SlotLoot`, and the composites `AlternativesEntry`, `SequentialEntry` and
`EntryGroup`. `TagEntry` has two modes and they are not variations on a theme:
expanded, it becomes one weighted candidate *per item in the tag* — and those
candidates are built bare, so the entry's own functions never run on them;
unexpanded, it is a single candidate that emits **every** item in the tag at
once, with its functions intact.
**Luck touches exactly two things**, and neither is what players think it is.
`LootPool.bonusRolls` is multiplied by luck and floored to add whole extra
draws, and `LootPoolSingletonContainer.EntryBase.getWeight` is
*weight + quality × luck*, floored, then clamped at zero. Because a candidate
whose weight comes out at zero or below is discarded rather than merely made
rare, a **negative quality with enough luck removes an entry from the pool
altogether**. Everything else players call luck is something else entirely:
Fortune is `ApplyBonusCount` and `BonusLevelTableCondition`, both reading
`LootContextParams.TOOL` and asking `EnchantmentHelper` for a level on it;
Looting is `EnchantedCountIncreaseFunction` and
`LootItemRandomChanceWithEnchantedBonusCondition`, both reading
`LootContextParams.ATTACKING_ENTITY` and asking about the killer's gear.
**Functions apply innermost first.** Each level wraps the output consumer with
`LootItemFunction.decorate` over its own `LootItemFunctions.compose`, so as the
call stack unwinds a drop passes the entry's functions, then the pool's, then
the table's. Forty-two of the forty-three registered functions extend
`LootItemConditionalFunction`, whose `LootItemConditionalFunction.apply` is
final and hands the stack back
untouched when its own conditions fail — which is why a function with a failing
condition is a no-op and not a veto on the drop. They also **mutate the stack in
place and return it**, which is safe because every leaf hands out something of
its own: `LootItem` and `TagEntry` construct fresh stacks and `SlotLoot` emits
copies. `DynamicLoot` is the exception: it calls straight out to
a callback the caller registered with `LootParams.Builder.withDynamicDrop`, and
the one `ShulkerBoxBlock.getDrops` registers hands back the block entity's live
stacks uncopied.
## The scatter
`LootTable.fill` does not simply place what it rolled.
`LootTable.getAvailableSlots` collects the container's empty slot numbers and
shuffles them; `LootTable.shuffleAndSplitItems` then pulls the multi-count
stacks out of the result list and repeatedly splits one — taking a random amount
between one and half its count — until the number of pieces roughly matches the
slot count. That is why one rolled stack of arrows arrives as several partial
ones in unrelated slots — how many depends on how much of the chest is free. If the pieces outnumber the free slots, the remainder
is **logged as a warning and silently discarded**.
Above that sits `LootTable.createStackSplitter`, which every public
`LootTable.getRandomItems` and `LootTable.fill` wraps its output in: it drops
items the level's feature flags disable, and cuts anything at or over its
maximum stack size into stack-sized pieces. `NestedLootTable` deliberately calls
`LootTable.getRandomItemsRaw` instead, so a nested table's results are split
once by the outer table rather than twice.
## Where the randomness comes from
`LootContext.Builder` resolves the random source in a fixed order, and the zero
is load-bearing. `LootContext.Builder.withOptionalRandomSeed` installs a seeded
source **only when the seed is non-zero**; failing that, the table's declared
random sequence is fetched from the server's per-world `RandomSequences` through
`MinecraftServer.getRandomSequence`; failing that, the level's own random is
used. `LootTable.RANDOMIZE_SEED` names the zero, and nothing in the game reads
the constant.
So a seed of zero means *unseeded*, is indistinguishable from having no seed at
all, and is never written to NBT — which is why a chest given a loot table by
command rolls unpredictably where a structure chest, carrying a seed, rolls the
same contents whoever opens it and whenever. Neither rolls twice: the key is
gone after the first unpack either way. Named
random sequences are the other half: a table that declares one draws from a
stored, saved sequence rather than the level random, which is what keeps the
same table in the same world reproducible across a restart. Villager trades use
that mechanism from outside the loot package —
`AbstractVillager.addOffersFromTradeSet` builds its context with
`TradeSet.randomSequence`.
Loading is the only part of any of this that is not on the server thread.
`ReloadableServerRegistries.reload` schedules one load per `LootDataType` on the
background executor, builds a registry for each, loads that registry's tags,
freezes the layer and only then validates
([the resource system](../foundations/resource-system.md)). Rolling is server
main everywhere, and the guarantee is a type rather than a thread check: the
parameters are built from a `ServerLevel`, so a `ClientLevel` cannot produce
them at all. No client class references the loot package.
## Questions players ask
**Did I just lose the loot by putting a hopper under it?** Yes, and the answer
is precise about which reads count.
**Five** — the container methods `RandomizableContainerBlockEntity` overrides so
that they unpack first: `RandomizableContainerBlockEntity.isEmpty`,
`RandomizableContainerBlockEntity.getItem`,
`RandomizableContainerBlockEntity.removeItem`,
`RandomizableContainerBlockEntity.removeItemNoUpdate` and — the surprising one —
`RandomizableContainerBlockEntity.setItem`.
A comparator gets there through
`AbstractContainerMenu.getRedstoneSignalFromContainer`, which walks
`Container.getItem` over every slot. A hopper gets there the same way before it
takes anything, and so does a hopper pointing *into* the chest — it asks
whether the destination is full before it pushes, so that one commits the roll
by reading as well. `Clearable.clearContent` and `Container.getContainerSize` do not
unpack, and neither does saving — which is why `/data get block` on an unopened
chest reports the loot table key instead of committing the roll.
**Can I reference the same table twice?** Yes. The recursion guard in
`LootContext` is a **stack, not a ledger**: `LootContext.pushVisitedElement`
adds the table on the way in and `LootContext.popVisitedElement` takes it off on
the way out, so two pools pointing at the same nested table each get items, and
so do two rolls of one pool. Only genuine re-entrancy — a table inside itself —
trips it, and it is logged as an infinite loop rather than passing silently.
**Why does a shulker box in my inventory say the contents are unknown?**
`DataComponents.CONTAINER_LOOT` carries a `SeededContainerLoot` and is declared
persistent with no network codec of its own, so
`ByteBufCodecs.fromCodecWithRegistries` supplies one from the persistence codec
and the component **does** reach the client
([data components](../foundations/data-components.md)). What it cannot carry is
the contents: the client has no loot registry at all, and
`SeededContainerLoot.addToTooltip` does not try — it prints the
unknown-contents line and nothing else, on either side.
**Does the type declared on the table do anything?** Not at roll time. A table's
parameter set is read during load-time validation and never compared against the
incoming parameters; whether a chest table works is entirely down to what the
*caller* put in the map. That, and the twenty-six sets, are next door in
[contexts and predicates](contexts-and-predicates.md).
Two callers on the other side of that door are worth naming here:
`BlockBehaviour.BlockStateBase.getDrops` for every block broken
([block breaking](../blocks/block-breaking.md)) and
`LivingEntity.dropFromLootTable` for every mob killed
([damage and death](../entities/damage-and-death.md)).
`EnchantWithLevelsFunction` and `EnchantRandomlyFunction` are the loot side of
[enchanting](enchanting.md). And `EquipmentUser.equip` is the one caller that
compares the looked-up table against `LootTable.EMPTY` **by identity** to decide
whether to bother.
## Where to look
`RandomizableContainer.unpackLootTable` · `RandomizableContainerBlockEntity` ·
`ChestBlock.getMenuProvider` · `LootTable.fill` ·
`LootTable.getRandomItemsRaw` · `LootPool.addRandomItems` ·
`ComposableEntryContainer` · `LootPoolSingletonContainer.EntryBase` ·
`AlternativesEntry` · `TagEntry` · `NestedLootTable` · `DynamicLoot` ·
`LootItemFunctions` · `LootItemConditionalFunction` ·
`LootTable.createStackSplitter` · `LootContext.Builder` · `RandomSequences` ·
`BuiltInLootTables` · `ReloadableServerRegistries` · `SeededContainerLoot`
---
*Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes `tools/verify_names.py`.*
---
# VIII · The player
> Verified against **Minecraft 26.2** · Part VIII · The one entity a human is steering: what it is made of, when it runs, and the four things it does that the rest of the world does differently.
Everything in Parts IV to VII happens to the world. This part is about the
one object in it that argues back. A player is an entity like any other —
same base class, same tick, same synched data — and then almost every rule
is bent for it: it is ticked twice instead of once, it is the only thing the
server simulates and then contradicts, its inventory reports seven slots it
does not store and aliases an eighth, and its melee combat has three
separate entry points of which the famous one is the least interesting. A player recognises the part
by the friction: the snap back after a laggy jump, the swing that does more
damage if you wait, the food bar that empties from sprinting and not from
walking, the effect timer that keeps counting while the connection is down.
## The shape of the part
Part VIII is a trunk and four branches. Two pages say what a player *is* and
when it runs; everything after them is one thing a player *does*, and they
are independent of each other — except the spear, which is the sword swing's
sequel and should not be watched before it.
```mermaid
flowchart TD
PA["Player anatomy — what a player is made of"]
TT["The two-phase tick — when it runs, and what is thrown away"]
IM["Input to movement — walking, and being believed"]
SS["The sword swing — one integer, and a number rebuilt"]
SP["The spear — the same hit, twice, neither through Player.attack"]
HE["Hunger and experience — two bars the server owns"]
SE["Status effects — a list of things happening to you"]
PA -- "eight classes, forty-three slots" --> TT
TT -- "phase two is where the player acts" --> IM
TT --> SS
TT --> HE
TT --> SE
SS -- "and two other melee paths" --> SP
```
## Before you start
[Part VI](../entities/README.md) is the hard prerequisite, and two of its
pages in particular. [Entity anatomy](../entities/entity-anatomy.md),
because a player is a `LivingEntity` with three rungs added on the server
and four on the client, and this part never re-teaches the base; and
**[authority](../entities/authority.md)**, because every page here rests on
it — a `Player` is client-authoritative on *both* sides, which is why the
server's own answer for your movement is thrown away in favour of the
number you sent it. If you watch one page from
another part first, watch that one.
Then [the server tick](../server/server-tick.md) and [the level
tick](../server/server-level-tick.md), because half this part's timing
claims are about which phase something ran in — including the fact that a
player's own physics run *after* every level has finished. [Players and
sessions](../server/players-and-sessions.md) owns how a `ServerPlayer` comes
to exist at all. And [Part VII](../items/README.md) for the inventory this
part stops at the edge of: [using an item](../items/using-an-item.md) in
particular, because the spear is an item you *use*.
## Watch in this order
1. [Player anatomy](player-anatomy.md) — the vocabulary page: eight
classes, two game-mode objects, forty-three slots. There is an abstract
class between `LivingEntity` and `Player` — `Avatar` — with no instance
fields at all, and the main-hand *item* is not stored anywhere: it is the
selected hotbar slot, aliased.
2. [The two-phase tick](the-two-phase-tick.md) — one player, one tick,
twice. The connection records where you are, runs the whole physics
pipeline, and then puts you back: the server keeps the velocity and
throws the position away.
3. [Input to movement](input-to-movement.md) — W is pressed. A movement key
held for less than a tick never happened, sending move packets faster makes the
anti-cheat *stricter*, and the packet that reports your key presses
cannot move you but can move a minecart.
4. [The sword swing](the-sword-swing.md) — left-click on a pig. The attack
packet carries one integer and the server rebuilds the rest, applying the
cooldown twice in two different shapes and multiplying the mace's fall
bonus by the critical hit.
5. [The spear](the-spear.md) — the 26.2 combat change, and the part's most
surprising lecture. Two components on one item: a stab whose packet has
no target in it, and a charge whose damage comes from closing speed and
which ignores the attack cooldown entirely.
6. [Hunger and experience](hunger-and-experience.md) — two bars the server
owns outright. Walking costs exactly zero exhaustion, and not one of the
named thresholds in the file the system is built on is read by anything —
`FoodData` writes every number as a literal instead.
7. [Status effects](status-effects.md) — the part's closer, and the cleanest
statement of the server/client split in the book: the client never runs a
single one of an effect's hooks, only counts it down — and an infinite
effect is never re-sent, because −1 never satisfies the re-send test.
Watched as lectures, one and two are the pair to keep together, and four and
five are the other pair. Six and seven can be watched in either order, or
skipped and returned to.
## Reference this part uses
[Attributes](../../reference/attributes.md), because reach, attack damage,
attack speed, sweeping ratio and knockback are all attributes — and attack
damage and knockback, the two that decide what a hit is worth, are not
synced to the client at all. [Packets](../../reference/packets.md) for the
movement, attack and health packets by name. [Data
components](../../reference/components.md) for the components that make an
item a weapon — eight of them on a spear. Then [game rules](../../reference/gamerules.md), [level
data and rules](../../reference/level-data-and-rules.md) and [diagram
lanes](../../reference/lanes.md).
The part stops where the player stops being a player: how a hit is resolved
once it lands is [damage and death](../entities/damage-and-death.md) in Part
VI, what your client is *told* about everyone else is [what the client is
told](../networking/what-the-client-is-told.md) in Part IX, and the ledger
behind the block you already saw break is [prediction and
acknowledgement](../client/prediction-and-acks.md) in Part X.
---
*Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes `tools/verify_names.py`.*
---
# Player anatomy
> Verified against **Minecraft 26.2** · Part VIII · You open your own inventory and look at what you are made of: five classes deep, forty-three slots wide, and one of those slots is an alias.
You are a `LivingEntity` that a human is steering through a socket. Almost
everything on this page follows from that sentence: the class ladder exists
to separate *what any living thing does* from *what a thing with an
inventory and a game mode does* from *what a thing with a connection does*.
But two of the rungs are not where a reader expects them. **There is an
abstract class between `LivingEntity` and `Player` that holds no instance
state at all**, and **the main-hand item is not stored anywhere** — the
hotbar slot you
are looking at and the item `LivingEntity.getMainHandItem` returns are the
same bytes, aliased through the equipment container a horse also has.
## The cast
| class | what it decides | thread |
|---|---|---|
| `Avatar` | the player-shaped hitbox and the two cosmetic synched values — and nothing else | both main threads |
| `Player` | everything a reader means by *the player*: inventory, abilities, experience, sleep, reach | both |
| `ServerPlayer` | the connection, the advancements, the statistics, and every *last sent* field | server main |
| `LocalPlayer` | the one a human steers: input, prediction, what to send | client main |
| `RemotePlayer` | every other player on your screen — interpolated, never derived | client main |
| `Inventory` | thirty-six stacks, and a window onto `EntityEquipment` for the rest | both |
| `ServerPlayerGameMode` / `MultiPlayerGameMode` | what the current `GameType` allows, one object per side | server / client main |
| `Mannequin` | the other `Avatar`: a posable dummy that gets the whole skin pipeline | both |
## The ladder, and the class 26.2 put in the middle
```mermaid
flowchart TD
E["Entity"] --> LE["LivingEntity"]
LE --> AV["Avatar — no instance fields"]
AV --> P["Player — abstract"]
AV --> M["Mannequin"]
P --> SP["ServerPlayer"]
P --> ACP["AbstractClientPlayer"]
ACP --> LP["LocalPlayer"]
ACP --> RP["RemotePlayer"]
M --> CM["ClientMannequin"]
```
`Entity` and `LivingEntity` belong to [Part VI](../entities/entity-anatomy.md).
The rung above them, **`Avatar`** (`world/entity`), is fifty-seven lines
and **no instance fields at all**. It owns the player-shaped dimensions
(`Avatar.POSES`, `Avatar.STANDING_DIMENSIONS`, `Avatar.CROUCH_BB_HEIGHT`,
`Avatar.SWIMMING_BB_WIDTH`, `Avatar.SWIMMING_BB_HEIGHT`), the 1.62 eye
height (`Avatar.DEFAULT_EYE_HEIGHT`), the two cosmetic synched values
(`Avatar.DATA_PLAYER_MAIN_HAND`, `Avatar.DATA_PLAYER_MODE_CUSTOMISATION`)
read back through `Avatar.getMainArm` and `Avatar.isModelPartShown`, and
one abstract method, `Avatar.getProfile`, returning a `ResolvableProfile`.
That is the whole class.
It exists for the renderer. `AvatarRenderer` is generic over *an `Avatar`
that is also a `ClientAvatarEntity`*, and exactly two classes satisfy that:
`AbstractClientPlayer` and **`ClientMannequin`**. The swap is worth knowing,
because it is the same server/client split `Player` has, one class lower:
`Mannequin` (`world/entity/decoration`) holds a mutable static factory,
`Mannequin.constructor`, and `ClientMannequin.registerOverrides` replaces it
during client startup, so a mannequin spawned into a `ClientLevel` is really
a `ClientMannequin` with the full `PlayerModel` and skin pipeline behind it.
The mannequin is therefore a **sibling of `Player`**, not of `ArmorStand`;
only the package is shared with the armour stand. It is posable
(`Mannequin.VALID_POSES`), profiled (`Mannequin.DATA_PROFILE`), describable
(`Mannequin.DATA_DESCRIPTION`) and optionally immovable
(`Mannequin.DATA_IMMOVABLE`).
**`Player`** itself is abstract for one method above all: `Player.gameMode`,
returning a nullable `GameType`. A long tail of its other methods are empty
hooks that exist so the two sides can disagree —
`Player.onUpdateAbilities`, `Player.awardStat`,
`Player.triggerRecipeCrafted`, `Player.crit`, `Player.magicCrit`,
`Player.sendSystemMessage`, `Player.doCloseContainer`,
`Player.openTextEdit`, `Player.sendMerchantOffers`,
`Player.handleCreativeModeItemDrop`. On `Player` they do nothing; the
subclass with somewhere to send a packet overrides them.
## What `Player` owns
| what | the fields | who explains it |
|---|---|---|
| storage | `Player.inventory`, `Player.enderChestInventory` (a `PlayerEnderChestContainer`) | below |
| the open window | `Player.inventoryMenu` (final) and `Player.containerMenu`, which *is* `Player.inventoryMenu` when nothing is open | [containers and menus](../items/containers-and-menus.md) |
| what the mode allows | `Player.abilities` | below |
| the food bar | `Player.foodData` | [hunger and experience](hunger-and-experience.md) |
| experience | `Player.experienceLevel`, `Player.experienceProgress`, `Player.totalExperience`, `Player.enchantmentSeed`, `Player.lastLevelUpTime`, `Player.takeXpDelay` | [hunger and experience](hunger-and-experience.md) |
| sleep | `Player.sleepCounter`, `Player.startSleepInBed` / `Player.stopSleepInBed`, the `Player.BedSleepingProblem` refusals, `Player.SLEEP_DURATION` (100) and `Player.WAKE_UP_DURATION` (10) | `ServerLevel` owns the *everyone is asleep* half |
| the two combat clocks | `LivingEntity.attackStrengthTicker` and `LivingEntity.itemSwapTicker`, declared one rung up but read, reset and incremented only here | [the sword swing](the-sword-swing.md) |
| cooldowns | `Player.cooldowns`, built by `Player.createItemCooldowns`, which only `ServerPlayer` overrides | [using an item](../items/using-an-item.md) |
| four synched values | `Player.DATA_PLAYER_ABSORPTION_ID`, `Player.DATA_SCORE_ID`, `Player.DATA_SHOULDER_PARROT_LEFT`, `Player.DATA_SHOULDER_PARROT_RIGHT` | [synched entity data](../entities/synched-entity-data.md) |
| addressing | `Player.ENDER_SLOT_OFFSET` (200), `Player.HELD_ITEM_SLOT` (499), `Player.CRAFTING_SLOT_OFFSET` (500), decoded by `Player.getSlot` | commands and containers |
| the odds and ends | `Player.gameProfile`, `Player.lastDeathLocation`, `Player.fishing`, `Player.reducedDebugInfo`, `Player.lastItemInMainHand`, `Player.hurtDir`, `Player.jumpTriggerTime`, `Player.wasUnderwater` | — |
None of those four synched values is the hand, which went up to `Avatar`;
and a *player's* skin is not synched data at all: it arrives out of band,
from the tab-list entry. A mannequin's does travel as synched data, in
`Mannequin.DATA_PROFILE`.
**Reach is two attributes, not one.** `Player.blockInteractionRange` and
`Player.entityInteractionRange` read `Attributes.BLOCK_INTERACTION_RANGE`
and `Attributes.ENTITY_INTERACTION_RANGE`
([attributes](../entities/attributes.md)), whose defaults — 4.5 and 3.0 —
live on the attributes themselves. `Player.DEFAULT_BLOCK_INTERACTION_RANGE`
and `Player.DEFAULT_ENTITY_INTERACTION_RANGE` name the same two numbers and
are read by nothing. The checks the server makes are
`Player.isWithinBlockInteractionRange` and
`Player.isWithinEntityInteractionRange`. Note which class supplies which:
`Player.createAttributes` adds the *block* range, `Attributes.BLOCK_BREAK_SPEED`,
`Attributes.SUBMERGED_MINING_SPEED`, `Attributes.SNEAKING_SPEED`,
`Attributes.MINING_EFFICIENCY`, `Attributes.SWEEPING_DAMAGE_RATIO` and the
waypoint attributes, while the *entity* range comes from
`LivingEntity.createLivingAttributes`.
Two smaller seams: `Player.permissions` returns
`PermissionSet.NO_PERMISSIONS` and both sides override it — it is what
`Player.canUseGameMasterBlocks` consults alongside `Abilities.instabuild`
for `Permissions.COMMANDS_GAMEMASTER` — and `Player` implements
`ContainerUser`, which is how a chest decides you are still close enough to
keep it open (`Player.getContainerInteractionRange`).
## Forty-three slots, and one of them is an alias
`Inventory` implements `Container` in a particular shape: **one
`Inventory.items` list of thirty-six stacks (`Inventory.INVENTORY_SIZE`)
plus a reference to the player's `EntityEquipment`.** Slots at or above
thirty-six are not stored here at all. `Inventory.EQUIPMENT_SLOT_MAPPING`
routes them into the equipment object — the four armour indices,
`Inventory.SLOT_OFFHAND` (40), `Inventory.SLOT_BODY_ARMOR` (41) and
`Inventory.SLOT_SADDLE` (42) — so `Inventory.getContainerSize` is
**forty-three**, and a player carries the same body-armour and saddle slots
a horse does.
`Player.createEquipment` returns a **`PlayerEquipment`**, which overrides
the map so that `EquipmentSlot.MAINHAND` resolves to
`Inventory.getSelectedItem`. That is the alias: the held item is not stored
twice, and the main hand *is* the selected hotbar slot seen through the
equipment interface.
The rest of the class is the vocabulary the whole game uses to put things
in a player: `Inventory.add`, `Inventory.getFreeSlot`,
`Inventory.getSlotWithRemainingSpace`,
`Inventory.placeItemBackInInventory`, `Inventory.findSlotMatchingItem`,
`Inventory.contains`, `Inventory.removeItem`,
`Inventory.clearOrCountMatchingItems`, `Inventory.dropAll`,
`Inventory.getSuitableHotbarSlot`, `Inventory.addAndPickItem` and
`Inventory.pickSlot` (pick-block), and `Inventory.fillStackedContents` (the
recipe book). `Inventory.save` and `Inventory.load` cover the thirty-six —
the equipment half is persisted by `LivingEntity` — and
`Inventory.setSelectedSlot` throws rather than accept a non-hotbar index.
## `Abilities`, `GameType`, and the one method that connects them
`Abilities` is five public booleans and two floats:
`Abilities.invulnerable`, `Abilities.flying`, `Abilities.mayfly`,
`Abilities.instabuild`, `Abilities.mayBuild`, plus
`Abilities.getFlyingSpeed` and `Abilities.getWalkingSpeed`. It does not
serialise itself by hand — it packs into the record `Abilities.Packed` and
`Abilities.Packed.CODEC` does the work, through `Abilities.pack` and
`Abilities.apply`.
`GameType` is the four-constant enum (`GameType.SURVIVAL`,
`GameType.CREATIVE`, `GameType.ADVENTURE`, `GameType.SPECTATOR`) with
`GameType.DEFAULT_MODE`, a `GameType.CODEC` and a `GameType.STREAM_CODEC`.
The method that matters is **`GameType.updatePlayerAbilities`**: the single
place in the game that decides which abilities a mode grants. Both sides
call it — the server from `ServerPlayerGameMode`, the client from
`MultiPlayerGameMode` on login, on respawn and on a mode-change event.
`GameType.isBlockPlacingRestricted` is what sets `Abilities.mayBuild`, and
`GameType.isSurvival` is true for `GameType.ADVENTURE` too.
## The two game-mode objects
| | `ServerPlayerGameMode` (`server/level`) | `MultiPlayerGameMode` (`client/multiplayer`) |
|---|---|---|
| owns the mode | `ServerPlayerGameMode.getGameModeForPlayer` | `MultiPlayerGameMode.getPlayerMode` |
| changes it | `ServerPlayerGameMode.changeGameModeForPlayer` | `MultiPlayerGameMode.setLocalMode` |
| breaking state | `ServerPlayerGameMode.isDestroyingBlock`, `ServerPlayerGameMode.destroyProgressStart`, `ServerPlayerGameMode.hasDelayedDestroy` | `MultiPlayerGameMode.isDestroying`, `MultiPlayerGameMode.destroyProgress`, `MultiPlayerGameMode.destroyDelay` |
| the block hooks | `ServerPlayerGameMode.handleBlockBreakAction`, `ServerPlayerGameMode.useItemOn`, `ServerPlayerGameMode.useItem` | `MultiPlayerGameMode.startDestroyBlock`, `MultiPlayerGameMode.continueDestroyBlock`, `MultiPlayerGameMode.useItemOn`, `MultiPlayerGameMode.useItem` |
| attacking | — (`ServerGamePacketListenerImpl` handles it) | `MultiPlayerGameMode.attack`, `MultiPlayerGameMode.interact` |
| containers | — | `MultiPlayerGameMode.handleContainerInput` |
Neither object is held by `Player` itself, and only one of them is held by
a player at all: `Minecraft.gameMode` holds the client one — `LocalPlayer`
has no such field — while `ServerPlayer.gameMode` is a field on the server
player. [Block
interaction](../blocks/block-interaction.md) and [block
breaking](../blocks/block-breaking.md) own the block halves of both columns,
and [prediction and acknowledgement](../client/prediction-and-acks.md) owns
the ledger they share — which, note, `MultiPlayerGameMode` does not hold
either: `MultiPlayerGameMode.startPrediction` reaches for
`ClientLevel.getBlockStatePredictionHandler` per call.
## The three sides of one player
**`ServerPlayer`** is everything that needs a server:
`ServerPlayer.connection` (the `ServerGamePacketListenerImpl`),
`ServerPlayer.gameMode`, `ServerPlayer.advancements`, `ServerPlayer.stats`,
`ServerPlayer.recipeBook` (a `ServerRecipeBook`),
`ServerPlayer.chunkTrackingView` and `ServerPlayer.lastSectionPos` — what
the client has been sent ([tickets and
loading](../world/tickets-and-loading.md)) —
`ServerPlayer.respawnConfig`, `ServerPlayer.camera`,
`ServerPlayer.textFilter`, `ServerPlayer.wardenSpawnTracker`,
`ServerPlayer.enderPearls`, `ServerPlayer.containerSynchronizer`, and a row
of mirror fields that exist so the server can notice a change: the
`ServerPlayer.lastSentHealth`, `ServerPlayer.lastSentFood` and
`ServerPlayer.lastSentExp` trio, which turn a difference into one packet,
and a second row led by `ServerPlayer.lastRecordedArmor`, which turns one
into a scoreboard criterion update instead. It also remembers what the
client *said*: `ServerPlayer.lastClientInput` (an `Input`) and
`ServerPlayer.lastKnownClientMovement`, both explained by [input to
movement](input-to-movement.md). [Players and
sessions](../server/players-and-sessions.md) owns this object's lifecycle;
it is constructed during the *configuration* phase by `PrepareSpawnTask`,
before the play listener exists.
**`AbstractClientPlayer`** adds the tab-list entry
(`AbstractClientPlayer.playerInfo`, fetched lazily from the connection), the
per-frame animation state `AvatarRenderer` reads
(`AbstractClientPlayer.clientAvatarState`), `AbstractClientPlayer.getSkin`
and the field-of-view modifier. **`LocalPlayer`** is the one the human
steers: `LocalPlayer.connection` (a `ClientPacketListener`),
`LocalPlayer.input` (a `ClientInput` at construction, swapped for a
`KeyboardInput` by the connection on login and respawn),
`LocalPlayer.lastSentInput`, the last-sent position block,
`LocalPlayer.recipeBook` (a `ClientRecipeBook`),
`LocalPlayer.dropSpamThrottler`, `LocalPlayer.permissions`,
`LocalPlayer.autoJumpEnabled`, `LocalPlayer.startedUsingItem` and the
view-bob fields `LocalPlayer.yBob` / `LocalPlayer.xBob`. **`RemotePlayer`**
is every *other* player on the client: it sets `Entity.noPhysics`,
interpolates through `RemotePlayer.lerpDeltaMovement`, and has an **empty
`RemotePlayer.updatePlayerPose`** — another player's pose is told to you,
not derived.
Which of the three is allowed to decide anything is [Part VI's
authority](../entities/authority.md), stated once there: a `Player` is
client-authoritative on *both* sides, and the server runs the physics
anyway. [The two-phase tick](the-two-phase-tick.md) is what that costs.
## What a player is on disk
`Player.addAdditionalSaveData` and `Player.readAdditionalSaveData` are where
a player becomes a file: the inventory as a sparse slot/stack list, the
selected slot, the sleep timer, the four experience fields including the
enchanting seed, the score, the abilities through `Abilities.Packed`, the
ender chest, and the last death location. `ServerPlayer` adds the game-type
history through `ServerPlayer.storeGameTypes`, the thrown ender pearls
through `ServerPlayer.saveEnderPearls`, the vehicle through
`ServerPlayer.saveParentVehicle`, and `ServerPlayer.SavedPosition` — which
is read *before* the entity exists, by the configuration-phase spawn task.
## Questions players ask
**Why does the creative flag not come from my abilities?**
`Player.isCreative` and `Player.isSpectator` do not read `Abilities` at all;
they compare `Player.gameMode` against `GameType` constants. The flags
themselves are read almost entirely through two accessors on `Player`:
`Player.hasInfiniteMaterials`, which reads `Abilities.instabuild`, has more
call sites than every other ability accessor combined — it is the most
widely read of them, not the narrowest — with `Player.preventsBlockDrops`
next and `Player.mayBuild`, `Player.isSwimming` and
`Player.isPushedByFluid` well behind.
**Whose game mode arrives late — and why is it yours?**
On the client every player's mode comes from the tab list:
`AbstractClientPlayer.gameMode` resolves through
`AbstractClientPlayer.getPlayerInfo`, and returns null when there is no
entry. For *another* player that window cannot be observed —
`ClientPacketListener` refuses to spawn a `RemotePlayer` whose `PlayerInfo`
has not arrived, and a `PlayerInfo` starts at `GameType.DEFAULT_MODE`, so
their mode is survival until an update packet says otherwise, never null.
The one player who really does have a null window is **you**: `LocalPlayer`
is built during login, before your own tab-list entry is sent. What covers
it is a second, independent source — `MultiPlayerGameMode.localPlayerMode`,
set from the spawn info on login and respawn and from the game-event packet
— and that is the one that drives `Abilities`, block breaking and the
creative screen.
**Why does building permission survive a packet that says otherwise?**
`Abilities.mayBuild` never goes on the wire, and nothing recomputes it on
receipt. `ClientboundPlayerAbilitiesPacket` carries four flag bits and two
floats, none of them the build permission; the client's copy is written only
by `MultiPlayerGameMode` on a mode change, so an abilities packet with no
mode change leaves it as it was. The other direction is smaller still:
`ServerboundPlayerAbilitiesPacket` carries only the flying bit.
**Why does the save file say *flySpeed* when the accessor is
`Abilities.getFlyingSpeed`?** Because `Abilities.Packed.CODEC` names the
keys, and it does not match the accessors. While reading that class, note
the misspelled constant: `Abilities.DEFAULY_FLYING`.
**Why does item ticking need two callers?** Because of the forty-three
slots. `Inventory.tick` runs over the thirty-six ordinary ones from
`Player.aiStep`, and `EntityEquipment.tick` covers the other seven from
`LivingEntity.aiStep`; and phase two walks all forty-three separately,
offering each stack to `ServerPlayer.synchronizeSpecialItemUpdates` for the
map-update packet a filled map needs.
**What crosses the wire about the player itself?**
`ClientboundLoginPacket` and `ClientboundRespawnPacket`, both carrying a
`CommonPlayerSpawnInfo` built by `ServerPlayer.createCommonSpawnInfo` —
which is also where the client's *local* game mode comes from;
`ClientboundPlayerAbilitiesPacket`; `ClientboundGameEventPacket.CHANGE_GAME_MODE`
and `ClientboundPlayerInfoUpdatePacket.Action.UPDATE_GAME_MODE` for a mode
change; `ClientboundSetHeldSlotPacket` and the serverbound
`ServerboundSetCarriedItemPacket` for the hotbar; and
`ClientboundSetPlayerInventoryPacket`, built by
`Inventory.createInventoryUpdatePacket`.
**Can a data pack redefine any of this?** Almost none of it.
`Player.createAttributes` supplies the attribute defaults, and game rules
and server properties set the starting `GameType`. The player is one of the
few systems in the game a data pack cannot redefine.
**Which names will I hunt for under other spellings?** The renderer is
`AvatarRenderer`, and pick-block is `Inventory.addAndPickItem`.
## Where to look
`Player` · `Avatar` · `ServerPlayer` · `AbstractClientPlayer` ·
`LocalPlayer` · `RemotePlayer` · `Inventory` · `PlayerEquipment` ·
`Abilities` · `GameType` · `ServerPlayerGameMode` · `MultiPlayerGameMode` ·
`PrepareSpawnTask` · `Mannequin` · `ClientMannequin` · `AvatarRenderer` ·
`ClientAvatarEntity`
---
*Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes `tools/verify_names.py`.*
---
# The two-phase tick
> Verified against **Minecraft 26.2** · Part VIII · One server tick of one player — which happens twice, from two different callers, and the second half throws its own answer away.
Every other entity on the server is ticked once, by the level it stands in.
A player is ticked twice: once from the level's entity loop, and once from
its own connection, after every level in the game has finished. The two
halves share almost no work — the first never calls up into `Player.tick`
at all, and the one block they have in common is the container-validity
check —
and the second half is stranger still. **The connection records where the
player is, runs the entire physics pipeline, and then puts the player back
where it found them.** The server simulates your movement in full, every
tick, and deletes the result: what it keeps is the *velocity*, because that
is the number the anti-cheat compares your reported motion against.
## The cast
| class | what it decides | thread |
|---|---|---|
| `ServerLevel` | phase one: ticks the player in entity order, inside the level tick | server main |
| `ServerPlayer` | both halves — `ServerPlayer.tick` and `ServerPlayer.doTick` overlap in one block only | server main |
| `ServerGamePacketListenerImpl` | phase two: the record–simulate–snap-back bracket | server main |
| `Player` | `Player.tick` and `Player.aiStep`, reached only from phase two | both |
| `Inventory` | the thirty-six ordinary slots' per-tick item hook | both |
| `FoodData` | hunger, regeneration and starvation, last of the three | server main |
| `AbstractContainerMenu` | the open window, diffed against what the client was told | server main |
| `LocalPlayer` | the client's own single tick, gated on the level having loaded | client main |
## Phase one: what the world does to this player
`ServerPlayer.tick` is called by the level's entity loop — through
`ServerLevel.tickNonPassenger` when the player is walking, and through
`ServerLevel.tickPassenger` and `Entity.rideTick` when mounted — and players
are ticked there whether or not their chunk is entity-ticking ([the level
tick](../server/server-level-tick.md)). It runs late in `ServerLevel.tick`:
after the block ticks and the chunk source, before the block entities.
It does **not** call `Player.tick`. What it does instead is the outside
world's business with the player: `ServerPlayerGameMode.tick` for
block-breaking progress and the delayed destroy, the invulnerability
countdown, `AbstractContainerMenu.broadcastChanges` on the open menu
followed by closing it if it is no longer valid, dragging the camera entity
along when one is set, the per-tick advancement criteria and a flush of the
dirty ones, the warden spawn tracker, and
`ServerPlayer.updatePlayerAttributes`. It is not quite connection-free: its
very first statement is the connection's client-load timeout.
## Phase two: what this player would do if it simulated itself
`ServerPlayer.doTick` is called by
`ServerGamePacketListenerImpl.tickPlayer`, from the connection tick, *after*
every level has ticked. This is the half that calls up into `Player.tick`
and `LivingEntity.tick`, so **the player's physics are simulated here**. It
then ticks `FoodData.tick`, the play-time statistics,
`ServerPlayer.synchronizeSpecialItemUpdates` over all forty-three slots, and
every *has this changed since I last sent it* comparison that produces
`ClientboundSetHealthPacket` and `ClientboundSetExperiencePacket`. Most of
it — including `Player.tick` — sits behind a gate that a spectator in
unloaded chunks fails.
`Player.aiStep`, reached from inside that, is where `Inventory.tick` runs
over the thirty-six ordinary slots, immediately before `EntityEquipment.tick`
covers the other seven from `LivingEntity.aiStep`. It is also the item and
orb pickup sweep, gated on being alive and not a spectator, and it takes
**one** experience orb per tick, chosen at random from those touching.
## The trace: one player, one tick, twice
```mermaid
sequenceDiagram
participant SL as ServerLevel
participant SP as ServerPlayer
participant SPGM as ServerPlayerGameMode
participant ACM as AbstractContainerMenu
participant SGPL as ServerGamePacketListenerImpl
participant Player as Player
participant Inv as Inventory
participant FD as FoodData
Note over SL: phase 1 — the entity loop, inside the level tick
SL->>SP: tick — and no call up to Player.tick
SP->>SPGM: tick — block-breaking progress and delayed destroy
SP->>ACM: broadcastChanges — diff the open menu, then stillValid
SP->>SP: updatePlayerAttributes — creative reach modifiers on and off
Note over SGPL: phase 2 — the connection tick, after every level
SGPL->>SGPL: resetPosition — record this position as firstGood and lastGood
SGPL->>SP: doTick — the simulation half
SP->>Player: Player.tick, then LivingEntity.tick — physics, to be discarded
Player->>Inv: tick — ItemStack.inventoryTick for the 36 ordinary slots
SP->>FD: tick — hunger, regeneration, starvation
SP->>SGPL: ClientboundSetHealthPacket — only if a watched field differs
SGPL->>SP: absSnapTo(firstGood) — put the position back, keep the rotation
```
## The bracket, and what survives it
`ServerGamePacketListenerImpl.tickPlayer` is a bracket around one call.
It **records** the player's current position into the `firstGood…` and
`lastGood…` fields, runs `ServerPlayer.doTick`, and then snaps the player
back to the recorded position with `Entity.absSnapTo`, keeping only the
rotation. The rest of the method is the anti-cheat that rides along in the
same bracket: the *floating too long* kick, and the same record-and-check
done again for the vehicle the player is steering. The authoritative position moves in
`ServerGamePacketListenerImpl.handleMovePlayer` or in a teleport, never
here.
What survives the snap-back is `Entity.getDeltaMovement` — exactly what the
anti-cheat subtracts from the client's reported displacement ([input to
movement](input-to-movement.md)) — plus everything non-positional the tick
did: drowning, burning, effects, hunger, the last-sent diffs. Both halves
run every tick whether or not a packet arrived, and packets are drained
before either of them. Everything the client must be *told* about its own
player is written during phase two, and it leaves at once: `Connection.tick`
flushes the channel on the line after it has run the listener that called
`ServerPlayer.doTick` ([the server tick](../server/server-tick.md)).
The pairing that makes this necessary is [Part VI's
authority](../entities/authority.md): `Player.isClientAuthoritative` is an
unconditional yes on **both** sides, which denies a `ServerPlayer`
local-instance authority, while `Entity.canSimulateMovement` and
`Entity.isEffectiveAi` are overridden true on the server anyway. So the
pipeline runs and its answer is not believed.
## The client's single tick
`LocalPlayer.tick` runs from `ClientLevel`'s entity tick on the main thread,
with its entire body gated on the connection reporting that the level has
loaded. `Minecraft.gameMode` is ticked separately, and *earlier* in
`Minecraft.tick` than the entity tick. `ClientInput.tick` is called from
inside `LocalPlayer.aiStep`, so input is **sampled inside the tick**, not
pushed from the key callback — though the method doing the sampling is
`KeyboardInput.tick`; `ClientInput.tick` itself is empty.
Netty threads mostly do not touch player state: fifty-two of the sixty-one
game handlers open by deferring to the owning thread ([the server
tick](../server/server-tick.md) covers the mechanism). The exceptions are
worth knowing, because they are not all trivial. Two really do touch
nothing — the ping reply and an empty custom-payload hook. But all three
chat handlers reach `ServerGamePacketListenerImpl.tryHandleChat`, which
reads `ServerPlayer.getChatVisibility` and calls
`ServerPlayer.resetLastActionTime` **on the Netty thread** before handing
the rest to `MinecraftServer.execute`.
## Questions players ask
**If the server ticks my player from the connection, does a silent client
stop being ticked?** No. `ServerPlayer.doTick` runs every tick regardless of
traffic, and so does `ServerPlayer.tick`; the one thing that stops phase two
is `MinecraftServer.isPaused`, which only an integrated server reports. What stops a silent client moving
is not a missing tick — it is the snap-back, which undoes every position the
simulation produced.
**Why does fall damage come from the packet handler?** Because inside
`Entity.move`, the fall-damage branch is gated on local-instance authority,
which is false for a `ServerPlayer`. The damage is applied instead by
`Entity.doCheckFallDamage`, called on the movement-packet path with the
client's own reported delta.
**Which half does the thing I am looking for?** If it is the world acting on
the player — the menu's change broadcast, the breaking timer, the spectator
camera, the advancement criteria — phase one. If it is the player acting — physics,
hunger, effects, item ticking, the packets that report a changed number —
phase two.
**Is a mounted player different?** Only in who calls phase one:
`ServerLevel.tickPassenger` through `Entity.rideTick` rather than
`ServerLevel.tickNonPassenger`. Phase two is unchanged, and the movement
packets a passenger sends are treated very differently — see [input to
movement](input-to-movement.md).
## Where to look
`ServerPlayer.tick` · `ServerPlayer.doTick` ·
`ServerGamePacketListenerImpl.tickPlayer` · `Entity.absSnapTo` ·
`Player.tick` · `Player.aiStep` · `Inventory.tick` · `FoodData.tick` ·
`LocalPlayer.tick` · `ServerLevel.tickNonPassenger`
---
*Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes `tools/verify_names.py`.*
---
# Input to movement
> Verified against **Minecraft 26.2** · Part VIII · W is pressed: the key becomes a boolean, the boolean becomes a velocity, the velocity becomes a packet, and the server decides whether to believe it.
You press W. Nothing happens for up to a twentieth of a second, because the
key was not pushed anywhere — it was written into a boolean that the next
tick will read. That tick turns seven booleans into a velocity, the velocity
into a position, and the position into a packet; the server then decides
whether to believe the packet. It usually does. The player is the one entity
the server does not control, and everything here exists to reconcile that.
The surprising part is what the server does while deciding. **It runs the
whole physics pipeline for your player every tick and throws the position
away.** It simulates in order to know what your velocity *ought* to be,
because that is the number the anti-cheat subtracts from what you reported.
## The cast
| class | what it decides | thread |
|---|---|---|
| `KeyboardHandler` | that a key went down, when nothing is in the way of it | client main |
| `KeyMapping` | what that key is bound to, and whether it is held | client main |
| `KeyboardInput` | seven booleans and a normalised vector, once per tick | client main |
| `LocalPlayer` | the movement itself, and what is worth sending | client main |
| `ServerGamePacketListenerImpl` | whether to believe it, and where the player really is | server main |
| `ServerPlayer` | what the client last said: the input, and the known movement | server main |
Who is *allowed* to decide any of this is [Part VI's
authority](../entities/authority.md), and this page assumes it: a `Player`
is client-authoritative on both sides, which denies a `ServerPlayer`
local-instance authority, while `Entity.canSimulateMovement` and
`Entity.isEffectiveAi` are overridden true on the server anyway. Two
consequences run through everything below — `Entity.checkFallDamage` inside
`Entity.move` is gated on local-instance authority and therefore never
fires for a `ServerPlayer`, so `Entity.doCheckFallDamage` on the packet path
does the work; and the ground flag is only updated unconditionally for an
authoritative instance, so on the server it needs real vertical motion.
## What each side holds
### On the client
- **`KeyMapping`** — one object per bindable action. `KeyMapping.ALL` is
the by-name registry, `KeyMapping.MAP` the physical-key reverse index
rebuilt by `KeyMapping.resetMapping`. Each holds `KeyMapping.isDown` and
`KeyMapping.clickCount`. **`KeyMapping.consumeClick` is a counter drain,
not an edge test** — most call sites loop on it, a few take one click
per tick — but the movement keys never use it; they are polled with
`KeyMapping.isDown`. `KeyMapping.Category` is a *record*, with constants
like `KeyMapping.Category.MOVEMENT` and a public
`KeyMapping.Category.register` for mods.
- **`ToggleKeyMapping`** — `Options.keyShift` and `Options.keySprint` are
these; hold-versus-toggle lives entirely in
`ToggleKeyMapping.setDown`, driven by `Options.toggleCrouch` and
`Options.toggleSprint`. Nothing downstream knows the difference. The
screen-focus machinery is theirs too: `KeyMapping.setAll`,
`KeyMapping.releaseAll`, `KeyMapping.resetToggleKeys` and
`KeyMapping.restoreToggleStatesOnScreenClosed`, which consults
`ToggleKeyMapping.shouldRestoreStateOnScreenClosed`. That is the answer
to why a sneak *toggle* survives opening the inventory when a held
sneak does not.
- **`Options`** — the movement bindings: `Options.keyUp`,
`Options.keyDown`, `Options.keyLeft`, `Options.keyRight`,
`Options.keyJump`, `Options.keyShift`, `Options.keySprint`. Plus
`Options.autoJump` and `Options.sprintWindow` (the double-tap window in
ticks, default seven, zero to disable).
- **`Input`** (`world/entity/player`) — a **shared** record of seven
booleans (forward, backward, left, right, jump, shift, sprint) with
`Input.EMPTY` and an `Input.STREAM_CODEC` that packs all seven into one
byte; the bit values are named `Input.FLAG_FORWARD` …
`Input.FLAG_SPRINT`.
- **`ClientInput`** — `ClientInput.keyPresses` (an `Input`) plus
`ClientInput.moveVector` (a `Vec2`, where `Vec2.x` is the *left*
impulse and `Vec2.y` the *forward* one). `ClientInput.makeJump` is how
auto-jump fakes a press. `ClientInput.tick` is empty; the subclass that
actually reads the keyboard is **`KeyboardInput`**, whose
`KeyboardInput.tick` builds a fresh `Input` from the seven
`KeyMapping.isDown` values, maps each pair to −1, 0 or +1 with
`KeyboardInput.calculateImpulse`, and normalises the resulting vector.
- **`LocalPlayer`** — the send-tracking block: `LocalPlayer.xLast`,
`LocalPlayer.yLast`, `LocalPlayer.zLast`, `LocalPlayer.yRotLast`,
`LocalPlayer.xRotLast`,
`LocalPlayer.lastOnGround`, `LocalPlayer.lastHorizontalCollision`,
`LocalPlayer.positionReminder` (against
`LocalPlayer.POSITION_REMINDER_INTERVAL`, twenty),
`LocalPlayer.lastSentInput`, `LocalPlayer.wasSprinting`,
`LocalPlayer.sprintTriggerTime`, `LocalPlayer.autoJumpEnabled`,
`LocalPlayer.autoJumpTime`, `LocalPlayer.crouching`.
`LocalPlayer.input` starts as a bare `ClientInput` and is replaced with a
`KeyboardInput` by `ClientPacketListener` on login and on respawn — which
is why a respawned player's input object is a different one.
### On the server
`ServerGamePacketListenerImpl` holds the whole judgement:
`ServerGamePacketListenerImpl.firstGoodX` and its siblings (where
`ServerGamePacketListenerImpl.tickPlayer` found the player),
`ServerGamePacketListenerImpl.lastGoodX` and its siblings
(the last accepted position),
`ServerGamePacketListenerImpl.awaitingPositionFromClient`,
`ServerGamePacketListenerImpl.awaitingTeleport`,
`ServerGamePacketListenerImpl.awaitingTeleportTime`,
`ServerGamePacketListenerImpl.clientIsFloating`,
`ServerGamePacketListenerImpl.aboveGroundTickCount`,
`ServerGamePacketListenerImpl.receivedMovePacketCount`,
`ServerGamePacketListenerImpl.knownMovePacketCount`,
`ServerGamePacketListenerImpl.receivedMovementThisTick`, and the vehicle
equivalents (`ServerGamePacketListenerImpl.lastVehicle`,
`ServerGamePacketListenerImpl.vehicleFirstGoodX`,
`ServerGamePacketListenerImpl.vehicleLastGoodX`,
`ServerGamePacketListenerImpl.clientVehicleIsFloating`).
`ServerPlayer.lastKnownClientMovement` is the observed per-tick
displacement, read back through `ServerPlayer.getKnownMovement` and
`ServerPlayer.getKnownSpeed`, and `ServerPlayer.lastClientInput` is the
raw key state.
**Almost none of the thresholds have names.** The numbers in the movement
checks are inline literals; the only named ones nearby are
`ServerGamePacketListenerImpl.MAXIMUM_FLYING_TICKS` (80) and
`ServerGamePacketListenerImpl.CLIENT_LOADED_TIMEOUT_TIME` (60).
## Sampled once a tick, judged once a tick
Keys are **sampled inside the tick, not pushed from the callback.** The
GLFW callback builds a `KeyEvent` and immediately defers to the client's
main thread with `BlockableEventLoop.execute`; the *movement* half of
`KeyboardHandler.keyPress` sets `KeyMapping.isDown` and bumps
`KeyMapping.clickCount`, and does even that only when no screen is open.
Everything else the method does — the screen's own key handling, the debug
keys, the pause — never reaches a `KeyMapping` at all. Releases are always delivered, which is the
asymmetry the toggle-restoring machinery above exists to repair. The read
happens once per game tick, deep inside `LocalPlayer.aiStep`, which calls
`ClientInput.tick`.
Mouse look is the exception: `MouseHandler.handleAccumulatedMovement` runs
**per frame**, in `Minecraft.runTick` after the tick loop, gated on the
window being active and the mouse grabbed, and `MouseHandler.turnPlayer`
calls `Entity.turn` directly — after cubing the sensitivity, applying
`Options.smoothCamera` through a `SmoothDouble` and honouring the two
invert options. Rotation is therefore finer-grained than position.
On the server the ordering is the whole story:
1. `MinecraftServer.processPacketsAndTick` drains `PacketProcessor`
**before** `MinecraftServer.tickServer`. Every movement packet for the
tick is applied first, ahead of any level ticking.
2. The levels tick.
3. `MinecraftServer.tickChildren` reaches its connection phase, and
`ServerGamePacketListenerImpl.tick` runs
`ServerGamePacketListenerImpl.tickPlayer` — the simulate-and-discard
step, and the place the floating check is enforced. A paused server
short-circuits before it.
## The trace: W is pressed
```mermaid
sequenceDiagram
participant KH as KeyboardHandler
participant KM as KeyMapping
participant KI as KeyboardInput
participant LP as LocalPlayer
participant LE as LivingEntity
participant SGPL as ServerGamePacketListenerImpl
participant SP as ServerPlayer
KH->>KM: set — isDown = true#59; nothing else happens yet
LP->>KI: tick — from inside aiStep: poll seven keys into one Input
KI->>LP: applyInput — moveVector becomes xxa/zza, jump becomes jumping
LP->>LE: travel — travelInAir, then Entity.move: the client is authoritative
LP->>SGPL: ServerboundPlayerInputPacket — only when the key set changed
LP->>SGPL: ServerboundMovePlayerPacket.PosRot — sendPosition decides which variant
SGPL->>SGPL: moved too quickly? — squared delta vs getDeltaMovement, budget 100 or 300
SGPL->>SP: move(MoverType.PLAYER) — where the server applies the position you reported
SGPL->>SGPL: moved wrongly? — residual over 0.0625, or a new collider
SGPL->>LP: ClientboundPlayerPositionPacket — rubber-band, awaiting an ack
SGPL->>SP: doTick — simulate the whole tick, then absSnapTo(firstGood…) and discard
```
**The client half.** `KeyboardInput.tick` builds an `Input` from the seven
keys and a normalised `Vec2`. `LocalPlayer.applyInput` — overriding a
`LivingEntity.applyInput` that does nothing but decay them —
turns that into the `LivingEntity.xxa` and `LivingEntity.zza` movement
fields, after passing it through
`LocalPlayer.modifyInput`: a flat 0.98 scaling, then the item-use
slowdown (from `LocalPlayer.itemUseSpeedMultiplier`),
`Attributes.SNEAKING_SPEED` when moving slowly, and
`LocalPlayer.modifyInputSpeedForSquareMovement`, the diagonal correction.
From there it is ordinary
[movement and collision](../entities/movement-and-collision.md):
`Player.travel` — a real override, handling passengers, the swimming look
nudge and the creative-flight damping — then `LivingEntity.travel` →
`LivingEntity.travelInAir` →
`LivingEntity.handleRelativeFrictionAndCalculateMovement` →
`Entity.moveRelative` → `Entity.move`.
Sprint is decided in `LocalPlayer.aiStep` before that, and it is a
**rising edge, not a release**. `LocalPlayer.aiStep` snapshots the forward
impulse *before* ticking the input, so the value it later tests is the
previous tick's; `LocalPlayer.canStartSprinting` requires the current
tick's. The pair means the double-tap window is armed on the first
*press* and consumed on the second, and a release only matters because
sneaking, using an item or walking backwards clears
`LocalPlayer.sprintTriggerTime` outright.
`LocalPlayer.shouldStopRunSprinting` ends it. Auto-jump is
`LocalPlayer.updateAutoJump` (called from `LocalPlayer.move`) setting
`LocalPlayer.autoJumpTime`, which makes the *next* tick call
`ClientInput.makeJump`.
**What goes on the wire.** `LocalPlayer.sendPosition` picks the variant:
`ServerboundMovePlayerPacket.PosRot` when both changed,
`ServerboundMovePlayerPacket.Pos` or `.Rot` for one,
`ServerboundMovePlayerPacket.StatusOnly` when only the ground or
collision flag changed, and **nothing at all** otherwise — except that a
position is re-sent every twenty ticks regardless, via
`LocalPlayer.positionReminder`. "Changed" is not the same test for the
two halves: rotation compares exactly, while position must have moved by
more than 2×10⁻⁴ blocks. And the whole method sits behind
`LocalPlayer.isControlledCamera`, so while spectating another entity a
client sends no move packets at all, not even the reminder. The two
booleans ride in one byte
(`ServerboundMovePlayerPacket.FLAG_ON_GROUND`,
`ServerboundMovePlayerPacket.FLAG_HORIZONTAL_COLLISION`).
`ServerboundPlayerInputPacket` is sent only when the key set *changes*,
and `ServerboundClientTickEndPacket` — a zero-byte singleton — closes
every client tick that has a level and is not paused.
**The server half.** `ServerGamePacketListenerImpl.handleMovePlayer`
begins with `ServerGamePacketListenerImpl.containsInvalidValues`, which
rejects **NaN** coordinates and non-finite *rotations* — an infinite
coordinate survives it and is clamped instead, to ±3×10⁷ horizontally by
`ServerGamePacketListenerImpl.clampHorizontal` and ±2×10⁷ vertically by
`ServerGamePacketListenerImpl.clampVertical`. It then discards the
position entirely while a teleport is outstanding; short-circuits a
sleeping player, teleporting them back if they claim to have moved more
than a block; and for a passenger applies rotation only —
snapping the position back with `Entity.absSnapTo` and re-registering the
chunk position, which is not quite "returns early". Then two checks:
- *moved too quickly*: the squared distance from `firstGood…` minus
`Entity.getDeltaMovement().lengthSqr()` against a budget of **100 per
packet, or 300 while fall-flying**, scaled by how many move packets
arrived since the last tick. Both sides are squared, so 100 is a
hundred blocks *squared* — about ten blocks a tick. The whole check,
and the packet counter it uses, is gated on
`TickRateManager.runsNormally`, so a frozen or stepping world does no
speed checking at all. It is also skipped for the singleplayer host,
during a dimension change, and when
`GameRules.PLAYER_MOVEMENT_CHECK` is off
(`GameRules.ELYTRA_MOVEMENT_CHECK` covers the elytra case). Failure
teleports the player back and returns.
- The move is then actually applied — `Entity.move` with
`MoverType.PLAYER` — and *moved wrongly* measures what is left over: a
residual above `0.0625` while not changing dimension, sleeping,
creative, spectating or inside `LivingEntity.isInPostImpulseGraceTime`
(the mace and wind-charge exemption, closed by
`ServerGamePacketListenerImpl.tryResetCurrentImpulseContext`). The
rubber-band that follows is a **disjunction**: either that failure with
a demonstrably clear old box, *or*
`ServerGamePacketListenerImpl.isEntityCollidingWithAnythingNew`
reporting the player ended up inside a collider it was not already
inside — which fires whether or not the residual check failed. Both
arms are additionally suppressed for a no-physics or sleeping player.
Accepting means `Entity.absSnapTo`, `ServerChunkCache.move`,
`Entity.setOnGroundWithMovement`, `Entity.doCheckFallDamage`,
`ServerGamePacketListenerImpl.handlePlayerKnownMovement` and
`ServerPlayer.checkMovementStatistics` — the walked-distance statistics
are computed from the *client's reported* delta, never from a simulation.
The server also **infers the jump**: a packet that reports leaving the
ground while moving upward calls `LivingEntity.jumpFromGround` on the player's
behalf.
**Where velocity comes from.** The reported delta is stored by
`ServerPlayer.setKnownMovement`, and
`ServerGamePacketListenerImpl.handleClientTickEnd` zeroes it if no move
packet arrived that tick. That is what everything downstream reads when it
wants the player's velocity — whether a swing sweeps, what a spear's charge
does, the speed a fired projectile inherits, leash physics — and it is why a
client that stops sending is treated as stationary rather than as still
coasting.
**The teleport handshake.** `ServerGamePacketListenerImpl.teleport` bumps
`ServerGamePacketListenerImpl.awaitingTeleport`, moves the player with `Entity.teleportSetPosition`,
records `ServerGamePacketListenerImpl.awaitingPositionFromClient` and sends
`ClientboundPlayerPositionPacket` (a `PositionMoveRotation` plus a set of
`Relative` flags saying which fields are deltas).
`ServerGamePacketListenerImpl.updateAwaitingTeleport` **re-sends after
more than twenty ticks** if no acknowledgement arrives, and until it does,
every incoming move packet contributes rotation only. The client replies
with `ServerboundAcceptTeleportationPacket` *and* an immediate
`ServerboundMovePlayerPacket.PosRot`, then calls
`BlockStatePredictionHandler.onTeleport` to drop its outstanding block
predictions ([block interaction](../blocks/block-interaction.md)). On the
receiving end `ClientPacketListener.handleMovePlayer` applies the position
only when the player is not a passenger, and never interpolates: it passes
the interpolate flag as a literal false, so your own player is always
snapped, and the 4096-blocks-squared jump test that gates interpolation is
reached only on the entity-teleport path.
`ClientboundPlayerRotationPacket` is the rotation-only sibling.
**Elytra** is its own round trip: `LocalPlayer.aiStep` asks
`Player.tryToStartFallFlying` and, if it says yes, sends
`ServerboundPlayerCommandPacket.Action.START_FALL_FLYING` — the server runs
the same method on receipt. The server may
disagree and call `LivingEntity.stopFallFlying`, and the flight itself is
`LivingEntity.updateFallFlying` and `LivingEntity.travelFallFlying`.
## What it calls, and what crosses the wire
- **Called by:** `Minecraft.tick` (client, via `ClientLevel` and
`Minecraft.handleKeybinds`); `PacketProcessor` and
`MinecraftServer.tickChildren` (server).
- **Calls into:** `LivingEntity.travel` and `Entity.move`
([movement and collision](../entities/movement-and-collision.md));
`ServerChunkCache.move`, which is what makes chunks load as you walk
([tickets and loading](../world/tickets-and-loading.md)).
- **Crosses the network as:** `ServerboundMovePlayerPacket` and its four
variants, `ServerboundPlayerInputPacket`,
`ServerboundPlayerCommandPacket` (whose
`ServerboundPlayerCommandPacket.Action` is seven values — start and stop
sprinting, start and stop riding-jump,
`ServerboundPlayerCommandPacket.Action.STOP_SLEEPING`,
`ServerboundPlayerCommandPacket.Action.OPEN_INVENTORY`,
`ServerboundPlayerCommandPacket.Action.START_FALL_FLYING`; **there is
no sneak action** — sneaking reaches the server through
`ServerboundPlayerInputPacket`, which calls `Entity.setShiftKeyDown`),
`ServerboundMoveVehiclePacket`,
`ServerboundAcceptTeleportationPacket`,
`ServerboundClientTickEndPacket`; and back,
`ClientboundPlayerPositionPacket`, `ClientboundPlayerRotationPacket`,
`ClientboundMoveVehiclePacket`.
- **Data-driven by:** almost nothing —
`GameRules.PLAYER_MOVEMENT_CHECK` and `GameRules.ELYTRA_MOVEMENT_CHECK`
([level data and rules](../../reference/level-data-and-rules.md)), plus the
movement attributes.
## Questions players ask
**Why does the server bother simulating me at all?**
`ServerGamePacketListenerImpl.tickPlayer` records the player's position into
the `firstGood…` and `lastGood…` fields, calls `ServerPlayer.doTick` — the
whole `LivingEntity.aiStep` / `LivingEntity.travel` / `Entity.move` pipeline
runs server-side — and then puts the player back with `Entity.absSnapTo`,
keeping the rotation. The simulation exists for `Entity.getDeltaMovement`,
the *expected* distance the check subtracts. On foot the authoritative
position moves only in `ServerGamePacketListenerImpl.handleMovePlayer` or
through a teleport; the exception is riding, where the vehicle repositions
you through `Entity.rideTick` every tick. [The two-phase tick](the-two-phase-tick.md) is that bracket seen
from the other side.
**If `ServerboundPlayerInputPacket` never moves me, what is it for?**
Two things. `ServerPlayer.setLastClientInput` feeds
`ServerPlayer.getLastClientMoveIntent`, and both `NewMinecartBehavior` and
`OldMinecartBehavior` read it to nudge a stalled cart along the rider's
intended direction — so the packet that cannot move a player *can* move a
minecart. And the handler sets the sneak flag directly, which is why there
is no sneak action on `ServerboundPlayerCommandPacket`. Boats are steered
client-side (`LocalPlayer.rideTick` → `AbstractBoat.setInput`) and the
*result* ships as `ServerboundMoveVehiclePacket`.
**Does sending move packets faster help me cheat?** It does the opposite.
The per-packet budget normally scales with how many packets arrived since
the last tick, but above five the code clamps the count to one — so a flood
gets a one-packet budget for a many-packet displacement. There is no
throttle or kick for the flood itself; only chat, commands and item drops
have a `TickThrottler`.
**Why is a passenger barely checked?** A passenger's own move packet
contributes rotation and a chunk re-registration and nothing else, and the
*vehicle's* packet is judged by a cut-down copy of the same code: a flat
budget of 100, no elytra case, no game rule, and no horizontal-collision
flag.
**What counts as floating, and why does creative flight not trip it?**
`ServerGamePacketListenerImpl.getMaximumFlyingTicks` returns an effectively
unbounded budget below a gravity of 10⁻⁵ and otherwise stretches the
eighty-tick budget as gravity falls, so the kick scales with gravity and
only upward. `ServerGamePacketListenerImpl.clientIsFloating` is separately
suppressed by spectator mode, `Abilities.mayfly`, the server's own
allow-flight setting, `MobEffects.LEVITATION`, fall-flying and riptide — and
the condition that actually defines *floating* is having no blocks anywhere
below.
**Why did my quick tap do nothing?** Movement polls `KeyMapping.isDown` once
a tick, so a press shorter than a tick never happened. The keys that use
`KeyMapping.consumeClick` behave the other way: three taps inside one tick
can fire three times.
**Why does brushing a wall sometimes cancel my sprint and sometimes not?**
`LocalPlayer.isHorizontalCollisionMinor` is a client-only override that
measures the angle against `LocalPlayer.MINOR_COLLISION_ANGLE_THRESHOLD_RADIAN`,
about eight degrees; a graze shallower than that is forgiven. The server has
no equivalent.
**What is quietly wrong here?** Three things, all harmless and all worth
knowing. The vertical residual in the *moved wrongly* check is dead code —
the guard that zeroes it is a disjunction true for every finite double, so
the 0.0625 test is horizontal-only in practice, in both the player and the
vehicle handler. `Options.autoJump` is read inside a networking method:
`LocalPlayer.autoJumpEnabled` is refreshed in `LocalPlayer.sendPosition`,
which does not run for a passenger or a non-camera player, so the setting
quietly stops tracking in those states. And `ServerboundPlayerCommandPacket`
carries an entity id the server never validates against the sender.
## Where to look
`KeyboardHandler` · `KeyMapping` · `ToggleKeyMapping` · `Options` ·
`ClientInput` · `KeyboardInput` · `Input` · `LocalPlayer` ·
`MouseHandler` · `ServerboundMovePlayerPacket` ·
`ServerboundPlayerInputPacket` · `ServerGamePacketListenerImpl` ·
`ClientboundPlayerPositionPacket` · `PositionMoveRotation` · `Relative` ·
`PacketProcessor` · `TickThrottler`
---
*Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes `tools/verify_names.py`.*
---
# The sword swing
> Verified against **Minecraft 26.2** · Part VIII · Left-click on a pig: the client picks a target and sends one integer, and the server rebuilds every part of the hit from scratch.
You put the crosshair on a pig and click. The client has already decided
what you are looking at — earlier in this same tick — checks a handful of
reasons not to swing, and sends the smallest packet in melee combat:
**`ServerboundAttackPacket` is a record of one int, the entity id.** No
hand, no sneak flag, no hit position, no damage. Everything else the server
re-derives: the weapon from your main hand, the geometry from the target's
bounding box measured against your eye, and the damage from an attribute, a cooldown curve applied twice in
two different shapes, and a multiplication order in which the mace's fall
bonus lands *before* the critical hit and is therefore multiplied by it.
## The cast
| class | what it decides | thread |
|---|---|---|
| `Minecraft` | what you are looking at, and whether the click swings at all | client main |
| `LocalPlayer` | the raycast, and which range each candidate is judged against | client main |
| `MultiPlayerGameMode` | sends the attack, and predicts almost nothing | client main |
| `ServerGamePacketListenerImpl` | resolves the id, re-checks the range and the item | server main |
| `Player` | `Player.attack`: one method, and the order inside it is load-bearing | both (only the server's answer counts) |
| `LivingEntity` | the swing animation state, and the two attack clocks | both |
| `AttackRange` | a weapon's own reach, with a minimum as well as a maximum | — |
## Picking: what is under the crosshair
`Minecraft.pick` runs once per tick inside `Minecraft.tick`, in this order:
`MultiPlayerGameMode.tick` → **`Minecraft.pick`** → the GUI →
`Minecraft.handleKeybinds`, which drains `Options.keyAttack` into
`Minecraft.startAttack` and finishes with `Minecraft.continueAttack` for
held-down mining. So the hit result a click uses was computed *earlier in
the same tick*. `Minecraft.pick` also runs per frame, for the crosshair and
the block outline, but that value is not what the attack sees.
It asks the camera entity, and for the local player that is
`LocalPlayer.raycastHitResult`. If the **active** item — the one being used,
if any, else the main hand — carries an `AttackRange`
(`DataComponents.ATTACK_RANGE`), that component's own search runs first; and
if it finds nothing, the classic algorithm runs **as well**: a block clip
out to the greater of the two ranges, an entity sweep with
`ProjectileUtil.getEntityHitResult` over the bounding box expanded along the
view direction and inflated by one, each candidate inflated by
`Entity.getPickRadius` (**zero** for everything but projectiles) — and the
entity wins only if it is strictly nearer than the block. Then
`LocalPlayer.filterHitResult` discards each against *its own* range:
`Attributes.ENTITY_INTERACTION_RANGE` (3.0) for the entity,
`Attributes.BLOCK_INTERACTION_RANGE` (4.5) for the block. That is where the
two reaches diverge.
`AttackRange` is worth a second look, because it is a reach *floor* as well
as a ceiling: a minimum and a maximum, separate creative values, a hitbox
margin and a mob factor. `AttackRange.isInRange` is the test,
`AttackRange.defaultFor` falls back to `Attributes.ENTITY_INTERACTION_RANGE`,
and `AttackRange.effectiveMinRange` / `AttackRange.effectiveMaxRange` apply
the mob factor — only for non-players.
## Deciding: most branches do not swing
`Minecraft.startAttack` is the branch point. It returns early — no swing, no
packet — while `Minecraft.missTime` is running, when there is no hit result
at all (setting a ten-tick miss time), while `LocalPlayer.isHandsBusy`, for
a disabled item, and when `Player.cannotAttackWithItem` refuses with a
tolerance of **zero**; spectators take a branch of their own. Two branches
do swing: the piercing short-circuit to `MultiPlayerGameMode.piercingAttack`
when the item carries `DataComponents.PIERCING_WEAPON` — [the
spear](the-spear.md) — and the tail of the hit-result switch: entity to
`MultiPlayerGameMode.attack`, block to
`MultiPlayerGameMode.startDestroyBlock` ([block
breaking](../blocks/block-breaking.md)), a miss on an air block to
`Player.resetAttackStrengthTicker` and the ten-tick miss time. Even the
entity branch is conditional — a weapon with its own `AttackRange` that the
hit falls outside of swings but sends no attack packet at all. The miss time
itself only exists outside creative, and opening any screen parks it at a
very large number.
On the server, `ServerGamePacketListenerImpl.handleAttack` requires the
client to have loaded and the player not to be a spectator, resolves the id
with `ServerLevel.getEntityOrPart`, checks the world border, applies
`Player.isWithinAttackRange` with a **3.0-block server buffer** — applied to
*both* ends, so a weapon's minimum range effectively vanishes server-side —
rejects a piercing weapon (that path arrives elsewhere), checks the item is
enabled, re-checks `Player.cannotAttackWithItem` with a tolerance of **five
ticks**, more lenient than the client's zero, and calls `Player.attack`.
Attacking something absurd — an `ItemEntity`, an `ExperienceOrb`, an
unattackable `AbstractArrow`, yourself — is a **disconnect**, not a
rejection; failing the range check is a silent drop.
The packet is drained from `PacketProcessor` at the **top** of the tick,
before `MinecraftServer.tickServer` and therefore before any level ticks.
That ordering matters: `Player.attack` runs before the victim's
`LivingEntity.baseTick` decrements `Entity.invulnerableTime` for the tick.
(The counter is declared on `Entity` and decremented in
`LivingEntity.baseTick`, which skips a `ServerPlayer` — because
`ServerPlayer.tick` decrements it itself, in phase one.) Each of the
resulting feedback packets is written and flushed on its own: the
connection suspends flushing only across `MinecraftServer.tickChildren`,
and the attack was handled before that bracket opened.
## The trace: one click, one integer, one round trip
```mermaid
sequenceDiagram
participant MC as Minecraft
participant LP as LocalPlayer
participant MPGM as MultiPlayerGameMode
participant SGPL as ServerGamePacketListenerImpl
participant Player as Player
participant LE as LivingEntity
participant SL as ServerLevel
MC->>LP: raycastHitResult — AttackRange first, then the classic pick
MC->>MPGM: attack — after cannotAttackWithItem and the range test
MPGM->>SGPL: ServerboundAttackPacket — one varint: the entity id
LP->>SGPL: ServerboundSwingPacket — from the branches that swing at all
SGPL->>SGPL: isWithinAttackRange — AttackRange plus a 3.0 buffer both ways
SGPL->>Player: attack — the server recomputes damage from nothing but the id
Player->>LE: hurtOrSimulate — into Part VI#59; returns did-it-land
Player->>Player: causeExtraKnockback, doSweepAttack, itemAttackInteraction
SL->>MC: ClientboundDamageEventPacket — a damage type and three ids, no amount
```
## The damage: one number, two curves, one order
`Player.attack` is a single method, and everything interesting about melee
combat is the order in which it touches one float.
```mermaid
flowchart TD
S["s = Player.getAttackStrengthScale, read with a partial tick of 0.5"]
BASE["base = Attributes.ATTACK_DAMAGE — or the riptide value while auto-spinning"]
BOOST["boost = Player.getEnchantedDamage minus base"]
BL["boost × s — linear in the cooldown"]
BQ["base × (0.2 + s² × 0.8) — quadratic in the same cooldown"]
RESET["Player.onAttack — resets the attack ticker, after the scale was read"]
GATE["either term above zero? — otherwise nothing below runs"]
ITEM["plus Item.getAttackDamageBonus — the mace's fall bonus lands here"]
CRIT["× 1.5 if full strength and Player.canCriticalAttack"]
TOTAL["total = that, plus the linear boost"]
HURT["Entity.hurtOrSimulate — its boolean gates the knockback, sweep and durability"]
S --> BL
S --> BQ
BOOST --> BL
BASE --> BQ
BQ --> RESET
RESET --> GATE
GATE --> ITEM
ITEM --> CRIT
CRIT --> TOTAL
BL --> TOTAL
TOTAL --> HURT
```
Read that picture for the two things it makes obvious. **The cooldown is
applied twice, differently** — a quadratic ramp on the base damage, a linear
one on the enchantment bonus, both from the same scale read with the same
0.5 partial tick. And **the item bonus is inside the crit**, because
`Item.getAttackDamageBonus` is added between the sprint check and the
multiplication.
The gates along the way are as particular as the arithmetic.
`Player.cannotAttack` comes first: the target must be attackable and must
not claim the interaction for itself. `Player.deflectProjectile` can end the
attack outright. Sprint knockback needs the scale above 0.9, plays a sound,
and adds a flat **0.5** to the knockback later. `Player.canCriticalAttack`
needs falling, not on the ground, not climbing, not in water, not
mobility-restricted, not a passenger, **not sprinting**, a `LivingEntity`
target — and full strength as well. `Player.isSweepAttack` needs full
strength, *not* a crit, *not* sprint knockback, on the ground, moving slower
than 2.5× the walking speed, and something in `ItemTags.SWORDS`.
If the hit landed, the tail runs in order: `Player.causeExtraKnockback` —
which is also where the attacker's own motion is damped and sprinting
cancelled, using `LivingEntity.getKnockback` computed from
`Attributes.ATTACK_KNOCKBACK` through the enchantments and halved — then
`Player.doSweepAttack`, `Player.attackVisualEffects`,
`LivingEntity.setLastHurtMob`, `Player.itemAttackInteraction`,
`Player.damageStatsAndHearts`, and `Player.causeFoodExhaustion` of 0.1. If
it did not land, a no-damage sound. Either way `Player.postPiercingAttack`
runs at the end.
`Player.itemAttackInteraction` is itself three steps in a particular order:
`ItemStack.hurtEnemy` (the item's own hook and the use statistic, *not*
durability), then `EnchantmentHelper.doPostAttackEffectsWithItemSource`,
then `ItemStack.postHurtEnemy`, which is where `Weapon`'s per-attack
durability cost is applied. `Weapon` (`DataComponents.WEAPON`) is a pair:
that cost, and `Weapon.disableBlockingForSeconds`, the axe's shield-breaking
rule, read back through `LivingEntity.getSecondsToDisableBlocking`.
`Player.doSweepAttack` damages every *living* entity in a box around the **primary
target** inflated by (1, 0.25, 1), for candidates within three blocks of the
**attacker**, excluding the attacker, the primary target, allies and marker
armour stands. Each takes `1.0 + Attributes.SWEEPING_DAMAGE_RATIO × base`,
run through `Player.getEnchantedDamage` and then scaled by the
attack-strength scale, plus a flat 0.4 knockback. Its sweep *sound* is
unguarded; the damage and the `ParticleTypes.SWEEP_ATTACK` particles sit
behind the server check.
`Entity.hurtOrSimulate` is the wrapper that branches on the side —
`Entity.hurtServer` on the server, `Entity.hurtClient` on the client. Armour,
invulnerability frames, `DataComponents.BLOCKS_ATTACKS` and knockback
resistance are all [damage and death](../entities/damage-and-death.md).
## Questions players ask
**Why does mashing do less damage?** Because of the quadratic. At half
charge the base damage is 0.2 + 0.25 × 0.8 = 40% of full, while the
enchantment bonus is at 50%. The vocabulary behind it is small:
`LivingEntity.attackStrengthTicker` and `LivingEntity.itemSwapTicker` are
declared on `LivingEntity` but read, reset and incremented only from
`Player`; `Player.getCurrentItemAttackStrengthDelay` is twenty divided by
`Attributes.ATTACK_SPEED`; `Player.resetAttackStrengthTicker` clears both
clocks and `Player.resetOnlyAttackStrengthTicker` clears one. `Player.tick`
also resets both when the main-hand *item type* changes; what distinguishes
the swap ticker is that `Player.onAttack` clears the attack ticker and
leaves it alone, because it exists only to drive the held-item swap
animation. Both sides then reset twice: once mid-`Player.attack`, and once
more after it — the client in `MultiPlayerGameMode.attack`, the server on
the swing packet that follows, because `ServerPlayer.swing` resets the
ticker too.
**Why does my sword make no sound until the server answers?**
`ClientLevel.playSeededSound` plays a sound only when the excluded player
*is* the local player, and `Player.playServerSideSound` excludes nobody — so
every hit sound the attacker hears arrives as a `ClientboundSoundPacket`,
one round trip late.
**Does the client predict any of this against a mob?** Almost none.
`Entity.hurtClient` returns false and neither `LivingEntity` nor `Mob`
overrides it, so on the client `Entity.hurtOrSimulate` reports that the hit
did not land and the entire block after it is skipped: no predicted
knockback, no sweep, no visual effects, no durability, no exhaustion. The
exceptions are the eight classes that do override it, and every one of them
is something you can hit that is not a mob: another *player*
(`RemotePlayer`), a boat or minecart (`VehicleEntity`), a painting or leash
knot (`BlockAttachedEntity`), an item frame, an end crystal, a shulker
bullet, a dropped item and an experience orb. Against those the whole block
runs locally. `Player.getEnchantedDamage` does nothing on `Player` either;
it returns its argument unchanged and only `ServerPlayer` overrides it. With
`Attributes.ATTACK_DAMAGE` not being client-syncable
([attributes](../entities/attributes.md)), the client's damage figure is
never authoritative wherever that block does run.
**Can a weapon be too close to swing?** On the client, yes — `AttackRange`
has a minimum. On the server, no: the 3.0-block leniency is subtracted from
the minimum as well as added to the maximum, so the floor does not survive
the round trip.
**How does my client know how badly the pig was hurt?** It does not.
`ClientboundDamageEventPacket` carries no amount at all — a damage-type
holder, three entity ids and an optional source position — and the victim's
red flash, hurt sound and invulnerability window are reconstructed from
that. Health bars come from [synched entity
data](../entities/synched-entity-data.md).
**Are sweep and knockback enchantment effects?** They are attributes.
`Attributes.SWEEPING_DAMAGE_RATIO` defaults to zero, so a vanilla sweep does
1.0 — scaled by the attack-strength ratio, so slightly less than 1.0
anywhere in the sweep's legal window below full charge. And
`Attributes.ATTACK_KNOCKBACK` defaults to zero, so for an unenchanted sword
the *entire* attacker-side knockback is the sprint bonus of 0.5.
**Why does my swing look different with a different weapon?** Because swing
duration is a data component — `ItemStack.getSwingAnimation` returns a
`SwingAnimation` — not a constant six ticks;
`MobEffects.MINING_FATIGUE` stretches it and haste shortens it. The
animation state itself is `LivingEntity.swinging`,
`LivingEntity.swingingArm`, `LivingEntity.swingTime` and
`LivingEntity.attackAnim`. The swing is not echoed to the swinger:
`ServerGamePacketListenerImpl.handleAnimate` broadcasts
`ClientboundAnimatePacket` to trackers only — but crit particles *are* sent
back, because they go to the trackers of the **attacker** while naming the
**victim**.
**Is this the only way to hit something in melee?** No. Two other paths end
in damage and neither goes through `Player.attack`: a `PiercingWeapon`
short-circuits before the hit-result switch, and a `KineticWeapon` is
reached from item *use* rather than attack. Both are [the
spear](the-spear.md).
## Where to look
`Minecraft.startAttack` · `LocalPlayer.raycastHitResult` ·
`MultiPlayerGameMode.attack` · `ServerboundAttackPacket` ·
`ServerGamePacketListenerImpl.handleAttack` · `Player.attack` ·
`Player.baseDamageScaleFactor` · `Player.doSweepAttack` ·
`Player.itemAttackInteraction` · `AttackRange` · `Weapon` ·
`ProjectileUtil` · `ClientboundDamageEventPacket` · `SwingAnimation`
---
*Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes `tools/verify_names.py`.*
---
# The spear
> Verified against **Minecraft 26.2** · Part VIII · Two ways to hit something with the same item: jab it, and the client sends no target at all; charge it and run, and the damage comes from how fast the gap is closing.
A spear is one item with two weapons in it. Left-click and you **stab**: the
client sends a packet with no entity id in it, and the server does its own
raycast and hits *everything* along the ray. Hold right-click and you
**charge**: the spear becomes an item you are using, like a bow, except that
what it does each tick is look for entities in front of you and hurt them in
proportion to the closing speed. Neither path goes anywhere near
`Player.attack`, the method [the sword swing](the-sword-swing.md) is about,
and the second one has a property no other melee attack in the game has —
**a charging spear ignores the attack-strength cooldown entirely**, because
the code that applies the cooldown curves is skipped for the item you are
currently using.
## The cast
| class | what it decides | thread |
|---|---|---|
| `PiercingWeapon` | the stab: who can be hit along a ray, and what a hit does | server main (sounds: both) |
| `KineticWeapon` | the charge: three speed conditions, and the damage from closing speed | server main |
| `Item.Properties.spear` | the seven spears, and the combat components that make one | — |
| `Minecraft` / `MultiPlayerGameMode` | the client's short-circuit, and the packet with no target | client main |
| `ServerGamePacketListenerImpl` | `ServerboundPlayerActionPacket.Action.STAB`, and the piercing rejection in the ordinary attack handler | server main |
| `LivingEntity.stabAttack` | the shared tail: damage, two knockbacks, dismount, durability | server main |
| `Player.stabAttack` | the override that adds the cooldown curves — sometimes | server main |
| `SpearUseGoal` / `SpearAttack` | how a zombie or a piglin does the same thing | server main |
## What an item needs to be a spear
`Item.Properties.spear` is one builder call per material, and the seven
spears — `Items.WOODEN_SPEAR` through `Items.NETHERITE_SPEAR` — differ
almost only in the numbers it is given; the wooden one gets its own sounds
and the netherite one is additionally fire-resistant. What it attaches is the interesting part,
because it is *both* weapons at once plus the reach to use them:
| component | what the spear gets |
|---|---|
| `DataComponents.PIERCING_WEAPON` | knockback yes, dismount no, a use sound and a hit sound |
| `DataComponents.KINETIC_WEAPON` | a contact cooldown of ten ticks, a delay, three conditions, and a damage multiplier |
| `DataComponents.ATTACK_RANGE` | `AttackRange.minReach` of 2.0 and `AttackRange.maxReach` of 4.5 — 2.0 and 6.5 in creative — with a hitbox margin and a mob factor |
| `DataComponents.MINIMUM_ATTACK_CHARGE` | 1.0: no partial-charge stab |
| `DataComponents.SWING_ANIMATION` | `SwingAnimationType.STAB`, with a per-material duration |
| `DataComponents.DAMAGE_TYPE` | `DamageTypes.SPEAR`, as a delayed holder component |
| `DataComponents.USE_EFFECTS` | `UseEffects.canSprint` **true** — the one item that lets you run while using it |
| `DataComponents.WEAPON` | a durability cost of one per attack |
| attribute modifiers | `Attributes.ATTACK_DAMAGE` from the material, and an `Attributes.ATTACK_SPEED` derived from the swing duration |
That `UseEffects` override is why a spear feels unlike every other held-down
item: [hunger and experience](hunger-and-experience.md) explains the
component, and `LocalPlayer.isSlowDueToUsingItem` is the reader that a spear
turns off.
## Two entries, one exit
```mermaid
flowchart TD
CLICK["left-click: Minecraft.startAttack"]
HAS["main hand has PIERCING_WEAPON?"]
NORMAL["the ordinary path: MultiPlayerGameMode.attack, then Player.attack"]
PA["MultiPlayerGameMode.piercingAttack — plays the sound, resets the ticker locally"]
PKT["ServerboundPlayerActionPacket, Action.STAB — no entity id, a dummy position"]
SGPL["handlePlayerAction: not a spectator, cannotAttackWithItem with a 5-tick tolerance"]
PW["PiercingWeapon.attack — the server's own raycast"]
USE["right-click: Item.use sees KINETIC_WEAPON, startUsingItem for 72000 ticks"]
TICK["every use tick: ItemStack.onUseTick, server side only"]
KW["KineticWeapon.damageEntities — ticksUsed, look vector, closing speed"]
RAY["ProjectileUtil.getHitEntitiesAlong — every entity on the ray, filtered by PiercingWeapon.canHitEntity"]
STAB["stabAttack — damage, two knockbacks, dismount, durability"]
CLICK --> HAS
HAS -- "no" --> NORMAL
HAS -- "yes" --> PA
PA --> PKT
PKT --> SGPL
SGPL --> PW
USE --> TICK
TICK --> KW
PW --> RAY
KW --> RAY
RAY --> STAB
```
Two things in that picture are worth stopping on. The **client tells the
server nothing about the target** on the stab path: the packet is a
`ServerboundPlayerActionPacket` carrying
`ServerboundPlayerActionPacket.Action.STAB`, whose block position and
direction are dummies, and every question about what was hit is answered by
the server's own raycast. And the ordinary attack handler *refuses* a
piercing weapon — `ServerGamePacketListenerImpl.handleAttack` checks for
`DataComponents.PIERCING_WEAPON` and drops out — so the two paths cannot be
confused for one another even by a client that tries.
## The stab
`PiercingWeapon.attack` takes the attacker's `Attributes.ATTACK_DAMAGE`,
the weapon in the given slot and `LivingEntity.getAttackRangeWith`, and
walks `ProjectileUtil.getHitEntitiesAlong` with the block-collider clip
context — so a wall stops the ray, but a crowd does not. **Every** entity
along it is stabbed — in the order the ray walk happened to append them,
which is not sorted by distance — each through `LivingEntity.stabAttack`
with the same damage figure.
`PiercingWeapon.canHitEntity` is the filter, and it is a projectile-shaped
test rather than a melee one: the target must not be
`Entity.isInvulnerableToPiercingWeapon`, must be alive, and must satisfy
`Entity.canBeHitByProjectile`. Player against player defers to
`Player.canHarmPlayer`, and an entity riding the same vehicle as the
attacker is not hit. An `Interaction` short-circuits the whole filter to
*hittable* before any of those tests, same vehicle included.
Afterwards the attacker gets `LivingEntity.onAttack` — which on a `Player`
resets the attack-strength ticker — and `LivingEntity.postPiercingAttack`,
the hook that runs `EnchantmentHelper.doPostPiercingAttackEffects`; only
then does the weapon play `PiercingWeapon.makeHitSound` if anything was hit
and `PiercingWeapon.makeSound` regardless, and swing the arm. The client
half did the swing and `LivingEntity.onAttack` itself a round trip earlier,
but not `LivingEntity.postPiercingAttack`, which does nothing off a
`ServerLevel`.
## The charge
A kinetic weapon is *used*, not swung. `Item.use` sees
`DataComponents.KINETIC_WEAPON`, calls `LivingEntity.startUsingItem` and
plays the sound; `Item.getUseDuration` returns **72000** for it, the same
effectively-endless duration a bow gets, so the charge ends only when you
release ([using an item](../items/using-an-item.md)). Starting also
allocates `LivingEntity.recentKineticEnemies`, a server-side map of who has
been hit and when, which `LivingEntity.stopUsingItem` throws away.
Each use tick, `ItemStack.onUseTick` diverts to
`KineticWeapon.damageEntities` — **and skips the item's own
`Item.onUseTick` when it does**. What that method computes is a speed
argument, not a swing:
- **How long you have been charging.** Ticks used must be at least
`KineticWeapon.delayTicks`; everything below is measured from there.
- **How fast you are going, along your look vector.**
`KineticWeapon.getMotion` reads `Entity.getKnownSpeed` — the *reported*
movement from [input to movement](input-to-movement.md) — scaled to
blocks per second, taking the **root vehicle's** motion for a
non-player passenger.
- **How fast the gap is closing.** The target's own projected speed is
subtracted, floored at zero, and that relative speed is what the damage
is built from.
- **Whether you already hit them.** `LivingEntity.wasRecentlyStabbed`
against `KineticWeapon.contactCooldownTicks` — ten for a spear — is why
running through a crowd does not hit the same mob every tick.
Three independent `KineticWeapon.Condition`s then decide what the hit *is*:
`KineticWeapon.dismountConditions`, `KineticWeapon.knockbackConditions` and
`KineticWeapon.damageConditions`, each a maximum duration and a speed bar —
measured against the attacker's own projected speed for the first two, and
against the closing speed for damage. A spear's three come from the builder
with different windows, and for all seven materials they nest the same way:
damage has the longest window and the lowest bar, dismount the shortest
window and much the highest. So a charge that has run too long can still
hurt when it can no longer knock a target off a horse — a wooden spear
dismounts for five seconds, knocks back for ten and damages for fifteen. If any of the three
passes, the damage is the attacker's **base** `Attributes.ATTACK_DAMAGE`
plus the floor of relative speed × `KineticWeapon.damageMultiplier` — base
value, so the modifiers a sword swing would pick up are not in it.
A landed charge broadcasts an entity event, and it is the part of the
telling that is *about the charge*, alongside the ordinary damage sync,
knockback and durability every hit sends: `LivingEntity.onKineticHit` plays a local hit
sound, throttled to ten ticks by a bare literal in
`LivingEntity.onKineticHit` — the `KineticWeapon.HIT_FEEDBACK_TICKS`
constant that names that number is read by nothing — and
`LivingEntity.getTicksSinceLastKineticHitFeedback` feeds the animation. A
`ServerPlayer` also trips `CriteriaTriggers.SPEAR_MOBS_TRIGGER` with the
number of living entities stabbed this charge.
## The tail, and the cooldown that is not applied
Both paths end in a method called *stabAttack*, which exists twice.
`LivingEntity.stabAttack` is the general one: it returns false off a
`ServerLevel`, runs the damage through `EnchantmentHelper.modifyDamage`,
calls `Entity.hurtServer`, applies two knockbacks — a flat one and
`LivingEntity.getKnockback` — dismounts the target if the caller asked,
runs `ItemStack.hurtEnemy` and the post-attack enchantment effects, and
plays the attack sound.
`Player.stabAttack` overrides it, and the override is where the spear
becomes strange. It computes the enchantment boost the way `Player.attack`
does, and then applies the two cooldown curves — the linear one to the
boost, the quadratic `Player.baseDamageScaleFactor` to the base — **only if
the player is not currently using an item in that slot.** A stab qualifies,
so a stab is charged like a sword swing. A kinetic charge does not: while
you are holding the spear out, both curves are skipped and every tick's hit
lands at full base damage. The rest of the override is the familiar tail —
`Player.deflectProjectile` can still end it, the knockbacks are the same
two, `Player.itemAttackInteraction` applies the durability cost, and
`Player.causeFoodExhaustion` charges the same 0.1 a sword does.
## Questions players ask
**Why does the server never ask which mob I stabbed?** Because it does not
trust the answer and does not need it. The stab packet is an action, not a
target: the server raycasts from the player's own look vector with the
weapon's `AttackRange`, and hits everything on the line. That also puts the stab
in the small company of melee attacks whose hit count is not one, beside the
sword's sweep and the spear's own charge.
**Does a spear work while I am moving?** It is the only weapon that
*requires* it. The charge's damage is built from closing speed, and the
`UseEffects` override exists so you can sprint while charging — the two
halves of the same design.
**Why did my charge stop hurting the same mob?**
`KineticWeapon.contactCooldownTicks` remembers it for ten ticks. The map is
allocated when you start using the spear and dropped when you stop, so
releasing and re-charging clears everyone.
**Can a mob do this?** Yes, both ways round. `SpearUseGoal` drives the
charge for a goal-based mob and `SpearApproach`, `SpearAttack` and
`SpearRetreat` do it for a brain-based one — zombies, zombified piglins and
piglins are the users in the tree, and `Piglin` treats a kinetic weapon like
a crossbow when deciding what it is holding. Both read
`KineticWeapon.computeDamageUseDuration` to know how long to hold it. The
thresholds are easier for them: the speed conditions are scaled by an action
factor of **0.2** for anything that is not a player, against 1.0 for you.
**Is any of this data-driven?** The shape is; the values are not.
`PiercingWeapon` and `KineticWeapon` are ordinary data components with
codecs and stream codecs, so a data pack can describe a weapon without code
— but every built-in spear's numbers are hard-coded in
`Item.Properties.spear`, not read from JSON. One field is not a combat
number at all: `KineticWeapon.forwardMovement` — 0.38 for a spear — is read
**only** by `SpearAnimations`, and only by its *third-person* methods, which
is a rendering offset living in the middle of a combat component.
## Where to look
`PiercingWeapon` · `KineticWeapon` · `KineticWeapon.Condition` ·
`Item.Properties.spear` · `Minecraft.startAttack` ·
`MultiPlayerGameMode.piercingAttack` · `ServerboundPlayerActionPacket` ·
`LivingEntity.stabAttack` · `Player.stabAttack` ·
`LivingEntity.recentKineticEnemies` · `ProjectileUtil.getHitEntitiesAlong` ·
`SpearUseGoal` · `SpearAnimations`
---
*Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes `tools/verify_names.py`.*
---
# Hunger and experience
> Verified against **Minecraft 26.2** · Part VIII · Two bars above the hotbar that the server owns outright: one you empty by sprinting, one you fill by mining — and they meet in the enchanting table.
The food bar and the experience bar look like the same kind of thing: a
number the server keeps and sends you. They are, and they are also the two
counters a player argues with most — the meal that does not seem to fill
you, the levels that vanish into an anvil. Both are worth following for the
same reason: neither is quite what the interface suggests. **There is no
method called *eat*** on `Player` or `LivingEntity` — eating is a walk over
the components of a stack that happens to end in `FoodData.eat` — and the
experience packet is change-detected on the **total** alone, so every
mutation that changes only your level has to poison the last-sent value or
the bar will not move.
## The cast
| class | what it decides | thread |
|---|---|---|
| `FoodData` | the food bar, saturation and exhaustion — and by type, only on the server | server main |
| `FoodProperties` | how much a given item is worth, as a data component | both |
| `Consumable` | how long eating takes, what it sounds like, and what else it applies | both |
| `Player` | the four experience fields, the level curve, and the enchanting seed | both |
| `ExperienceOrb` | a value and a multiplicity, wandering toward you | both |
| `ServerPlayer` | the change detection that turns any of this into a packet | server main |
Both halves hang off `ServerPlayer.doTick`, the connection-driven half of
[the two-phase tick](the-two-phase-tick.md); the level's entity tick touches
essentially none of it. The order inside that half matters: item use is
resolved, then `Player.aiStep` runs `ServerPlayer.tickRegeneration` — which
*is* the Peaceful refill, its whole body gated on that difficulty — and the
orb pickup, then **`FoodData.tick`**, and then the
change-detection block that emits the packets. So a meal eaten this tick and
a Hunger effect's exhaustion from this tick are both visible to
`FoodData.tick` in the same tick, and the resulting health and food reach
the client in that tick too.
## The food bar is four numbers and a pile of literals
**`FoodData`** (`world/food`) is a value bag with no back-reference to the
player: `FoodData.foodLevel` (20), `FoodData.saturationLevel` (5.0),
`FoodData.exhaustionLevel` and `FoodData.tickTimer`. Its surface is the two
`FoodData.eat` overloads — one taking a `FoodProperties`, one taking a
nutrition and saturation pair — plus `FoodData.addExhaustion` (which caps at
40), `FoodData.needsFood`, `FoodData.hasEnoughFood`, `FoodData.setFoodLevel`,
`FoodData.setSaturation`, the two accessors, and `FoodData.tick` — whose
signature is `FoodData.tick(ServerPlayer)`, **server-only by type**. It saves
as four loose keys in the player tag, not a sub-compound.
**`FoodConstants`** names every threshold in the system —
`FoodConstants.MAX_FOOD`, `FoodConstants.HEAL_LEVEL`,
`FoodConstants.HEALTH_TICK_COUNT`,
`FoodConstants.HEALTH_TICK_COUNT_SATURATED`,
`FoodConstants.EXHAUSTION_DROP`, `FoodConstants.EXHAUSTION_HEAL`,
`FoodConstants.EXHAUSTION_SPRINT`, `FoodConstants.EXHAUSTION_MINE`,
`FoodConstants.EXHAUSTION_ATTACK`, `FoodConstants.SPRINT_LEVEL`,
`FoodConstants.SATURATION_FLOOR` and the saturation-quality ladder from
`FoodConstants.FOOD_SATURATION_POOR` to
`FoodConstants.FOOD_SATURATION_SUPERNATURAL` — and **none of them is
referenced by anything.** Only `FoodConstants.saturationByModifier` has call
sites; `FoodData` writes every threshold as an inline literal, so the
constants file and the behaviour can drift apart without a compile error.
What `FoodData.tick` does with those literals is one exhaustion rule and a
three-way mutually exclusive chain:
```mermaid
flowchart TD
EX["exhaustion above 4.0?"]
EX -- "yes" --> DRAIN["spend 4.0 of exhaustion, and take 1.0 off saturation — or one off the food bar once saturation is spent and the difficulty is not Peaceful"]
EX -- "no" --> CHAIN
DRAIN --> CHAIN["then at most one of the three"]
CHAIN --> FAST["heal fast: every 10 ticks, at a full bar, hurt, with saturation left and the game rule on — the heal is funded by that saturation"]
CHAIN --> SLOW["heal slowly: every 80 ticks, at 18 or more food, hurt, and the game rule on"]
CHAIN --> STARVE["starve: every 80 ticks at zero food, and not gated on the game rule at all"]
```
The game rule is `GameRules.NATURAL_HEALTH_REGENERATION`, which lives in
`world/level/gamerules` with typed `GameRule` lookups ([level data and
rules](../../reference/level-data-and-rules.md)). The starvation hit only
lands if health is above five hearts, or above half a heart on Normal, or
unconditionally on Hard: five hearts is the floor on Easy and Peaceful, half
a heart on Normal, and death on Hard. `DamageTypes.STARVE` is declared with
zero exhaustion, so starving does not feed itself.
## Eating is a component walk
**`FoodProperties`** (`DataComponents.FOOD`) is three things: nutrition,
saturation and *can always eat*. The duration is not on it — that lives on
**`Consumable`** (`DataComponents.CONSUMABLE`), which owns
`Consumable.consumeSeconds` (with `Consumable.consumeTicks` derived from
it), the `ItemUseAnimation`, the sound, the particles and a list of
`ConsumeEffect`s: `ApplyStatusEffectsConsumeEffect`,
`RemoveStatusEffectsConsumeEffect`, `ClearAllStatusEffectsConsumeEffect`,
`TeleportRandomlyConsumeEffect`, `PlaySoundConsumeEffect`.
`FoodProperties` reaches the player by implementing **`ConsumableListener`**,
and `Consumable.onConsume` walks every component of that type on the stack.
It is not the only implementation — `PotionContents`,
`SuspiciousStewEffects` and `OminousBottleAmplifier` implement it too, and
`PotionContents` is how drinking applies an effect, which is where this page
and [status effects](status-effects.md) meet in one method. Two routes reach
`FoodData.eat` without any of that: `CakeBlock.eat`, and the saturation
effect, both using the raw nutrition-and-saturation overload.
```mermaid
sequenceDiagram
participant LE as LivingEntity
participant IStack as ItemStack
participant Cons as Consumable
participant FP as FoodProperties
participant FD as FoodData
participant SP as ServerPlayer
participant CPL as ClientPacketListener
LE->>LE: updateUsingItem — the zero check is server side only
SP->>CPL: ClientboundEntityEventPacket(9) — sent first, so the client replays the meal
LE->>IStack: finishUsingItem — the item decides what finishing means
IStack->>Cons: onConsume — walks every ConsumableListener on the stack
Cons->>FP: onConsume — the food component is one such listener
FP->>FD: eat — nutrition and pre-multiplied saturation, clamped
Cons->>Cons: onConsumeEffects — server only#59; then consume(1)
SP->>FD: tick — exhaustion drain, then regen or starvation
SP->>CPL: ClientboundSetHealthPacket — when health, food or zero-saturation changed
```
`Consumable.canConsume` consults `Player.canEat` only when the stack has
`DataComponents.FOOD` and the user is a player — potions and milk are
ungated — and `Player.canEat` itself passes for *invulnerable abilities*,
*can always eat*, or `FoodData.needsFood`. An item whose
`Consumable.consumeTicks` is zero is consumed instantly with no animation.
After the food lands, `ItemStack.finishUsingItem` applies
`DataComponents.USE_REMAINDER` and `DataComponents.USE_COOLDOWN` ([using an
item](../items/using-an-item.md)).
The *decision* to finish is server-only, but the client replays the meal:
the server announces it with an entity event, `Player.handleEntityEvent`
turns that back into `LivingEntity.completeUsingItem`, and the client
therefore runs `FoodProperties.onConsume` and its `FoodData.eat` locally,
with no side guard on it. `ClientPacketListener.handleSetHealth` then
overwrites food and saturation outright, and routes health through
`LocalPlayer.hurtTo`, which works out the delta first so the damage flash
still plays. The client also *reads* its food data for two decisions of its
own: sprinting is gated on having more than six food *or* being able to
fly, and the HUD's food-bar jitter reads saturation.
Eating slowdown is a third component again. **`UseEffects`**
(`DataComponents.USE_EFFECTS`) is on *every* item —
`UseEffects.canSprint`, `UseEffects.interactVibrations`,
`UseEffects.speedMultiplier` — and its slowdown half is genuinely
client-side: `LocalPlayer.isSlowDueToUsingItem` and
`LocalPlayer.itemUseSpeedMultiplier` are its only readers, which is how a
spear overrides the component and lets you sprint while charging ([the
spear](the-spear.md)). The vibration half is not client-side:
`ItemStack.causeUseVibration` reads the same component server-side to decide
whether using an item emits a game event.
## The other bar
`Player.experienceLevel`, `Player.experienceProgress`,
`Player.totalExperience` and `Player.enchantmentSeed`, plus
`Player.takeXpDelay` and `Player.lastLevelUpTime` — which exists only to
throttle the level-up sound — are the whole of it. The arithmetic is
`Player.giveExperiencePoints`, `Player.giveExperienceLevels` and
`Player.getXpNeededForNextLevel` — the three-segment curve with corners at
levels 15 and 30.
Where orbs come from is worth naming, because two game rules gate it:
`LivingEntity.dropExperience` requires the experience not to have been
consumed already, and either an always-dropper or a recent player kill with
`GameRules.MOB_DROPS` on and the entity's own
`LivingEntity.shouldDropExperience` agreeing; a player's own death drop is
`Player.getBaseExperienceReward`, seven per level capped at 100, unless
`GameRules.KEEP_INVENTORY` — or unless you are a spectator, which drops
nothing.
**`ExperienceOrb`** is an `Entity` with `ExperienceOrb.DATA_VALUE` synched
and `ExperienceOrb.count`, `ExperienceOrb.age`, `ExperienceOrb.health` and
`ExperienceOrb.followingPlayer` unsynched — though `ExperienceOrb.age` and
`ExperienceOrb.followingPlayer` are still mutated by the client's own tick,
which runs the follow behaviour locally. `ExperienceOrb.health` is not: only
`ExperienceOrb.hurtServer` ever writes it.
`ExperienceOrb.awardWithDirection` splits an amount into denominations via
`ExperienceOrb.getExperienceValue` (a fixed ladder from 2477 down to 1) and
calls `ExperienceOrb.tryMergeToExisting` for each; `ExperienceOrb.award` is
a one-line delegate to it. Merging is by **count, not value**: an orb
carries one value and a multiplicity. The merge candidate search picks a
*random* group number below `ExperienceOrb.ORB_GROUPS_PER_AREA` and only
merges into orbs whose id is congruent to it — which caps how many orbs
collapse into one entity rather than reducing the scan.
## Questions players ask
**Why does walking cost nothing?** Because it is multiplied by zero, out
loud: `ServerPlayer.checkMovementStatistics` multiplies distance by a
literal zero on both the walking and the crouching branch, while
`FoodConstants.EXHAUSTION_WALK` documents the intent and is referenced by
nothing. The exhaustion economy is sprinting, jumping, swimming, mining,
attacking, the Hunger effect, the `ApplyExhaustion` enchantment effect, and
being hurt — the last of which is data-driven, since
`DamageSource.getFoodExhaustion` reads the damage type. And in creative or
spectator the whole economy is disabled in one line:
`Player.causeFoodExhaustion` returns immediately for invulnerable abilities.
**Why does my saturation sit above my food bar on Peaceful?**
`ServerPlayer.tickRegeneration` raises saturation directly toward 20, while
`FoodData.eat` clamps saturation to the food level. Only one of the two
respects the clamp.
**Why does the saturation shown by the HUD lag?** Because it is sent but not
change-detected. `ClientboundSetHealthPacket` carries a full float, yet the
server only notices whether saturation became *zero* — so your client's
saturation does not update until health or food moves.
**Why does enchanting update the bar when the level packet is
change-detected on the total?** Because the code forces it.
`ServerPlayer.lastSentExp` is compared against `Player.totalExperience`, so
every mutation that changes only the level — `ServerPlayer.setExperienceLevels`,
`Player.giveExperienceLevels`, enchanting, respawn — has to poison the
last-sent value to make the packet go out.
**Why does an orb repair my pickaxe before it reaches my bar?** Because
`ExperienceOrb.playerTouch` runs `ExperienceOrb.repairPlayerItems` first and
only the remainder becomes experience — and that method calls itself with
the leftover. One orb entity can also be picked up many times: it carries a
count, decremented per touch behind a two-tick `Player.takeXpDelay`, and a
player absorbs **one orb per tick**, chosen at random from those it is
touching. The pickup sweep buckets orbs separately from items for exactly
that purpose.
**Why is the Standard Galactic gibberish stable until I enchant?** Because
`Player.onEnchantmentPerformed` subtracts the level cost *and* re-rolls
`Player.enchantmentSeed`, while `AnvilMenu` — which also spends levels,
through `Player.giveExperienceLevels` — does not. A seed that loads back as
zero is re-rolled on read. [Enchanting](../items/enchanting.md) owns what
the seed is for.
**What crosses the wire?** `ClientboundSetHealthPacket` (health, food and
saturation together, to that player only), `ClientboundSetExperiencePacket`
(progress, level and total), and `ClientboundTakeItemEntityPacket` for the
orb pickup animation. The data-driven side is `DataComponents.FOOD`,
`DataComponents.CONSUMABLE`, `DataComponents.USE_EFFECTS`,
`Registries.CONSUME_EFFECT_TYPE`,
`EnchantmentEffectComponents.REPAIR_WITH_XP` for mending, and the three game
rules above.
## Where to look
`FoodData` · `FoodConstants` · `FoodProperties` · `Consumable` ·
`ConsumableListener` · `ConsumeEffect` · `UseEffects` · `Foods` ·
`Player.giveExperiencePoints` · `ExperienceOrb` ·
`ServerPlayer.tickRegeneration` · `ClientboundSetHealthPacket` ·
`ClientboundSetExperiencePacket`
---
*Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes `tools/verify_names.py`.*
---
# Status effects
> Verified against **Minecraft 26.2** · Part VIII · You drink a potion of Poison: the server starts hurting you on a rhythm, and your client never runs a single one of the effect's hooks.
Poison II lands. Your health starts dropping in steps, the swirls appear,
the icon in the corner counts down, and if the connection stutters the
number in the corner keeps counting anyway. That last part is the whole
page. **The client never runs a `MobEffect` hook.** It counts durations
down, advances a blend factor, unhides a masked effect and spawns particles
from a list the server synched. It does *read* the effects it holds — jump
boost in `LivingEntity.getJumpBoostPower`, slow falling in
`LivingEntity.getEffectiveGravity`, levitation inside `LivingEntity.travel`
— because that is shared movement code your own player runs unguarded. But
every attribute modifier, every pulse of damage, every regeneration tick
happens on the server behind an explicit server-side guard, and the client's copy of the duration is
corrected by a re-send every six hundred ticks. An **infinite** effect is
never re-sent at all, because its duration is −1 and −1 never satisfies the
test.
## The cast
| class | what it decides | thread |
|---|---|---|
| `MobEffect` | what the effect *does*, and on what rhythm | server main (the client reads only its colour and blend durations) |
| `MobEffectInstance` | duration, amplifier, flags, and the masked effect underneath | both main threads |
| `MobEffects` | the forty built-in holders | — |
| `LivingEntity` | `LivingEntity.activeEffects`, the tick, and the three server-guarded hooks | both |
| `AttributeInstance` | where an effect's modifier actually lands | server main |
| `ServerPlayer` | who gets told, and how often | server main |
| `MobEffectUtil` | the questions the rest of the game asks about effects | both |
## What an effect is
**`MobEffect`** is the behaviour singleton: a category, a colour, a particle
factory, blend durations and a map of `MobEffect.AttributeTemplate`s. Its
hooks are the interesting part, because one of them is false by default.
| hook | when it runs |
|---|---|
| `MobEffect.shouldApplyEffectTickThisTick` | every tick, to ask whether this is a pulse — **false by default** |
| `MobEffect.applyEffectTick` | on a pulse; returning false ends the effect |
| `MobEffect.applyInstantaneousEffect` | never from the tick — only from the splash potion, the lingering cloud and the drink |
| `MobEffect.onEffectAdded` | when it lands on an entity that did not already have it |
| `MobEffect.onEffectStarted` | on every successful add, a refresh of an existing effect included |
| `MobEffect.onMobHurt` | when the holder takes damage |
| `MobEffect.onMobRemoved` | when the holder goes |
The default *false* is why each effect has its own rhythm: the overrides
give poison a pulse every *25 ≫ amplifier* ticks, regeneration every
*50 ≫ amplifier*, wither every *40 ≫ amplifier*, and hunger every tick.
Attribute modifiers go on as `AttributeInstance.addPermanentModifier` with
an amount linear in amplifier + 1, computed by
`MobEffect.AttributeTemplate.create` ([attributes](../entities/attributes.md)).
**`MobEffectInstance`** is the per-entity half: duration, amplifier, the
ambient, visible and show-icon flags, a private blend state, and
**`MobEffectInstance.hiddenEffect`** — the stack that lets a stronger,
shorter effect temporarily mask a weaker, longer one, built by
`MobEffectInstance.update`. `MobEffectInstance.INFINITE_DURATION` is −1, and
`MobEffectInstance.compareTo` is what orders the icons in the HUD.
`LivingEntity.canBeAffected` is the veto, and it consults three entity tags
as well as the effect itself.
**`MobEffects`** is forty entries, and every one is a `Holder`,
not a bare `MobEffect` — including `MobEffects.BREATH_OF_THE_NAUTILUS`. Some
point at attributes a reader would not expect: `MobEffects.JUMP_BOOST`
modifies `Attributes.SAFE_FALL_DISTANCE`, and `MobEffects.INVISIBILITY`
modifies `Attributes.WAYPOINT_TRANSMIT_RANGE`.
On the entity itself: `LivingEntity.activeEffects` (a plain unordered map),
`LivingEntity.effectsDirty`, and two synched values —
`LivingEntity.DATA_EFFECT_PARTICLES`, which is a **list of
`ParticleOptions`** rather than a packed colour, and
`LivingEntity.DATA_EFFECT_AMBIENCE_ID`. `MobEffectUtil` is the shared
question-asking surface: `MobEffectUtil.hasDigSpeed`,
`MobEffectUtil.hasWaterBreathing`,
`MobEffectUtil.shouldEffectsRefillAirsupply`,
`MobEffectUtil.addEffectToPlayersAround` and the duration formatter the
inventory screen uses.
## The trace: Poison II, on both sides at once
Effects are ticked from `LivingEntity.tickEffects`, the last call
`LivingEntity.baseTick` makes before it copies this tick's rotations into
last tick's — which for a player means inside
`ServerPlayer.doTick`, the connection-driven half of [the two-phase
tick](the-two-phase-tick.md), not the level's entity tick.
```mermaid
sequenceDiagram
participant LE as LivingEntity
participant MEI as MobEffectInstance
participant ME as MobEffect
participant AttrI as AttributeInstance
participant SP as ServerPlayer
participant CPL as ClientPacketListener
LE->>MEI: update — masks any weaker instance as hiddenEffect
LE->>ME: addAttributeModifiers — from LivingEntity.onEffectAdded, server-guarded
ME->>AttrI: addPermanentModifier — amount linear in amplifier + 1
SP->>CPL: ClientboundUpdateMobEffectPacket — amplifier, duration, four flag bits
Note over LE: every tick after this
LE->>MEI: tickServer — count down, and ask for a pulse
MEI->>ME: shouldApplyEffectTickThisTick — every 25 ≫ amplifier for poison
MEI->>ME: applyEffectTick — the pulse itself, and false here ends the effect
Note over LE: the client, in its own tick
LE->>MEI: tickClient — count down, unhide, advance the blend
```
The client branch of `LivingEntity.tickEffects` never calls
`MobEffect.applyEffectTick` and never touches an attribute. It does not even
remove an expired effect: it keeps a zero-duration instance until told
otherwise.
## Questions players ask
**Why does my duration sometimes jump?** Because it was wrong and got
corrected. All of `LivingEntity.onEffectAdded`,
`LivingEntity.onEffectUpdated` and `LivingEntity.onEffectsRemoved` are
server-guarded, so no attribute modifier is ever applied client-side and
attribute values arrive by their own sync; the client's *duration* is a
local countdown that drifts, re-sent every six hundred ticks. Two holes in
that: an **infinite** effect's duration is −1 and never satisfies the
re-send test, so it is never re-sent, and the re-send only ever reaches the
affected player or a player riding them.
**Why can I not see how long a mob's effect has left?** Because you were
never told. A client watching a mob it is not riding receives no
`MobEffectInstance` at all — only `LivingEntity.DATA_EFFECT_PARTICLES`, the
synched particle list, which is why other entities have swirls and no
numbers.
**Where did my weaker effect go?** Under the stronger one.
`MobEffectInstance.hiddenEffect` is a stack, and both of its codecs are
recursive, so both the save and the wire *can* carry the chain. The packet
does not: **`ClientboundUpdateMobEffectPacket` never uses that stream codec
at all**, writing an entity id, a `MobEffect` holder, an amplifier, a
duration and a flags byte by hand — so the client rebuilds an instance with
no hidden effect under it, and learns about the masked one only when
`MobEffectInstance.downgradeToHiddenEffect` surfaces it and triggers a
re-send.
**Why does Nausea swim in and out, but Poison just starts?** Blending is a
pure render quantity: `MobEffectInstance`'s blend state ticks only on the
client, is never saved and never sent, and only `MobEffects.NAUSEA` and
`MobEffects.DARKNESS` use it. The blend *bit* is set only when an effect is
first added — an update clears it, and the client responds by skipping the
blend.
**Why are a beacon's swirls so faint?** Twice over. The default particle
factory bakes ambience into the `ParticleOptions` itself — alpha 38 of 255
instead of opaque — so an ambient effect really does synch a different,
fainter particle; and it also makes them rarer, because the client spawns
one particle from the synched list with a probability that an invisible
entity divides by about four and a further five when **every** effect on
the entity is ambient, which is what `LivingEntity.DATA_EFFECT_AMBIENCE_ID`
records.
**Does an effect pulse on its own clock or the world's?** Both, depending on
whether it ends. `MobEffectInstance.tickServer` counts an infinite-duration
effect's pulses off the entity's age and a finite one off its own countdown.
**What happens when one effect adds another?** The rest of that tick's
effects are silently skipped. `LivingEntity.tickEffects` catches a
concurrent-modification error and drops it, so an effect that adds or
removes another quietly aborts the loop it was in.
**What crosses the wire?** `ClientboundUpdateMobEffectPacket` and
`ClientboundRemoveMobEffectPacket` for the effects you hold, and
`LivingEntity.DATA_EFFECT_PARTICLES` through [synched entity
data](../entities/synched-entity-data.md) for everyone else's swirls.
Effects themselves are code, registered into `BuiltInRegistries.MOB_EFFECT`
by `MobEffects` with no JSON behind them; what is data-driven is the ways
they land — `PotionContents`, `SuspiciousStewEffects`,
`ApplyStatusEffectsConsumeEffect` and its siblings — are [using an
item](../items/using-an-item.md) and [hunger and
experience](hunger-and-experience.md).
## Where to look
`MobEffect` · `MobEffectInstance` · `MobEffects` · `MobEffectCategory` ·
`MobEffectUtil` · `LivingEntity.tickEffects` · `LivingEntity.activeEffects` ·
`LivingEntity.canBeAffected` · `ClientboundUpdateMobEffectPacket` ·
`ClientboundRemoveMobEffectPacket`
---
*Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes `tools/verify_names.py`.*
---
# IX · Networking
> Verified against **Minecraft 26.2** · Part IX · One socket, four languages, and everything the two halves of the game say to each other across it.
Almost every part before this one had a single machine to describe. This
one has two, and a wire between them that neither trusts. Singleplayer runs the same
wire — an integrated server on its own threads, talking to the client
through an in-memory channel — so the split is not a multiplayer feature
bolted on the side, it is the shape of the program. A player recognises the
part by its failures: the rubber-band after a laggy jump, the block that
comes back, the *Connection lost* screen with a reason string on it, the
chat message that arrives with a grey bar down its left edge.
## The shape of the part
Part IX is **one wire and three passengers**. Two lectures carry bytes and
are really one lecture in two halves — the transport, then what travels on
it. The other three are unrelated systems the wire carries or is, and each
has a different shape: a state machine, a policy, and a protocol written
against an adversary.
```mermaid
flowchart TD
TC["The connection — bytes to a handler call, and back"]
PSC["Packets and stream codecs — what the thing crossing the wire is"]
PP["Protocol phases — one socket, four languages in turn"]
WCT["What the client is told — the server's choosing"]
CS["Chat and signing — a message that has to prove who sent it"]
TC -- "one round trip, two threads" --> PSC
PSC -- "a different codec table per phase" --> PP
PP --> WCT
PP --> CS
```
The spine is the pair at the top: read them together and the rest of the
part is applications. *Protocol phases* is what the wire *is* over the life
of a connection; *what the client is told* and *chat and signing* are the
two the part spends longest on, and neither needs the other. They are not
the same size of traffic: entity tracking and chunk sending are most of
what a connection ever carries, and chat is a few packets a minute.
## Before you start
[Part III](../server/README.md), and not optionally. Two of this part's
claims are really facts about somebody else's loop, and Part IX states the
consequence and links rather than teaching them a third time: [the server
tick](../server/server-tick.md) owns what happens after every level has
ticked, and [the level tick](../server/server-level-tick.md) owns the phase
in which broadcasts go out — before the entity phase, which is why one
broadcast carries this tick's block changes but the *previous* tick's
entity movement.
[Part I's anatomy](../anatomy/anatomy.md) for the two-loops figure: the
client's frame loop and the server's tick loop are different clocks, and the
client drains its inbound packets once per **frame**, not once per tick.
That one fact is behind half of what looks like network jitter. Part X's
*client loop* is the deeper version of it, and this part does not wait for
that page: where the arithmetic matters, the pages here state the
consequence and link forward.
Then [Part II](../foundations/README.md) for two objects this part assumes
whole: [codecs](../foundations/codecs-nbt-json.md), because a packet codec
is the same idea specialised to a byte buffer, and
[components](../foundations/text-components.md), because a chat message is
one. And [authority](../entities/authority.md) from Part VI, which is the
premise under *what the client is told*: the server does not send the client
the truth, it sends the client what it is not allowed to be wrong about.
## Watch in this order
1. [The connection](the-connection.md) — bytes land on a socket, and some
milliseconds later a method runs on the game thread. The lecture with the
round-trip diagram: two threads, two codec layers, one hop.
2. [Packets and stream codecs](packets-and-stream-codecs.md) — the second
half of the same lecture. What the thing crossing the wire is, now that
you have watched it travel.
3. [Protocol phases](protocol-phases.md) — a login, from clicking a server
in the list to standing in the world. Four languages over one socket, and
the player object built *after* the configuration task named for
preparing it has reported itself finished.
4. [What the client is told](what-the-client-is-told.md) — a creeper walks
into view. Not a trace but a policy: every gate a change passes before it
becomes a packet, and the things the server decides never to say.
5. [Chat and signing](chat-and-signing.md) — the part's closer, and the only
system in the book designed against an adversary. What each check
catches, and whether it kills the message, the chain, or the connection.
One and two are the pair to keep together. Four and five can be watched in
either order, and neither strictly needs three: they are applications of the
play phase and never name the phase machinery. Three comes first because it
is where the wire's own story ends.
## Reference this part uses
[Packets](../../reference/packets.md) above all — the catalogue this part
narrates, and the page to keep open beside every lecture in it.
[Registries](../../reference/registries.md) for the registry data that
crosses during configuration, [components](../../reference/components.md)
where a packet carries a stack, and [diagram
lanes](../../reference/lanes.md) for the abbreviations these pages' figures
use. [The threads](../../reference/threads.md) names the Netty event loop
and the two game threads by their real names.
Where the part stops: what the *client* does with what it is told is [the
client level](../client/the-client-level.md) and [prediction and
acknowledgement](../client/prediction-and-acks.md) in Part X, and how a
`ServerPlayer` comes to exist at all is [players and
sessions](../server/players-and-sessions.md) in Part III.
---
*Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes `tools/verify_names.py`.*
---
# The connection
> Verified against **Minecraft 26.2** · Part IX · you swing at a pig and the server answers: one round trip, from a value on one thread to bytes on a wire to a method call on another.
You swing. A small immutable value is handed to `Connection.send` on the
client's main thread, and some milliseconds later a method runs on the
server's game thread with that value as its argument; the server's answer
makes the same trip in reverse. Now close the server list and open a
singleplayer world. Every sentence above is still true. The integrated server
is another thread in the same process, and the client reaches it through a
real Netty channel with a real `PacketEncoder` and a real `PacketDecoder` in
it: **singleplayer serialises every packet to bytes and parses them back
again.** The local pipeline swaps the length-prefix framing for an in-memory
hand-off and never installs a cipher or a compressor, and that is the whole
of the difference. There is no local shortcut: the same encoder runs, the
same decoder runs, and the value your click produced is rebuilt from bytes on
the other side.
## The cast
| class | what it decides | thread |
|---|---|---|
| `Connection` | the channel, the current `PacketListener`, and when a fault kills the link | Netty event loop, plus one call a tick from a game thread |
| `PacketEncoder` | one packet becomes the bytes of one frame, or is skipped | the sender's Netty event loop |
| `PacketDecoder` | one frame becomes one packet, and a terminal one dismantles the codec | the receiver's Netty event loop |
| `PacketProcessor` | the queue that carries a decoded packet to a game thread | filled from Netty, drained by its owner |
| `PacketListener` | whether this packet should be handled at all — asked twice | both |
| `TickablePacketListener` | the only way a listener gets time when no packet has arrived | a game thread |
| `ServerConnectionListener` | the server's accept side, and which connections get ticked | server main |
| `EventLoopGroupHolder` | NIO, Epoll, KQueue or in-memory, and the threads that run them | any |
## One packet there, and one back
```mermaid
sequenceDiagram
participant CPL as ClientPacketListener
participant Conn as Connection
participant PEnc as PacketEncoder
participant Wire as the network
participant PDec as PacketDecoder
participant SGPL as ServerGamePacketListenerImpl
Note over Conn,PDec: one instance of each of these at each end
Note over CPL: client main thread — a value, not yet any bytes
CPL->>Conn: send — no packet queue, Netty owns the buffering
Note over Conn,PEnc: the sender's Netty event loop, joined by sendPacket
Conn->>PEnc: write, or write and flush
PEnc->>Wire: the phase's one codec writes a VarInt id, then the fields
Note over Wire: compress, prepender, encrypt, then decrypt, splitter, decompress
Note over PDec,SGPL: the receiver's Netty event loop
Wire->>PDec: exactly one whole frame
PDec->>Conn: channelRead0, at the tail of the pipeline
Conn->>SGPL: shouldHandleMessage, then Packet.handle
SGPL->>SGPL: ensureRunningOnSameThread queues the pair and aborts the call
Note over SGPL: server main thread — processQueuedPackets, before the tick
SGPL->>SGPL: shouldHandleMessage again, then the handler from the top
SGPL->>Conn: send the reply — written, not flushed, inside the tick's bracket
Note over Conn: the connection phase flushes the channel
Conn->>PEnc: the same handlers, the other direction
PEnc->>Wire: clientbound bytes
Wire->>PDec: one frame, on the client's Netty event loop
PDec->>Conn: channelRead0 again
Conn->>CPL: shouldHandleMessage, then Packet.handle
CPL->>CPL: ensureRunningOnSameThread queues the pair and aborts the call
Note over CPL: client main thread — the drain, once per frame
CPL->>CPL: the handler from the top, a frame later at the earliest
```
Four things in that picture are worth stopping on: the framing, the hop, the
drain, and the fact that `Connection` appears once but exists twice.
**Framing is separate from decoding.** `Varint21FrameDecoder` reads at most
three length bytes, refuses anything wider and refuses a zero length, and
emits exactly one frame or nothing at all. Everything downstream of it may
assume it is looking at one whole packet, which is why the codec layer never
has to handle a half-arrived value.
**`Connection.channelRead0` calls `Packet.handle` directly, on the Netty
thread.** There is no automatic hop. `PacketListener.shouldHandleMessage` is
consulted first — which is how a listener being torn down ignores what is
still arriving — and `Connection.receivedPackets` counts only the packets that
pass it. Three other outcomes live in that method: a packet arriving before
any listener is set is an illegal state; a packet whose listener is of the
wrong shape is a cast failure and an *invalid_packet* kick; and a rejected
schedule — the `PacketProcessor` closed because the game is shutting down —
becomes a *server_shutdown* kick.
**The hop is the handler's own first line.**
`PacketUtils.ensureRunningOnSameThread` asks the `PacketProcessor` whether
this is the right thread; if it is not, it enqueues the listener-and-packet
pair and throws the singleton `RunningOnDifferentThreadException`, a stackless
exception that `Connection.channelRead0` catches and drops on the floor. **The
handler body then runs again from the top** when the queue is drained, which
is why a handler method must do nothing observable before that line. A handler
that touches no game state — the pong bookkeeping, the chunk-batch clock, the
keep-alive answer — simply omits it and runs on Netty; the client's play
listener has nine, listed in
[threads](../../reference/threads.md#the-nine-client-handlers-that-never-hop).
The unknown-custom-payload fallback is not one of them, and looks as though it
should be: `ClientCommonPacketListenerImpl` hops first and dispatches to it
afterwards, so it runs on the main thread like everything else.
**The drain has a phase of its own, and the two sides do not schedule it
alike.** The server drains before the tick proper, so every packet that
arrived since last time enters the world at one point ([the server
tick](../server/server-tick.md)); the client drains **once per frame**, not
once per tick, because its tick is a sub-step of the frame loop ([two loops
and a wire between them](../anatomy/anatomy.md#two-loops-and-a-wire-between-them),
and [the client loop](../client/the-client-loop.md) for the arithmetic). The
consequence for a packet is that its handling latency on the client is a frame,
not a tick — and that packet handling and *execute*-style task scheduling are
two different queues drained at two different moments on both sides.
**The drain re-asks the same question.** `PacketProcessor.ListenerAndPacket`
calls `PacketListener.shouldHandleMessage` a second time before dispatching,
and logs and drops if the answer has changed. That is the gate that matters:
a packet can wait between the two checks, and a disconnect arriving in between
must not be able to run its handler.
**Errors on re-dispatch go somewhere else entirely.** The drain catches
exceptions only — a bare out-of-memory error is not caught at all — and routes
what it does catch to `PacketListener.onPacketError`, which by default raises
a reported crash. The one special case is a `ReportedException` *caused by* an
out-of-memory error: that is rethrown, but not untouched.
`PacketUtils.makeReportedException` delegates both steps to
`PacketUtils.fillCrashReport`, which first decorates the report with an
*Incoming Packet* category naming the type and its terminal and skippable
flags, then lets the listener add its own detail.
## The pipeline, in both directions
Two directions through one list of handlers. Inbound runs head to tail;
outbound runs tail to head. Handlers in *italics* are added later, if at all.
| inbound order | handler | added by |
|---|---|---|
| 1 | `"timeout"` — a read timeout of thirty seconds | the connect or accept site, before serialization |
| 2 | `"legacy_query"` — `LegacyQueryHandler` | `ServerConnectionListener.startTcpServerListener` only, and only if the server replies to status; removes itself on the first modern byte |
| 3 | *`"decrypt"`* — `CipherDecoder` | `Connection.setEncryptionKey` |
| 4 | `"splitter"` — `Varint21FrameDecoder` | `Connection.configureSerialization` |
| 5 | *`"decompress"`* — `CompressionDecoder` | `Connection.setupCompression`, inserted directly *after* `"splitter"` |
| 6 | an unnamed flow-control handler | `Connection.configureSerialization` |
| 7 | `"decoder"` or `"inbound_config"` | `Connection.configureSerialization` |
| 8 | *`"bundler"`* — `PacketBundlePacker` | `Connection.setupInboundProtocol`, only for a protocol with a bundle |
| 9 | `"packet_handler"` — the `Connection` itself | `Connection.configurePacketHandler` |
Outbound, from the game outwards: `"packet_handler"`, then `"hackfix"` — an
anonymous pass-through that `Connection.configurePacketHandler` adds
immediately before it, whose write method does nothing but call its
superclass — then *`"unbundler"`* (`PacketBundleUnpacker`), `"encoder"` or
`"outbound_config"`, *`"compress"`*, `"prepender"`, *`"encrypt"`*, and the
socket.
Which side gets a live codec at birth is decided by direction. The end that
will *receive* the handshake — the server — is built with a real `"decoder"`
and a dead `"outbound_config"` placeholder; the end that will *send* it gets a
real `"encoder"` and a dead `"inbound_config"`. Only the live one is built
from `Connection.INITIAL_PROTOCOL`, which is `HandshakeProtocols.SERVERBOUND`.
The placeholder is a bare `UnconfiguredPipelineHandler` holding no protocol at
all, which is the entire point of it.
`HandlerNames` is a class of constants for most of those names, and **nothing
references it**: every name the pipeline is actually built with is a string
literal in `Connection`, `ServerConnectionListener`, `ProtocolSwapHandler` and
`UnconfiguredPipelineHandler`. It has already drifted, which is what an index
no code reads does — it has no entry for *hackfix*, and it carries
`HandlerNames.LATENCY`, a handler that exists only on the local pipeline behind
a debug flag. Cite it for the names, not for the list.
### The threads underneath it
`EventLoopGroupHolder` owns the event-loop groups: four
instances behind two accessors, `EventLoopGroupHolder.local` for the in-memory
channel and `EventLoopGroupHolder.remote`, which tries KQueue and then Epoll
**only if the native-transport flag is set** and otherwise goes straight to
NIO. That flag is the client's option and the server property of the same
name, so switching it off really does change transport rather than hint at it.
The class lives in `server/network` and the client uses it too: `ConnectScreen`
and the server list ask for a group, and the server list hands the one it got
to `ServerStatusPinger`, which never asks for its own.
## Singleplayer runs the same pipeline
`Connection.configureInMemoryPipeline` builds the local variant, and the
differences are smaller than almost anyone assumes.
- `"splitter"` and `"prepender"` become `LocalFrameDecoder` and
`LocalFrameEncoder`, which do nothing but `HiddenByteBuf.pack` and
`HiddenByteBuf.unpack` — no length prefix, because the buffer never becomes
a byte stream.
- **No read timeout on either side**, so a wedged integrated server hangs
rather than disconnecting.
- No legacy query handler, and **never** a cipher or a compression handler —
though by two different mechanisms. Compression is refused at the
installation sites, which both test `Connection.isMemoryConnection`.
Encryption is not: neither `Connection.setEncryptionKey` nor either side's
key handler asks. What prevents it sits one gate further up, in
`ServerLoginPacketListenerImpl`, where the decision to *ask* for encryption
requires authentication **and** a non-memory connection, so
`ClientboundHelloPacket` is never sent and the ciphers are never reached.
- Everything else is identical, with one debug exception. `PacketEncoder` and
`PacketDecoder` are still there, still running the same `StreamCodec`s, and
singleplayer pays the full serialisation cost; the only handler that exists
*only* on the local pipeline is `ServerConnectionListener.LatencySimulator`,
installed when `SharedConstants.DEBUG_FAKE_LATENCY_MS` is positive.
The integrated server binds its channel through `EventLoopGroupHolder.local`
and `ServerConnectionListener.startMemoryChannel` hands back an address that
`Connection.connectToLocalServer` dials.
## Sending, and the two flushes
Outbound is the mirror image and shorter. `Connection.send` reaches
`Connection.sendPacket`, which checks whether the calling thread is already
the channel's event loop and, if not, schedules the write onto it — so a
packet sent from the game thread crosses the same boundary an inbound packet
does, just without a queue of its own. `Connection.doSendPacket` then chooses
between a write and a write-and-flush, and between a real future and Netty's
void promise; `Connection.flushChannel` performs the same hop for a bare flush.
`PacketSendListener` is the callback that rides along.
`PacketSendListener.thenRun` runs something once the packet is really on the
wire — which is how compression and the client's encryption are installed
*after* the packet that announced them, and how a disconnect waits for its own
kick message to leave. `PacketSendListener.exceptionallySend` sends a fallback
packet when the write fails. Both run on the event loop, which is why
"disconnect after sending" is not a game-thread operation.
### Two writes per client per tick
The server does not flush per packet. `ServerCommonPacketListenerImpl.send`
turns a send into a write with no flush while
`ServerCommonPacketListenerImpl.suspendFlushing` has set the flag — but only
for a caller on the server thread, because the flag is tested together with
the thread check, so anything sent from another thread flushes on its own
regardless of the bracket. The bracket is opened around the whole server tick,
and two different things then empty the buffer inside it.
**Two** — writes to the socket per client per tick.
The first is `Connection.tick` itself, which flushes the channel
unconditionally in the middle of its own body, and runs inside the bracket:
everything the levels produced leaves there, including the block changes that
were collected during the tick and only became packets in the chunk source's
step ([the level tick](../server/server-level-tick.md#the-broadcast-which-is-why-entities-are-a-tick-behind)).
The second is `ServerCommonPacketListenerImpl.resumeFlushing`, which both
clears the flag and flushes the channel itself, and therefore carries
everything the server does after the connection phase — the ordering of which
belongs to [the server tick](../server/server-tick.md#the-two-writes-each-client-gets).
### `Connection.tick`, the one call from a game thread
`Connection.tick` is also the only place a listener gets time on a game thread
without a packet having arrived. It drains `Connection.pendingActions` (the
one queue `Connection` owns, holding closures rather than packets, and mattering
only in the window before the channel exists); ticks the listener if it is a
`TickablePacketListener`; calls `Connection.handleDisconnection` if the channel
has died; flushes; every twentieth tick runs `Connection.tickSecond`, which
rolls the packet-rate averages; and last, samples bandwidth. Note the order:
the flush is in the middle, and the disconnect check happens before it.
Its callers differ by side. The server has one,
`MinecraftServer.tickConnection`, which walks `ServerConnectionListener.tick`.
The client has three, because the client has more than one connection:
`MultiPlayerGameMode.tick` ticks the play connection, `Minecraft.tick` ticks
`Minecraft.pendingConnection` — the one still handshaking or logging in, and
therefore the one that drives a login — and `ServerStatusPinger` ticks the
connections it opened to ping the servers in the list. Note the clock: the
pending connection is ticked at tick rate, not at the frame rate the drain
above runs on.
## A phase change is a message written down the pipeline
The pipeline is **reconfigured by writing through it**, not by editing it from
outside. `Connection.setupInboundProtocol` validates that the new listener's
direction and phase match the `ProtocolInfo`, assigns
`Connection.packetListener`, and then builds an
`UnconfiguredPipelineHandler.InboundConfigurationTask` — a closure that will
replace the current handler with a new `PacketDecoder` and turn auto-read back
on, optionally adding `"bundler"` after it. That task is *written down the
channel*, where `UnconfiguredPipelineHandler.Inbound` recognises it and runs it
with the right context, and `Connection.syncAfterConfigurationChange` blocks
the caller until it completes. `Connection.setupOutboundProtocol` is the mirror
image, and also records whether the new outbound protocol is the login one, for
the benefit of the disconnect path.
### Getting back to unconfigured, which nobody asks for
Returning to the unconfigured state is automatic, and it is asymmetric.
`ProtocolSwapHandler.handleInboundTerminalPacket` fires when a packet whose
`Packet.isTerminal` is true passes through `PacketDecoder`: it turns auto-read
*off*, inserts a fresh `UnconfiguredPipelineHandler.Inbound` under the name
`"inbound_config"`, and removes the decoder.
`ProtocolSwapHandler.handleOutboundTerminalPacket` does the equivalent on the
encoder side — but there is no incoming flow to stop, so it leaves auto-read
alone and simply puts an `UnconfiguredPipelineHandler.Outbound` in the
encoder's place. The bundler and unbundler remove themselves on the same
signal, and `PacketBundlePacker` treats a terminal packet arriving *inside* a
bundle as a decode error rather than a swap.
So a phase change reads: terminal packet, codecs self-destruct and inbound
reads stop, the game thread installs the new protocol, a configuration task
travels the pipeline in order with the byte stream, reads resume. The unnamed
flow-control handler between `"splitter"` and the decoder is what makes
turning auto-read off actually stop delivery mid-batch. Which phases exist,
and what ends each of them, is [protocol phases](protocol-phases.md).
The server's first listener is installed by
`Connection.setListenerForServerboundHandshake`, which refuses if one already
exists and refuses on a connection that is not receiving serverbound traffic in
the handshake protocol — so it is server-side by construction. The client has no
equivalent: its first listener arrives with the pair of protocols that
`Connection.initiateServerboundConnection` installs around
`ClientIntentionPacket`, and `Connection.initiateServerboundStatusConnection` is
the same code with a different intent.
## Compression and encryption arrive behind the packet that announces them
**Compression** is `Connection.setupCompression`, which inserts
`CompressionDecoder` after `"splitter"` and `CompressionEncoder` after
`"prepender"`, or re-thresholds them if they already exist; a negative
threshold removes both. The server turns it on during login, sending
`ClientboundLoginCompressionPacket` with a send-listener that installs the
handlers only *after* that packet is on the wire, and the client installs its
own side when it handles the packet. Both sides skip it entirely on a memory
connection. The asymmetry worth knowing is that the server validates that a
compressed frame really was above the threshold and the client does not; the
frame ceilings the two handlers enforce are in [packets and stream
codecs](packets-and-stream-codecs.md).
**Encryption** is `Connection.setEncryptionKey`, which inserts `CipherDecoder`
before `"splitter"` and `CipherEncoder` before `"prepender"` — so decryption is
the first thing that happens to inbound bytes and encryption the last thing
before outbound bytes leave. Neither is ever removed. The server installs its
ciphers synchronously the moment it handles the key packet; the client attaches
its own to the *send* of that packet, so the key packet itself goes out in the
clear and everything after it does not.
## How a connection dies
`Connection.exceptionCaught` is the funnel, and it has four outcomes.
- A `SkipPacketException` is **logged and swallowed**: the codec layer has
already decided this one packet may be dropped, and the connection survives.
It is the one marker that does not end the connection — an empty interface,
implemented by `SkipPacketDecoderException` and `SkipPacketEncoderException`,
one per direction.
- A timeout — the thirty-second read timeout expiring — disconnects with
*disconnect.timeout*, the "Timed out" a player sees.
- Any other fault, the **first** time: the listener is asked for a
`DisconnectionDetails` through `PacketListener.createDisconnectionInfo`; if
this end is the one sending clientbound traffic it tries to tell the peer
why, with either `ClientboundLoginDisconnectPacket` or
`ClientboundDisconnectPacket` depending on `Connection.sendLoginDisconnect`,
and disconnects once that packet has gone. `Connection.setReadOnly` is not
the last step but an immediate one, taken on both branches the moment the
write is handed over and long before it completes.
- Any other fault, the **second** time — a fault while handling a fault, which
`Connection.handlingFault` detects — skips all of that and disconnects
immediately.
`Connection.setReadOnly` is how a pending disconnect ignores the rest of the
stream: it turns auto-read off and leaves the peer's remaining packets unread.
`Connection.handleDisconnection` is the other end of the story. It runs from
`Connection.tick`, only once the channel is really closed, reports to the
listener — or to `Connection.disconnectListener`, the client's connect-attempt
fallback, if the connection never got a real one — and is guarded by
`Connection.disconnectionHandled` so that it reports exactly once.
**Keep-alive is the real timeout on a live connection.**
`ServerCommonPacketListenerImpl.keepConnectionAlive` sends a challenge every
fifteen seconds and
disconnects if the previous one was never answered, or if the answer carries
the wrong id, with an exemption for the singleplayer host. It stops sending
them once the listener has closed itself behind a terminal packet; from that
moment `ServerCommonPacketListenerImpl.checkIfClosed` gives the protocol swap
another fifteen seconds and then times the connection out. The thirty-second
read timeout exists only on socket connections, so the singleplayer host has
neither clock running against them: no read timeout on the in-memory pipeline,
and an exemption from the keep-alive.
## Questions players ask
**Which phase am I in?** `Connection` cannot tell you: there is no protocol
field and no getter. The answer is distributed between the two codec handlers
currently in the pipeline and the listener object, and every place
`Connection` names a `ConnectionProtocol` is a comparison rather than a
reading: `Connection.validateListener` checks a new listener against the
protocol it is being installed for, `Connection.setupOutboundProtocol` asks
only whether this is *login*, and the handshake entry point asks only whether
the listener is the initial one.
**Does login happen on the Netty thread or the game thread?** Both, and that is
the surprise. None of `ServerHandshakePacketListenerImpl`,
`ServerStatusPacketListenerImpl`, `ServerLoginPacketListenerImpl` or the
client's `ClientHandshakePacketListenerImpl` contains a single
`PacketUtils.ensureRunningOnSameThread` call, so every state transition and the
encryption setup happen on the event loop. But
`ServerLoginPacketListenerImpl` is a `TickablePacketListener`, and its tick —
on the server thread, through `Connection.tick` — is what runs the ban and
whitelist checks, switches compression on, and sends the terminal packet that
ends the phase. Login is a three-thread state machine; [protocol
phases](protocol-phases.md) walks it.
**Does the server drop packets when it is behind?** Not for being late.
`Connection` keeps no outbound packet queue at all — once the channel exists,
everything written goes into Netty's own buffer and backpressure is Netty's
water marks — and inbound, the `PacketProcessor`'s queue is unbounded and each
drain empties it. What a slow server costs you is latency, not messages, until
the keep-alive gives up.
**Why does kicking someone sometimes stall the caller?** Not for the obvious
reason. `Connection.disconnect` does block on the channel close, but the
deliberate kicks never call it from the game thread: `/kick` and the ban
commands go through `ServerCommonPacketListenerImpl.disconnect`, which defers
`Connection.disconnect` to a `PacketSendListener.thenRun` callback on the event
loop. What stalls the game thread is the next line —
`MinecraftServer.executeBlocking` running `Connection.handleDisconnection`,
so the kicking tick does not continue until the player has been removed. The
one place that does block on the close from a game thread is
`ServerLoginPacketListenerImpl.disconnect`, reached from that listener's tick.
**Is a flood of packets rate-limited?** Only if the server was configured for
it. `RateKickingConnection` overrides `Connection.tickSecond` to kick a client
whose average received-packet rate exceeds
`RateKickingConnection.rateLimitPacketsPerSecond`, and the two accept sites
build one only when the server's rate limit is above zero — which it is not by
default. An ordinary socket gets a plain `Connection`.
**Why does one client's bug crash a singleplayer world but not a server?**
`ServerConnectionListener.tick` catches a per-connection failure and kicks that
client with an internal-server-error message — unless
`Connection.isMemoryConnection`, in which case it raises a fresh reported crash
and takes the integrated server down.
**Why is the network graph in the debug screen client-side only?**
`Connection.bandwidthDebugMonitor` is inbound-only and socket-only, set from
`ConnectScreen` and the Realms connect path and nowhere else, so no server ever
has one. `MonitoredLocalFrameDecoder` exists for the singleplayer case and is
**never installed**: the local pipeline is always built with a null monitor.
That is the transport: one wire, two ends, a thread hop at each of them, and a
picture that does not change between singleplayer and a public server. What
actually
crosses — what a packet class has to declare, how its fields become bytes, how
the far side knows which class to build, and what stops a hostile sender from
allocating a gigabyte — is the other half of this lecture, [packets and stream
codecs](packets-and-stream-codecs.md).
## Where to look
`Connection` · `PacketListener` · `TickablePacketListener` ·
`PacketProcessor` · `PacketUtils` · `PacketSendListener` · `ProtocolInfo` ·
`UnconfiguredPipelineHandler` · `ProtocolSwapHandler` · `PacketDecoder` ·
`PacketEncoder` · `Varint21FrameDecoder` · `HandlerNames` ·
`CompressionDecoder` · `CipherBase` · `EventLoopGroupHolder` ·
`ServerConnectionListener` · `RateKickingConnection` · `DisconnectionDetails` ·
`ServerCommonPacketListenerImpl`
---
*Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes `tools/verify_names.py`.*
---
# Packets and stream codecs
> Verified against **Minecraft 26.2** · Part IX · Someone says hello in chat, and you stop the message on its way out of the server to ask what it actually is.
[The connection](the-connection.md) hands you a frame: a length, then a run
of bytes that a handler on a Netty thread is about to turn into a method
call on the other side. This page is what is inside that frame. A chat line
leaving the server is a `ClientboundSystemChatPacket` — a record of a
`Component` and a boolean — and it has no write method, no byte layout of
its own, and no number. The number that goes on the wire in front of it is
written down nowhere in the game: not on the packet, not on its
`PacketType`, not in any table a human maintains. **A packet's id is the
position of one line in a chain of registration calls.** Swap two of those
lines and the whole protocol renumbers — and because a handful of packet
types are registered into several phases, *the same packet type is a
different number in each phase it appears in*.
> **For a 1.21-era reader.** `Packet` no longer knows how to write itself.
> There is no *write* method on the interface and no buffer constructor it
> is obliged to have. Serialisation is a *STREAM_CODEC* static field that
> the protocol description reads — which is why one packet class can have
> two of them, and why a packet class need not own one at all.
## The cast
| class | what it decides | thread |
|---|---|---|
| `Packet` | that a message is a value with a type, one handler method and two flags — and nothing at all about bytes | any |
| `PacketType` | which message this is: a direction and a name, and no number | — |
| `StreamCodec` | one value's bytes — an encoder and a decoder over a `ByteBuf`, composed out of its fields' codecs | Netty |
| `ByteBufCodecs` | the primitive vocabulary every packet codec is built from, and where the read limits sit | Netty |
| `IdDispatchCodec` | one codec for a whole phase: a var-int id, then delegate to the entry that id names | Netty |
| `ProtocolInfo` | what a configured connection holds — the phase, the direction, that one codec, and the bundler | Netty |
| `ProtocolInfoBuilder` | the registration chain, and therefore every packet number in the game | class-load, or the configuration-to-play swap |
| `RegistryFriendlyByteBuf` | that a registry id on the wire means something, by carrying the `RegistryAccess` it is relative to | Netty |
The catalogue of *which* packets exist is generated, not written:
[reference/packets.md](../../reference/packets.md) lists all 232 packet
types across the eight `*PacketTypes` classes that declare them.
## A packet is a value, a name and a direction
`Packet` is an interface with four methods and one static helper.
| member | what it says |
|---|---|
| `Packet.type` | the `PacketType`, always a constant from one of the eight `*PacketTypes` classes |
| `Packet.handle` | hands this packet to one named method on the phase's listener interface |
| `Packet.isSkippable` | default false. True for the chat-shaped packets, so a failure to encode one is dropped rather than fatal |
| `Packet.isTerminal` | default false. True for the seven packets that end a protocol phase |
| `Packet.codec` | a static convenience: `StreamCodec.ofMember` under a friendlier name |
Exactly one packet refuses to be handled at all: `BundleDelimiterPacket`
makes `Packet.handle` final and throws, because a delimiter is consumed by
the pipeline and must never reach a listener. The skippable set is the five
chat-shaped packets — `ClientboundSystemChatPacket`,
`ClientboundPlayerChatPacket`, `ClientboundDisguisedChatPacket`,
`ClientboundPlayerCombatKillPacket` and `ClientboundTagQueryPacket`. The
terminal set is exactly the seven transition packets drawn on
[protocol phases](protocol-phases.md) and no others; what the flag *does*
to the pipeline is [the connection](the-connection.md)'s business. Beware
the namesake: `ServerboundResourcePackPacket.Action.isTerminal` asks
whether a resource-pack *response* is a final answer, and that packet is
not terminal.
**`PacketType` is a record of two things** — a `PacketFlow` and an
`Identifier`. A direction and a name; no number, no version, no size.
`PacketFlow` is the two-constant enum `PacketFlow.SERVERBOUND` /
`PacketFlow.CLIENTBOUND`, with `PacketFlow.getOpposite` and
`PacketFlow.id`.
Three shapes of packet class coexist in the tree, and two of them carry the
argument. The modern one is a record
whose *STREAM_CODEC* is a `StreamCodec.composite` naming each component's
codec and accessor — `ClientboundSystemChatPacket` is one. The older one is
a plain class with a private buffer constructor and a private write method,
joined into a codec by `Packet.codec`; `ServerboundSwingPacket`,
`ClientboundKeepAlivePacket` and `ClientboundSetHealthPacket` are these,
and `StreamMemberEncoder` exists chiefly so that second form can bind a
member reference as its encoder half — `CustomPacketPayload` is its one
other client. The third shape is the one with no fields to serialise at all:
a singleton whose codec is `StreamCodec.unit`, fourteen of them, of which
`ServerboundFinishConfigurationPacket` is the one this part's login trace
turns on.
## From two fields to a numbered blob
```mermaid
flowchart TB
subgraph V["the value"]
F1["content, a Component"]
F2["overlay, a boolean"]
end
subgraph C["one stream codec per component"]
SC1["ComponentSerialization.TRUSTED_STREAM_CODEC"]
SC2["ByteBufCodecs.BOOL"]
end
F1 --> SC1
F2 --> SC2
SC1 --> COMP["ClientboundSystemChatPacket.STREAM_CODEC, a StreamCodec.composite of two codec-and-getter pairs plus the constructor"]
SC2 --> COMP
COMP --> ENTRY["one addPacket call in GameProtocols.CLIENTBOUND_TEMPLATE, pairing that codec with GamePacketTypes.CLIENTBOUND_SYSTEM_CHAT"]
ENTRY --> WRAP["mapStream, applied when the protocol is bound, wraps every call in a fresh RegistryFriendlyByteBuf"]
WRAP --> DISP["IdDispatchCodec for the whole clientbound play phase. The id is this entry's index in the chain"]
DISP --> OUT["a VarInt id, then the two fields in argument order"]
```
Read it downwards and you have the page. **Nothing above the
`ProtocolInfoBuilder.addPacket` line knows anything about ids**, and
nothing below it knows anything about chat.
**One codec serves an entire phase.** `ProtocolInfo.codec` is a single
`StreamCodec` — an `IdDispatchCodec` — and not a table the encoder walks:
encoding looks the `PacketType` up in a map, and a type never registered
in *this* protocol is an encoder error naming the unknown packet, which is
how a configuration-phase packet sent during play fails. Decoding reads the
id, bounds-checks it and delegates — and then **must have consumed the
frame exactly**, or `PacketDecoder` raises an error naming how many bytes
were left over. An under-read corrupts nothing visible until much later, so
it is caught at the one point where the answer is still knowable. All of
that runs on the Netty event loop, inside `PacketEncoder` and
`PacketDecoder`, never on a game thread; the framing, the compression, the
ciphers and the later hop to the game thread are
[the connection](the-connection.md)'s.
## The codec layer is small, and composition is all of it
`net/minecraft/network/codec` is a handful of files and does all of the
composing; the byte-level encodings themselves live outside it, in `VarInt`,
`VarLong`, `Utf8String`, `LpVec3` and `FriendlyByteBuf`. `StreamCodec` is one interface extending `StreamEncoder` and
`StreamDecoder`, so `StreamEncoder.encode` takes a buffer and a value and
`StreamDecoder.decode` takes a buffer and returns one. It is deliberately
*not* a `Codec`: a packet is written once, read once and must be small, so
it gets hand-laid bytes rather than a document in some format — the
distinction is [codecs, NBT and
JSON](../foundations/codecs-nbt-json.md)'s.
The constructors and combinators are `StreamCodec.of`,
`StreamCodec.ofMember`, `StreamCodec.unit`, `StreamCodec.map`,
`StreamCodec.mapStream`, `StreamCodec.apply`, `StreamCodec.dispatch`,
`StreamCodec.recursive` and `StreamCodec.cast` — plus
**`StreamCodec.composite` in twelve arities**, one through twelve pairs of
codec and getter followed by a constructor. Fields encode and decode
strictly in argument order, and *that ordering is the format
specification*: there is no other statement anywhere of what a packet's
bytes look like. `StreamCodec.CodecOperation` lets `StreamCodec.apply` read
left to right, and `StreamCodec.dispatch` is how registry-dispatched values
travel — `ConsumeEffect.STREAM_CODEC`, `SlotDisplay.STREAM_CODEC` and
`RecipeDisplay.STREAM_CODEC` are built with it.
`ByteBufCodecs` is the primitive library underneath: SCREAMING\_CASE
constants for the fixed things, lowerCamel factories for the parameterised
ones.
| what | the names |
|---|---|
| numbers | `ByteBufCodecs.BOOL`, `ByteBufCodecs.BYTE`, `ByteBufCodecs.SHORT`, `ByteBufCodecs.UNSIGNED_SHORT`, `ByteBufCodecs.INT`, `ByteBufCodecs.VAR_INT`, `ByteBufCodecs.LONG`, `ByteBufCodecs.VAR_LONG`, `ByteBufCodecs.FLOAT`, `ByteBufCodecs.DOUBLE` |
| bytes and text | `ByteBufCodecs.BYTE_ARRAY`, `ByteBufCodecs.LONG_ARRAY`, `ByteBufCodecs.STRING_UTF8`, `ByteBufCodecs.byteArray`, `ByteBufCodecs.stringUtf8`, `ByteBufCodecs.PLAYER_NAME` |
| tags and JSON | `ByteBufCodecs.TAG`, `ByteBufCodecs.COMPOUND_TAG`, `ByteBufCodecs.TRUSTED_TAG`, `ByteBufCodecs.TRUSTED_COMPOUND_TAG`, `ByteBufCodecs.lenientJson` |
| shapes and identities | `ByteBufCodecs.VECTOR3F`, `ByteBufCodecs.QUATERNIONF`, `ByteBufCodecs.RGB_COLOR`, `ByteBufCodecs.CONTAINER_ID`, `ByteBufCodecs.GAME_PROFILE`, `ByteBufCodecs.GAME_PROFILE_PROPERTIES` |
| structure | `ByteBufCodecs.optional`, `ByteBufCodecs.collection`, `ByteBufCodecs.list`, `ByteBufCodecs.map`, `ByteBufCodecs.either`, `ByteBufCodecs.lengthPrefixed` |
| registries | `ByteBufCodecs.idMapper`, `ByteBufCodecs.registry`, `ByteBufCodecs.holder`, `ByteBufCodecs.holderRegistry`, `ByteBufCodecs.holderSet` |
Two are worth naming for what they encode rather than for what they are.
`ByteBufCodecs.ROTATION_BYTE` is one byte meaning 1/256 of a full turn — a
little over a degree, with `Mth.packDegrees` and `Mth.unpackDegrees` doing
the arithmetic — and `ByteBufCodecs.OPTIONAL_VAR_INT` spends zero for
absent and value-plus-one otherwise.
The bridge to the disk-and-JSON codecs of Part II is
`ByteBufCodecs.fromCodec` and its relatives, which run an ordinary `Codec`
into a carrier format and put the result on the wire. The combinator
underneath takes the ops as an argument and is format-agnostic, and its six
NBT entry points all pass `NbtOps`, so **a `Codec` on the wire almost always
means a compound tag**. Almost: the combinator is public, and two packets
call it with `JsonOps` and carry JSON strings instead — the server-list
response, `ClientboundStatusResponsePacket`, and the login kick,
`ClientboundLoginDisconnectPacket`. They are the two a player is likeliest
to have seen. Beneath it, `ByteBufCodecs.tagCodec` takes
an `NbtAccounter` supplier — which is exactly what *trusted* turns out to
mean, below.
`IdDispatchCodec` is the class that makes a protocol out of a pile of
codecs: a list of serialisers, a type-to-int map, a var-int written in
front and a delegation behind it. `IdDispatchCodec.DontDecorateException`
is the marker meaning *rethrow me as I am, do not wrap me*, and
`IdDispatchCodec.Builder.build` refuses a duplicate `PacketType` outright,
so registering one type twice in a protocol fails loudly rather than
silently shadowing an id. Loudly, but not always early: the check runs when
the template is bound, which for the four static phases is class-load and for
play is the first connection's configuration-to-play switch.
## Which buffer, and why play needs its own
```mermaid
flowchart LR
RAW["ByteBuf, what the pipeline hands the codec"] --> ID["status serverbound binds the identity function and never wraps at all"]
RAW --> FBB["FriendlyByteBuf for handshaking, status clientbound, login and configuration"]
FBB --> RFBB["RegistryFriendlyByteBuf for play, adding one field, a RegistryAccess"]
```
`FriendlyByteBuf` is a `ByteBuf` decorator declaring a hundred and fifty-two
readers and writers, a hundred and twenty-one of which add a wire format the
plain buffer knows nothing about — `FriendlyByteBuf.readVarInt`,
`FriendlyByteBuf.writeUtf`, `FriendlyByteBuf.readIdentifier`,
`FriendlyByteBuf.writeResourceKey`, `FriendlyByteBuf.readNbt`,
`FriendlyByteBuf.readCollection`, `FriendlyByteBuf.readEnumSet`,
`FriendlyByteBuf.readBlockPos`, `FriendlyByteBuf.readBlockHitResult`,
`FriendlyByteBuf.readWithCodec` and so on. It holds the two length
constants `FriendlyByteBuf.MAX_STRING_LENGTH` and
`FriendlyByteBuf.MAX_COMPONENT_STRING_LENGTH`, and also
`FriendlyByteBuf.limitValue`, the wrapper an old-style packet puts round a
collection constructor to get the cap `ByteBufCodecs.collection` gives for
free.
**`RegistryFriendlyByteBuf` extends it and adds exactly one field**, a
`RegistryAccess`, behind `RegistryFriendlyByteBuf.registryAccess`. It
exists because a numeric registry id means nothing on its own — item number
37 is only an item relative to the registry set the server sent during
configuration ([identifiers and
registries](../foundations/identifiers-and-registries.md)). Every codec
that writes a registry id needs one: `ByteBufCodecs.registry`,
`ByteBufCodecs.holderRegistry`, `ByteBufCodecs.holder`,
`ByteBufCodecs.holderSet`, `ByteBufCodecs.fromCodecWithRegistries` and
`ByteBufCodecs.registryFriendlyLengthPrefixed` — and therefore
`ItemStack.STREAM_CODEC`, `DataComponentPatch.STREAM_CODEC`
([data components](../foundations/data-components.md)),
`ComponentSerialization.STREAM_CODEC` and `HashedStack.STREAM_CODEC`. The
wrapper is a throwaway, not a pipeline object: `ProtocolInfoBuilder`
applies the decorator — `RegistryFriendlyByteBuf.decorator` for play —
through `StreamCodec.mapStream`, so a fresh view is constructed round the
raw buffer on every single encode and every single decode.
Below all of it the actual encodings live in `VarInt`, `VarLong`,
`Utf8String` and `LpVec3`, the quantised position behind
`Vec3.LP_STREAM_CODEC`. Two everyday values are worth naming because they
are *not* special-cased: `Identifier.STREAM_CODEC` is a plain UTF-8 string
under the ordinary 32,767-character cap — an identifier on the wire is
text, never an interned number — and `UUIDUtil.STREAM_CODEC` is two longs.
## Where a packet's number comes from
`ProtocolInfo` is what a configured connection actually holds:
`ProtocolInfo.id` (a `ConnectionProtocol`), `ProtocolInfo.flow`,
`ProtocolInfo.codec` — the single phase-wide `StreamCodec` — and a nullable
`ProtocolInfo.bundlerInfo`. It is built by `ProtocolInfoBuilder`, whose
`ProtocolInfoBuilder.addPacket` and `ProtocolInfoBuilder.withBundlePacket`
are the registration calls and whose `ProtocolInfoBuilder.buildUnbound`
yields an `UnboundProtocol` or a `SimpleUnboundProtocol` — a protocol that
knows everything except which buffer type to wrap the bytes in.
`UnboundProtocol.bind` supplies that. Underneath it, `ProtocolCodecBuilder`
is the layer that talks to `IdDispatchCodec`.
**The id is a registration index.** `ProtocolCodecBuilder.add` appends to a
list and `IdDispatchCodec.Builder.build` walks that list assigning 0, 1, 2
in call order, so a packet's wire number is literally its position in the
`ProtocolInfoBuilder.addPacket` chain in `GameProtocols`,
`ConfigurationProtocols`, `LoginProtocols`, `StatusProtocols` or
`HandshakeProtocols`. `ProtocolCodecBuilder.add` also asserts that the
type's `PacketFlow` matches the protocol's, so a clientbound type cannot be
registered into a serverbound protocol.
Those numbers are readable from outside for one reason.
`ProtocolInfo.DetailsProvider` and `ProtocolInfo.Details` let tooling
enumerate a phase, and `ProtocolInfo.Details.listPackets` hands a
`ProtocolInfo.Details.PacketVisitor` each `PacketType` with its network id.
The tooling is the data generator: `PacketReport` walks every template and
writes every packet's id in every phase into a report — the only place in
the project those numbers are written down.
There are four registration entry points, one per direction and per
context-or-not: `ProtocolInfoBuilder.serverboundProtocol`,
`ProtocolInfoBuilder.clientboundProtocol`,
`ProtocolInfoBuilder.contextServerboundProtocol` and
`ProtocolInfoBuilder.contextClientboundProtocol`. The context ones let a
codec ask the *connection* a question, and in 26.2 exactly one protocol
uses one: `GameProtocols.SERVERBOUND_TEMPLATE`, whose context is
`GameProtocols.Context` and whose only question is
`GameProtocols.Context.hasInfiniteMaterials`. Every other template is a
`SimpleUnboundProtocol`; `CodecModifier` is the hook a context-aware codec
is installed through.
**When the description is built** splits the same way. Handshaking, status,
login and configuration bind their buffers eagerly at class-load — and the
serverbound status protocol binds the identity function, so it is the one
phase and direction that never wraps its bytes at all. Play binds **per
connection**, at the configuration-to-play transition: on the client that
really is the first moment a `RegistryAccess` exists, while on the server
the registries have been loaded since startup and the rebind happens
because the protocol changed, not because the registries arrived.
## A bundle is two empty markers round ordinary packets
`ClientboundBundlePacket` has a `PacketType` and **no wire id at all**.
`ProtocolInfoBuilder.withBundlePacket` registers only the *delimiter*,
`ClientboundBundleDelimiterPacket`, serialised with `StreamCodec.unit` — an
id and a zero-byte body — and records a `BundlerInfo` beside the codec
list, so a bundle on the wire is two empty markers with ordinary,
individually numbered packets between them. `PacketBundleUnpacker` explodes
an outgoing bundle into delimiter-packets-delimiter and
`PacketBundlePacker` collects an incoming run back up; `BundlePacket` holds
the sub-packets and `BundlerInfo` the logic, split between
`BundlerInfo.unbundlePacket` outgoing and its nested `BundlerInfo.Bundler`
incoming, with
`BundlerInfo.BUNDLE_SIZE_LIMIT` caps a bundle at 4,096 sub-packets. Only
the clientbound play protocol declares a bundle at all.
**What a bundle buys is atomicity against the client's tick.**
`ClientPacketListener.handleBundlePacket` hops to the main thread once for
the whole bundle and then handles the sub-packets inline, so the client can
never tick or render with half a bundle applied. There are only two
senders, both in `ServerEntity`: `ServerEntity.addPairing`, which collects
what `ServerEntity.sendPairingData` writes into a list and sends the result
as one bundle, so an entity never appears mid-initialisation ([what the
client is told](what-the-client-is-told.md)); and the motion-plus-power
pair sent for a hurtling projectile.
## What stops a hostile sender
The frame limit does most of the work, and it is not on this page: a frame
length is at most three var-int bytes, and `Varint21FrameDecoder` refuses a
wider prefix or a zero length before any codec sees anything
([the connection](the-connection.md)). Above that sit two separate
collection defences, and only the second is famous.
`ByteBufCodecs.readCount` is the first — it compares the declared count
against the codec's own maximum and refuses outright, which is what makes
the three-argument `ByteBufCodecs.collection` different from the
two-argument one whose maximum is effectively unbounded. Behind it,
`ByteBufCodecs.MAX_INITIAL_COLLECTION_SIZE` clamps the *allocation* to
65,536 entries whatever the count says, so even an accepted count cannot
force a huge array up front. `ByteBufCodecs.lengthPrefixed` bounds the
bytes instead of the count, handing the inner codec a slice it physically
cannot read past.
*Trusted* is a real distinction in the codec library, and a statement about
the read budget rather than about direction:
`ByteBufCodecs.fromCodecTrusted` gives the NBT reader an unlimited heap
quota where plain `ByteBufCodecs.fromCodec` gives it the default. Hence the
pairs `ByteBufCodecs.TAG` / `ByteBufCodecs.TRUSTED_TAG`,
`ByteBufCodecs.COMPOUND_TAG` / `ByteBufCodecs.TRUSTED_COMPOUND_TAG` and
`ComponentSerialization.STREAM_CODEC` /
`ComponentSerialization.TRUSTED_STREAM_CODEC`. The rule is direction: the
server wrote it, so the client may trust it ([codecs, NBT and
JSON](../foundations/codecs-nbt-json.md) has the serverbound half).
**Exactly one packet lets a client hand the server an arbitrary item**, and
it is fenced three ways. `ServerboundSetCreativeModeSlotPacket` uses
`ItemStack.OPTIONAL_UNTRUSTED_STREAM_CODEC`, which differs from
`ItemStack.OPTIONAL_STREAM_CODEC` only in using
`DataComponentPatch.DELIMITED_STREAM_CODEC`:
every component's payload is length-prefixed, and
`ByteBufCodecs.lengthPrefixed` hands the inner codec a slice and advances
the outer reader past the whole region before delegating, so a component
that lies about its own length cannot mis-frame the ones after it. That is
containment, not recovery — nothing catches a component that throws, and
one bad component still fails the whole packet. It is then wrapped in
`ItemStack.validatedStreamCodec`, which re-encodes the decoded stack
against `NullOps` purely to collect its errors. And the registration
carries `GameProtocols.HAS_INFINITE_MATERIALS`, a `CodecModifier` that
refuses the packet whenever its context says the connection is not in
creative — a `SkipPacketDecoderException` one way and a
`SkipPacketEncoderException` the other, landing on one side only because
the client's context answers `GameProtocols.Context.hasInfiniteMaterials`
true unconditionally while the server's answers from the real player. The
packet is refused **in the decoder**, before any handler exists to fool.
The ordinary container click is defended by carrying nothing to validate.
`ServerboundContainerClickPacket` sends a `HashedStack` — either
`HashedStack.EMPTY` or `HashedStack.ActualItem`, with an item holder, a
count and a `HashedPatchMap`, itself two halves rather than one:
`HashedPatchMap.addedComponents`, a map of component type to a hash, and
`HashedPatchMap.removedComponents`, a bare set. A removal is as much a part
of the claim as an addition, and `HashedStack.matches` checks both.
Client-supplied component *contents* never cross the wire at all; see
[containers and menus](../items/containers-and-menus.md).
The limits in one table:
| limit | value | where |
|---|---|---|
| frame length prefix | three var-int bytes | `Varint21FrameDecoder` |
| compressed frame | 2 MiB | `CompressionDecoder.MAXIMUM_COMPRESSED_LENGTH` |
| decompressed frame | 8 MiB | `CompressionDecoder.MAXIMUM_UNCOMPRESSED_LENGTH` |
| default string | 32,767 chars | `FriendlyByteBuf.MAX_STRING_LENGTH` |
| component as string | 262,144 | `FriendlyByteBuf.MAX_COMPONENT_STRING_LENGTH` |
| player name | 16 | `ByteBufCodecs.PLAYER_NAME` |
| collection allocation | 65,536 | `ByteBufCodecs.MAX_INITIAL_COLLECTION_SIZE` |
| sub-packets in a bundle | 4,096 | `BundlerInfo.BUNDLE_SIZE_LIMIT` |
| slots in one click | 128 | `ServerboundContainerClickPacket.MAX_SLOT_COUNT` (named, but the codec passes the literal) |
| var-int / var-long | 5 / 10 bytes | `VarInt.read`, `VarLong.read` |
## Custom payloads, the only extension point
The packet set is code, fixed at compile time, and no data pack adds to it.
The one seam is `CustomPacketPayload`, with `CustomPacketPayload.Type`,
`CustomPacketPayload.createType` and `CustomPacketPayload.codec`, carried
by `ClientboundCustomPayloadPacket` and `ServerboundCustomPayloadPacket`.
The route in is `CustomPacketPayload.FallbackProvider`, the codec handed to
the dispatch as the map miss, with `CustomPacketPayload.TypeAndCodec` as
the registration pair; `BrandPayload` is vanilla's own use of the
mechanism.
An unrecognised payload decodes to `DiscardedPayload` — a record of the
identifier and **nothing else**. Its decoder checks the remaining length
against a per-direction maximum and then skips every byte, and its encoder
writes nothing. The payload is discarded, not held.
## Questions players ask
**Why do packet ids move — between versions, and between phases?** Because
nobody chose them. They are indexes into a registration chain, so inserting
one line in `GameProtocols` shifts every packet declared after it, and the
*common*, *cookie* and *ping* types, registered into several phases, count
from zero again in each. A `PacketType` is a name and a direction; a number
is a property of a phase, not of a type — which is why nothing in the game
reads a packet number as a constant, and why `PacketReport` is the only
place they are ever written out.
**Why can one packet class have several codecs?**
`ClientboundCustomPayloadPacket` declares
`ClientboundCustomPayloadPacket.GAMEPLAY_STREAM_CODEC` and
`ClientboundCustomPayloadPacket.CONFIG_STREAM_CODEC`;
`ClientboundShowDialogPacket` declares
`ClientboundShowDialogPacket.STREAM_CODEC` and
`ClientboundShowDialogPacket.CONTEXT_FREE_STREAM_CODEC`. The same
`PacketType` is registered with the first in `GameProtocols` and with the
second in `ConfigurationProtocols`. Type and encoding are genuinely
separate things.
**Why does a malformed chat message not disconnect me?** Because
`SkipPacketException` is a marker and the skip is a decision two layers up.
`SkipPacketEncoderException` and `SkipPacketDecoderException` implement it
and `IdDispatchCodec.DontDecorateException`; `PacketEncoder` turns a
failure on a `Packet.isSkippable` packet into one; `PacketDecoder` drains
the rest of the frame and **rethrows**, because the frame was already
delimited and nothing was ever misaligned, so the drain only satisfies
Netty's bookkeeping. What keeps the connection alive is
`Connection.exceptionCaught` logging it and returning
([the connection](the-connection.md)).
**Is a modern packet safer than an old one?** Measurably.
`FriendlyByteBuf.readCollection` applies its constructor to the raw decoded
count with no cap of its own, so only the frame limit bounds it, where
`ByteBufCodecs.collection` refuses the count and then clamps the
allocation. The hand-written shape is still in the tree, and
`FriendlyByteBuf.limitValue` is what one of those must remember to use.
Which packets a phase registers, and in what order, is therefore the whole
definition of that phase — so the next page is [protocol
phases](protocol-phases.md), where nine codec tables — one per direction per
phase, bar handshaking's serverbound-only one — become the four languages a
joining connection speaks in turn.
## Where to look
`Packet` · `PacketType` · `PacketFlow` · `StreamCodec` · `ByteBufCodecs` ·
`IdDispatchCodec` · `ProtocolInfo` · `ProtocolInfoBuilder` ·
`ProtocolCodecBuilder` · `GameProtocols` · `GamePacketTypes` ·
`FriendlyByteBuf` · `RegistryFriendlyByteBuf` · `VarInt` · `BundlerInfo` ·
`PacketBundlePacker` · `CustomPacketPayload` · `HashedStack` ·
`PacketReport`
---
*Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes `tools/verify_names.py`.*
---
# Protocol phases
> Verified against **Minecraft 26.2** · Part IX · A login: from clicking a server in the list to standing in the world.
Click a server in the multiplayer list and one TCP connection opens, and
over the next second it speaks four different languages in turn. Each is a
`ConnectionProtocol`; each has its own packet set, and all but handshaking a
listener at both ends — handshaking is serverbound only, so the client has
nothing to listen with; and each hands over to the next by a packet marked *terminal*,
which tears its own codec out of the pipeline as it passes. What a
1.21-era reader will not expect is where the work happens. Every
server-side handler in the handshake and login phases runs on the Netty
event loop, and the thing that actually advances a login is a **tick**. And
the `ServerPlayer` — the object, its save data, its position, the chunks
under it — is *prepared* during configuration, by a task named for it, and
**constructed after the client has already acknowledged that configuration
is over**, by which point the server is encoding play packets to a player
that does not yet exist.
## The cast
| class | role | thread |
|---|---|---|
| `ConnectionProtocol` | the five phases — a bare enum of labels; the codec lives in a `ProtocolInfo`, the behaviour in a `PacketListener` | — |
| `Connection` | one channel, and `Connection.setupInboundProtocol` / `Connection.setupOutboundProtocol` at every transition | Netty |
| `ServerHandshakePacketListenerImpl` | the three-way switch and the version gate | Netty |
| `ServerLoginPacketListenerImpl` | the login state machine; its handlers set a volatile state and its `ServerLoginPacketListenerImpl.tick` acts on it | Netty, ticked from Server |
| `ServerConfigurationPacketListenerImpl` | the serial task queue, and the handler that finally builds the player | mixed — see below |
| `ClientHandshakePacketListenerImpl` | the client's side of handshake and login, including the session-service call | Netty, client IO pool |
| `ClientConfigurationPacketListenerImpl` | accumulates registries and tags, then constructs the `ClientPacketListener` | Netty, then Render |
| `PrepareSpawnTask` | finds a spawn, tickets its chunks, waits — and later, on request, spawns the player | Server |
## The five phases
```mermaid
stateDiagram-v2
direction LR
[*] --> HANDSHAKING : TCP accept
HANDSHAKING --> STATUS : ClientIntentionPacket, intent STATUS
HANDSHAKING --> LOGIN : ClientIntentionPacket, intent LOGIN or TRANSFER
STATUS --> [*] : pong, then the server hangs up
LOGIN --> CONFIGURATION : ClientboundLoginFinishedPacket, ServerboundLoginAcknowledgedPacket
CONFIGURATION --> PLAY : ClientboundFinishConfigurationPacket, ServerboundFinishConfigurationPacket
PLAY --> CONFIGURATION : ClientboundStartConfigurationPacket, ServerboundConfigurationAcknowledgedPacket
PLAY --> [*] : disconnect
note right of HANDSHAKING : every transition packet is terminal, so the codec that decoded it is already gone
```
`ConnectionProtocol` is five constants — `ConnectionProtocol.HANDSHAKING`,
`ConnectionProtocol.STATUS`, `ConnectionProtocol.LOGIN`,
`ConnectionProtocol.CONFIGURATION`, `ConnectionProtocol.PLAY` — each
carrying only a string `ConnectionProtocol.id`. There is no number, no
packet table and no lookup by id. What a phase *is* lives in two other
places: the packet set, bound as a `ProtocolInfo`, and the listener that
handles it.
| phase | serverbound listener | clientbound listener | `ProtocolInfo` |
|---|---|---|---|
| `ConnectionProtocol.HANDSHAKING` | `ServerHandshakePacketListenerImpl`, or `MemoryServerHandshakePacketListenerImpl` in singleplayer | none — there is no clientbound handshake protocol | `HandshakeProtocols.SERVERBOUND`, one packet |
| `ConnectionProtocol.STATUS` | `ServerStatusPacketListenerImpl` | reached from `ServerStatusPinger` | `StatusProtocols.SERVERBOUND` binds a **raw buffer**; `StatusProtocols.CLIENTBOUND` a `FriendlyByteBuf` |
| `ConnectionProtocol.LOGIN` | `ServerLoginPacketListenerImpl` | `ClientHandshakePacketListenerImpl` | `LoginProtocols.SERVERBOUND` / `LoginProtocols.CLIENTBOUND` |
| `ConnectionProtocol.CONFIGURATION` | `ServerConfigurationPacketListenerImpl` | `ClientConfigurationPacketListenerImpl` | `ConfigurationProtocols.SERVERBOUND` / `ConfigurationProtocols.CLIENTBOUND` |
| `ConnectionProtocol.PLAY` | `ServerGamePacketListenerImpl` | `ClientPacketListener` | **not pre-bound** — `GameProtocols.SERVERBOUND_TEMPLATE` and `GameProtocols.CLIENTBOUND_TEMPLATE` are bound per connection |
The bindings in the last column are the per-phase codec tables that
[packets and stream codecs](packets-and-stream-codecs.md) builds, and the
swap itself is the pipeline surgery [the connection](the-connection.md)
performs; this page is what the swaps are *for*. The first four bind their
buffers once, at class load, because they need no registries. Play cannot: its codecs write registry ids, so both templates are
bound per connection with `RegistryFriendlyByteBuf.decorator` at the
configuration-to-play switch. On the client that is genuinely the first
moment a `RegistryAccess` exists; on the server the registries have been
there since startup and the rebind is only because the protocol changed.
The serverbound play template is also the one protocol with a context
object, `GameProtocols.Context`, whose single question is
`GameProtocols.Context.hasInfiniteMaterials` — which is why it is an
`UnboundProtocol` where the other eight templates are a
`SimpleUnboundProtocol`. Eight, not four: every phase but handshaking
declares one per direction.
Two listeners sit under the phases. `ServerCommonPacketListenerImpl` is the
shared base of the server's configuration and play listeners and holds
everything legal in both — keep-alive, latency, custom payloads,
resource-pack responses and the flush suspension — with
`ClientCommonPacketListenerImpl` its client counterpart; that inheritance
is why the *common* packets in [reference/packets.md](../../reference/packets.md)
belong to no single phase. And the state that crosses a phase change is a
`CommonListenerCookie`: on the server a small record of profile, latency,
client information and a transferred flag; on the client a much larger one
carrying registries, feature flags, cookies, chat state and the server
brand.
## Handshake
The handshake is one packet and a three-way switch. `ClientIntentionPacket`
carries the protocol version, the address the client dialled and a
`ClientIntent`, and `ServerHandshakePacketListenerImpl.handleIntention`
branches on it. `ClientIntent.STATUS` installs the status listener, or
disconnects at once if the server does not reply to status.
`ClientIntent.TRANSFER` disconnects if the server does not accept transfers,
and otherwise joins `ClientIntent.LOGIN` in
`ServerHandshakePacketListenerImpl.beginLogin`, which compares the client's
protocol version against this build's and refuses a mismatch —
*outdated_client* below the 1.16.4 protocol number, *incompatible* above
it. The two refusals on the login path first install the **login** clientbound
protocol, purely so they can send `ClientboundLoginDisconnectPacket` and have
the client render a reason. The status refusal is the exception and the
rudest: the status clientbound protocol is installed before the branch, and
then the connection is dropped with no packet at all.
The client does not wait for any of that. Whichever of its three entry
points opened the connection — `ConnectScreen` for a listed or direct
server, `Minecraft` for the integrated server's memory channel,
`RealmsConnect` — it sends `ServerboundHelloPacket` immediately after the
intention packet; there is no round trip between them. The profile id the hello carries is decoded and then never read — the
server mints or fetches identity for itself. All of this runs on the Netty
thread, and the intention packet, being terminal, has already torn out the
codec that decoded it.
## Status, the phase nobody logs in through
`ConnectionProtocol.STATUS` is two packets each way and a deliberate dead
end. `ServerStatusPacketListenerImpl` answers exactly one
`ServerboundStatusRequestPacket` — a second one disconnects the caller —
and answers `ServerboundPingRequestPacket` with
`ClientboundPongResponsePacket` and then **hangs up**. A status connection
is expected to be thrown away, which is why `ServerStatusPinger` opens one
per server in the list and why `Connection.initiateServerboundStatusConnection`
exists as a separate entry point.
## Login
```mermaid
stateDiagram-v2
direction LR
[*] --> HELLO
HELLO --> KEY : online mode over a socket, ClientboundHelloPacket sent
HELLO --> VERIFYING : singleplayer profile, or offline mode
KEY --> AUTHENTICATING : ServerboundKeyPacket, ciphers installed now
AUTHENTICATING --> VERIFYING : the User Authenticator thread stores the profile
VERIFYING --> WAITING_FOR_DUPE_DISCONNECT : tick, a player with this profile is still in the world
VERIFYING --> PROTOCOL_SWITCHING : tick, bans and whitelist pass, ClientboundLoginFinishedPacket sent
WAITING_FOR_DUPE_DISCONNECT --> PROTOCOL_SWITCHING : tick, the old connection is gone
PROTOCOL_SWITCHING --> ACCEPTED : ServerboundLoginAcknowledgedPacket, configuration begins
NEGOTIATING : NEGOTIATING, declared and never assigned
note left of VERIFYING : the three tick transitions are the server-thread work that advances the login
```
`ServerLoginPacketListenerImpl` has no thread hop anywhere, which is why its
`ServerLoginPacketListenerImpl.state` field is volatile: the packet handlers
run on the Netty thread and set the state, and
`ServerLoginPacketListenerImpl.tick` — reached from
`MinecraftServer.tickConnection` through `ServerConnectionListener.tick`
and `Connection.tick` — reads it on the server thread and does the login.
The tick is also where `ServerLoginPacketListenerImpl.MAX_TICKS_BEFORE_LOGIN`,
six hundred ticks, is enforced: a client that has not reached the end of
the phase in thirty seconds is disconnected for a slow login.
**Three branches out of the hello.** If the name matches the singleplayer
profile, verification starts at once with no encryption. If the server uses
authentication and this is not a memory connection, the state becomes
`ServerLoginPacketListenerImpl.State.KEY` and `ClientboundHelloPacket`
carries the server's RSA public key and a four-byte challenge. Otherwise —
offline mode — the profile is minted from the name by
`UUIDUtil.createOfflineProfile` and nothing is encrypted.
**Both sides authenticate, and the client goes first.**
```mermaid
sequenceDiagram
participant CHPL as ClientHandshakePacketListenerImpl
participant SLPL as ServerLoginPacketListenerImpl
participant Auth as User Authenticator thread
SLPL->>CHPL: ClientboundHelloPacket, RSA public key and a four-byte challenge
CHPL->>CHPL: generate the AES secret, digest over server id, secret and key
CHPL->>Auth: joinServer on the client IO pool, before the key packet is sent
CHPL->>SLPL: ServerboundKeyPacket, secret and challenge RSA-encrypted, ciphers attached to the send
SLPL->>SLPL: validate the challenge, recover the secret, Connection.setEncryptionKey now
SLPL->>Auth: hasJoinedServer on a fresh thread named for user authentication
Auth-->>SLPL: the authenticated profile, state VERIFYING
Note over SLPL: the next server tick runs bans, whitelist, compression, duplicates
```
The client generates the AES secret and computes a digest over the server
id, the secret and the server's public key; if the server asked for
authentication it calls the session service on its IO pool *before* the key
packet goes anywhere, and sends `ServerboundKeyPacket` from that callback,
attaching its own ciphers to the send. The server validates the challenge,
recovers the secret, recomputes the digest, installs its ciphers
*immediately and synchronously*, and only then starts its own
session-service call. An unauthenticated connection is already encrypted.
**Authentication is a plain thread with two fallbacks.** It calls the
session service, reports login activity and on success stores the profile
and flips the state to `ServerLoginPacketListenerImpl.State.VERIFYING`. On
failure it disconnects — unless the server is a singleplayer host, in which
case both a null result and an unreachable authentication service fall back
to an offline profile. That is how a LAN world admits a guest whose account
cannot be checked. A login over a real socket against an authenticating
server starts exactly one such thread; an offline-mode, memory or
singleplayer-profile login starts none.
**The tick does the real login.**
`ServerLoginPacketListenerImpl.verifyLoginAndFinishConnectionSetup` runs on
the server thread: `PlayerList.canPlayerLogin` for bans, whitelist and
capacity; the compression switch; and
`PlayerList.disconnectAllPlayersWithProfile` for a duplicate login, after
which the machine waits in
`ServerLoginPacketListenerImpl.State.WAITING_FOR_DUPE_DISCONNECT` until the
old connection has actually died. It also compares the authenticated profile
against `Connection.getIntendedProfileId`, which is set in exactly one place,
`ServerConnectionListener.acceptChannel` — and nothing in the tree calls
that, so it is an embedder's hook.
**Login ends with a terminal packet in each direction**, and the two sides
install their codecs in mirror order. `ClientboundLoginFinishedPacket` then
`ServerboundLoginAcknowledgedPacket` — but the server installs *outbound*
configuration when the acknowledgement arrives, in
`ServerLoginPacketListenerImpl.handleLoginAcknowledgement`, whereas the
client installs *inbound* configuration before sending it and outbound
immediately after. Both packets are terminal, so the codecs tear themselves
out as they pass ([the connection](the-connection.md)). The client then
volunteers two packets straight away: its own `BrandPayload` and
`ServerboundClientInformationPacket`, which is where the server learns the
language it will pick a code of conduct in.
What disconnects a login: a version mismatch, a ban, a full whitelist-only
server, a failed session check on a non-singleplayer host, an unexpected
custom-query answer (`ServerLoginPacketListenerImpl.State.NEGOTIATING` is
declared and never assigned — `ClientboundCustomQueryPacket` decodes every
payload as `DiscardedQueryPayload`, and an answer just disconnects), or six
hundred ticks.
## Configuration
```mermaid
flowchart LR
S["startConfiguration: BrandPayload, server links, enabled features, outside the queue"] --> R["SynchronizeRegistriesTask"]
R --> C["ServerCodeOfConductConfigurationTask, if the server has one"]
C --> P["ServerResourcePackConfigurationTask, if the server has one"]
P --> W["returnToWorld appends the last two"]
W --> PS["PrepareSpawnTask: Preparing, then Ready"]
PS --> J["JoinWorldTask sends ClientboundFinishConfigurationPacket, terminal"]
J --> F["handleConfigurationFinished: outbound play, the gate again, then spawnPlayer"]
```
`SynchronizeRegistriesTask` is the reason configuration exists, and the
queue around it is strictly serial. `ServerConfigurationPacketListenerImpl.startConfiguration`
sends three things outside the queue — the server's `BrandPayload`,
`ClientboundServerLinksPacket` if there are links, and
`ClientboundUpdateEnabledFeaturesPacket` — then queues the registry task, a
code-of-conduct task if the server has one and a resource-pack task if it
has one, before `ServerConfigurationPacketListenerImpl.returnToWorld`
appends `PrepareSpawnTask` and `JoinWorldTask` and starts the first. Each
`ConfigurationTask` finishes before the next begins:
`ServerConfigurationPacketListenerImpl.finishCurrentTask` rejects a
completion naming the wrong task type, and an exception out of any task
disconnects the client.
**Registry and tag sync.** The task begins with `ClientboundSelectKnownPacks`,
a list of `KnownPack` records naming, by namespace, id and version, those of
the server's packs that declare one — `PackLocationInfo.knownPackInfo` is an
optional, so a world's own datapack is simply absent from the request; the client matches them against
its bundled vanilla repository through `KnownPacksManager.trySelectingPacks`
and replies with the subset it recognises. If that reply is not *exactly*
the requested list — same packs, same order — the server discards the
negotiation and re-sends everything; it is all or nothing, never a per-pack
intersection. Then one `ClientboundRegistryDataPacket` **per registry**,
walking `RegistryDataLoader.SYNCHRONIZED_REGISTRIES`, each element a
`RegistrySynchronization.PackedRegistryEntry` whose data is omitted when the
element came from a pack the client already has — the entire point of the
negotiation — written as NBT with the registry's own element codec. Then
one `ClientboundUpdateTagsPacket` covering the **networkable** registries and
the static ones too — the surprise, since the data packets are dynamic-only —
with empty payloads dropped ([tags](../foundations/tags.md)). On the client,
`RegistryDataCollector` accumulates the contents and the tags and only
resolves them at `ClientConfigurationPacketListenerImpl.handleConfigurationFinished`,
loading them on a background executor against the negotiated packs — and
blocking on the result, so the load is dispatched away rather than genuinely
asynchronous — before constructing the `ClientPacketListener` with the
finished `RegistryAccess`. In singleplayer the result is narrowed to the
server's own objects, so both sides share instances.
**The seam is not where it looks.** Four of the server's configuration
handlers hop to the main thread —
`ServerConfigurationPacketListenerImpl.handleSelectKnownPacks`,
`ServerConfigurationPacketListenerImpl.handleConfigurationFinished` and, from
the common base, the resource-pack response and
`ServerCommonPacketListenerImpl.handleCustomClickAction` —
while the client-information and code-of-conduct handlers, like the
keep-alive and ping handlers they inherit, stay on the Netty thread. That
last one is load-bearing: accepting a code of conduct finishes a task, which
**starts the next task on the Netty thread**, and the next task may be
`PrepareSpawnTask`, whose first act is to read a player save file and
resolve a spawn position. `ServerConfigurationPacketListenerImpl.tick` runs
each tick to drive the current task and keep the spawn chunks loaded.
**`PrepareSpawnTask` is two states, and the player is born in neither.**
Its `PrepareSpawnTask.Preparing` state reads the save file for a stored
position, resolves a level, runs `PlayerSpawnFinder.findSpawn`, takes a
`TicketType.PLAYER_SPAWN` ticket at `PrepareSpawnTask.PREPARE_CHUNK_RADIUS`
and waits — which is what `ConfigurationTask.tick` exists for. When the
chunks arrive it becomes `PrepareSpawnTask.Ready`, reports itself finished
and does nothing further except re-arm the ticket every tick through
`PrepareSpawnTask.keepAlive`. `JoinWorldTask` then sends the terminal
packet. Only when the *client's* `ServerboundFinishConfigurationPacket`
arrives does `ServerConfigurationPacketListenerImpl.handleConfigurationFinished`
swap the outbound protocol to play, re-run the duplicate-player check and
`PlayerList.canPlayerLogin` — because a ban or a full server can arrive in
the seconds a configuration takes — and call `PrepareSpawnTask.spawnPlayer`,
which constructs the `ServerPlayer`, reads the save data into it — a second
read, the first having happened when the task started — and hands it to
`PlayerList.placeNewPlayer`, which installs the inbound play protocol. [Players and sessions](../server/players-and-sessions.md) owns the
rest of that story. Everything between the join task and that handler is a
server holding a ticket on chunks for a player that does not exist.
What disconnects a configuration: a task that throws on start or on tick, a
completion for the wrong task, a ban or a full server at the second check,
and an exception while placing the player.
## Play, and the way back
The play protocol is installed from two different places on each side: the
server swaps outbound a line into the finish handler and inbound inside
`PlayerList.placeNewPlayer`; the client swaps inbound, sends the finish
packet, then swaps outbound. That is why the server can be encoding play
packets while still nominally in the configuration listener. Chat session
keys are not part of any of this: they are negotiated in play, after the
client learns the server's mode from `ClientboundLoginPacket`
([chat and signing](chat-and-signing.md)).
**A reconfigure does not re-run configuration.**
`ServerGamePacketListenerImpl.switchToConfig` removes the player from the
world, sends `ClientboundStartConfigurationPacket` and swaps outbound; the
client's `ClientPacketListener.handleConfigurationStart` flushes its chat
queue, stashes the chat state and carries the registry access, feature
flags, brand and server links forward in a rebuilt cookie, then answers
`ServerboundConfigurationAcknowledgedPacket`; and
`ServerGamePacketListenerImpl.handleConfigurationAcknowledged` installs a
configuration listener **without** calling
`ServerConfigurationPacketListenerImpl.startConfiguration`. No registries,
tags, feature flags or brand are re-sent, and none need to be. The player
parks in configuration with an empty queue until
`ServerConfigurationPacketListenerImpl.returnToWorld` re-queues the spawn
and join tasks. Both directions are reachable in vanilla only from
`DebugConfigCommand`.
## What the phases leave unused
**Seven packets are terminal**, and four of them are the two handshakes that
bracket configuration; `ClientIntentionPacket` is one of the seven, so the
very first packet of a connection already tears out the codec that decoded
it.
**The creative-inventory packet is filtered at the codec, asymmetrically.**
`GameProtocols.HAS_INFINITE_MATERIALS` refuses to encode or decode the
packet when its context says the connection is not in creative — but the
client's own context answers `GameProtocols.Context.hasInfiniteMaterials`
true unconditionally, while the server's is `ServerGamePacketListenerImpl`
answering from the real player, so the symmetric modifier bites on exactly
one side ([packets and stream codecs](packets-and-stream-codecs.md)).
Compression is asymmetric the same way: the server validates that a
compressed frame really was above the threshold; the client does not.
**Cookies, transfers and the chat reset are proxy hooks.** A
`ServerboundCookieResponsePacket` arriving at any server listener is an
unexpected query and a disconnect, and nothing in the tree constructs
`ClientboundStoreCookiePacket` or `ClientboundCookieRequestPacket`. Of the
transfer machinery only `ClientboundTransferPacket` has a vanilla caller,
`/transfer`: it sends the client to another server, which sees a
`ClientIntent.TRANSFER` handshake and a transferred flag in its cookie, and
`ClientCommonPacketListenerImpl.shouldHandleMessage` keeps accepting
store-cookie and transfer packets while a transfer is in flight, which is
what lets a proxy's trailing state land. `ClientboundResetChatPacket` is
registered and handled and never sent. The rest is fully implemented on the
client and unused by the server.
> **For a 1.21-era reader.** The assumption that "packet handlers run on
> the game thread" is exactly backwards for the first two phases: the
> handshake and login listeners run to completion on the Netty thread, and
> the first `PacketUtils.ensureRunningOnSameThread` in a connection's life
> is in configuration. The connection is encrypted before it is
> authenticated — the server installs both ciphers while handling the key
> packet, before its own session-service call has begun.
## Where to look
`ConnectionProtocol` · `ProtocolInfo` · `ProtocolInfoBuilder` ·
`ServerHandshakePacketListenerImpl` · `ServerStatusPacketListenerImpl` ·
`ServerLoginPacketListenerImpl` · `ServerConfigurationPacketListenerImpl`
· `ClientHandshakePacketListenerImpl` ·
`ClientConfigurationPacketListenerImpl` · `ConfigurationTask` ·
`SynchronizeRegistriesTask` · `PrepareSpawnTask` · `JoinWorldTask` ·
`RegistrySynchronization` · `KnownPack` · `Crypt` ·
`ServerCommonPacketListenerImpl` · `CommonListenerCookie`
---
*Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes `tools/verify_names.py`.*
---
# What the client is told
> Verified against **Minecraft 26.2** · Part IX · a creeper walks into view: everything the server decides to say, in order, and everything it decides not to.
A creeper three chunks away steps across a section boundary, and inside that
tick the server decides a player may know about it. One bundled packet goes
out, and the position in that packet is not where the creeper is. It is where
the creeper's tracker last *said* it was — stale by at least the entity's
update interval and, for an entity sitting in a chunk that is loaded but not
ticking, stale by no bounded amount at all. That is not a bug being
tolerated. Every viewer has to start dead reckoning from an identical base,
so **the server sends the base rather than the truth**, and the rest of this
page is that trade made over and over: which chunks a player is sent and how
fast, which entities they are told about, and what counts as a change worth
a packet.
## The cast
| class | what it decides | thread |
|---|---|---|
| `ChunkMap` | which players can see which chunks and which entities — it owns both maps | Server |
| `ChunkMap.TrackedEntity` | one entity's audience, `ChunkMap.TrackedEntity.seenBy`, and the range test that fills it | Server |
| `ServerEntity` | the change detector: the dead-reckoning baseline, the interval gate, the packet shape | Server |
| `ChunkTrackingView` | which chunks a player has been sent, and what a movement turns into | Server |
| `PlayerChunkSender` | how many chunks leave for one player this tick | Server |
| `ChunkHolder` | the per-chunk block-change batch, one flush a tick | Server |
| `EntityType` | the per-type tracking range, update interval and delta exclusion | static, from the builder |
| `ChunkBatchSizeCalculator` | the client's answer — how many chunks a tick it wants | client Netty thread, and it never hops |
## One entity's tick, and the gates it does not pass
```mermaid
flowchart TD
TICK["ChunkMap.tick walks every tracked entity, in the chunk-source phase, before entities tick"] --> SEC{"did the entity change section"}
SEC -- yes --> UP["ChunkMap.TrackedEntity.updatePlayer, once per player in the level"]
UP --> G1{"gate 1: three conjuncts, all required"}
G1 -- "horizontal range, and Entity.broadcastToPlayer, and ChunkMap.isChunkTracked" --> IN["seenBy gains the connection: ServerEntity.addPairing sends the introduction bundle"]
G1 -- "any one false" --> OUT["seenBy loses it: ClientboundRemoveEntitiesPacket"]
SEC -- no --> G2{"gate 2: is the change detector called at all"}
IN --> G2
OUT --> G2
G2 -- "changed section, or Entity.needsSync, or the chunk is in entity-ticking range" --> SC["ServerEntity.sendChanges"]
G2 -- "none of the three" --> MUTE["nothing, and the call counter does not advance"]
SC --> FREE["past gate 3 only: a changed passenger list, an item frame every tenth call, and Entity.hurtMarked knockback"]
SC --> G3{"gate 3: three disjuncts, any one opens it"}
G3 -- "the call count is a multiple of EntityType.updateInterval, or Entity.needsSync, or the synched data is dirty" --> D["three decisions"]
G3 -- "none of the three" --> WAIT["wait for a later call"]
D --> D1{"relative or absolute"}
D1 -- "the delta fits a short, precision is not demanded, the ground flag held, it was not riding, and the teleport delay is within ServerEntity.FORCED_TELEPORT_PERIOD" --> REL["ClientboundMoveEntityPacket.Pos, .Rot or .PosRot"]
D1 -- otherwise --> ABS["ClientboundEntityPositionSyncPacket, and the teleport delay resets"]
D --> D2["head yaw: its own ClientboundRotateHeadPacket, whenever it moved by a byte"]
D --> D3["velocity: ClientboundSetEntityMotionPacket, only for a tracked-delta type, a needsSync, or an elytra flight"]
```
The figure is the page. Three gates stand between an entity moving and a
player hearing about it, and each is a three-term test: gate 1 is a
conjunction, where all three must hold, and gates 2 and 3 are disjunctions,
where any one term is enough. What comes out the bottom is not a description
of the entity but a description of the *difference* between the entity and
what this viewer was last told. The sections below are one per gate, then one
per feed that does not go through them at all.
> **For a 1.21-era reader.** `PlayerChunkSender` is in `server/network`, not
> `server/level`. And routine movement no longer travels as
> `ClientboundTeleportEntityPacket`: that packet survives, but `ServerEntity`
> never touches it, and an absolute position is
> `ClientboundEntityPositionSyncPacket`.
### Gate 1: who is allowed to see it
`ChunkMap.tick` re-tests visibility only when something moved between
sections — the entity's own section against the tracker's last, and, for
players who changed section, every entity against that player. `ChunkMap.move`
does the same eagerly the moment a player crosses a boundary
([tickets and loading](../world/tickets-and-loading.md)). The test itself is
three conjuncts.
**The distance is horizontal.** `ChunkMap.TrackedEntity.updatePlayer` compares
squared *x/z* distance against the smaller of the entity's effective range and
the player's view distance in blocks. **Y is ignored entirely** — an entity
directly above you, at any height, is in range.
**The range is the maximum over the whole vehicle stack.**
`ChunkMap.TrackedEntity.getEffectiveRange` takes the largest
`EntityType.clientTrackingRange` among the entity and all its indirect
passengers, then scales it by `MinecraftServer.getScaledTrackingDistance`,
which both server classes override: `DedicatedServer` applies the
*entity-broadcast-range-percentage* property, and `IntegratedServer` applies
the client's own **Entity Distance** video option. In singleplayer a graphics
slider decides how far away mobs are tracked.
**And the chunk must already be there.** `Entity.broadcastToPlayer` looks like
a per-entity hiding hook and is not: it defaults to true and is overridden
exactly once, by `ServerPlayer`, to make spectators see only what they are
spectating and to keep a spectator out of everyone else's view. And
`ChunkMap.isChunkTracked` is false while the chunk is still queued in
`PlayerChunkSender`, so an entity is never sent before the ground it stands
on — the ordering is guaranteed rather than hoped for.
One case never reaches the test at all: `ChunkMap.TrackedEntity.updatePlayer`
returns immediately for the player's own entity. **You never track yourself.**
That is why `ServerEntity.Synchronizer` has three verbs and not one —
`ServerEntity.Synchronizer.sendToTrackingPlayers`,
`ServerEntity.Synchronizer.sendToTrackingPlayersAndSelf` and
`ServerEntity.Synchronizer.sendToTrackingPlayersFiltered` — and why damage,
knockback and synched data must all reach for the self-directed one.
### The introduction is one bundle
```mermaid
sequenceDiagram
participant CM as ChunkMap
participant CMTE as ChunkMap.TrackedEntity
participant SE as ServerEntity
participant CPL as ClientPacketListener
CM->>CMTE: the creeper changed section, so re-test every player
CMTE->>CMTE: range, veto and chunk-tracked all hold, so seenBy gains this connection
CMTE->>SE: addPairing
SE->>SE: sendPairingData fills one list, in a fixed order
SE->>CPL: one ClientboundBundlePacket
Note over SE,CPL: add-entity at the tracker baseline, then synched data, attributes, equipment, passengers, leash
CPL->>CPL: the bundle is applied inside one task, so nothing renders half-built
```
`ServerEntity.sendPairingData` produces the list and `ServerEntity.addPairing`
sends it as a single `ClientboundBundlePacket`, so the creeper can never be
seen half-initialised. The synched values are not read fresh: they come from
`ServerEntity.trackedDataValues`, the cached snapshot of the entity's
non-default values, refreshed whenever dirty data is flushed
([synched entity data](../entities/synched-entity-data.md)). Only the syncable
attributes go ([attributes](../entities/attributes.md)), and equipment,
passengers and leash links go only if there are any.
The add packet is the page's hook, and it has three exceptions and two
refusals. Paintings, item frames and leash knots build their own
`ClientboundAddEntityPacket` from their real position, bypassing
`ServerEntity` entirely — they are the three `BlockAttachedEntity` subclasses,
and a block-attached entity has no dead reckoning to agree about.
`EnderDragonPart` refuses outright, and `ChunkMap` never asks it. `Marker` throws
outright if anyone asks — which nobody does, because its tracking range is
zero and `ChunkMap` never tracks it. Everything else reads its position from
`ServerEntity.getPositionBase` and its rotations and motion from the last-sent
fields, however old they are.
### Gate 2: whether the detector is called at all
`ChunkMap.tick` calls `ServerEntity.sendChanges` when *any* of three things is
true: the entity changed section, `Entity.needsSync` is set, or its chunk is in
entity-ticking range. Two public fields on `Entity` do the forcing.
`Entity.needsSync` appears at this gate, again at gate 3 and again in the
velocity decision — three of the cascade's terms are the same flag — and is
set by being pushed, by being loaded from disk, and by a couple of dozen
classes for their own reasons. `Entity.syncPosition` is subtler: it re-phases the call
counter to the next interval boundary, so a bounced entity syncs at once
rather than up to an interval late.
The two counters inside `ServerEntity` are deliberately out of step.
`ServerEntity.tickCount` advances on every call, gate 3 open or shut, so
`ServerEntity.FORCED_POS_UPDATE_PERIOD` counts *calls*.
`ServerEntity.teleportDelay` advances only inside gate 3, so
`ServerEntity.FORCED_TELEPORT_PERIOD` counts *gated* calls — the forced
absolute sync is rarer than the forced position packet by however long the
interval gate stays shut.
This is also where a distant entity goes quiet, and the silence is
conditional. Being out of entity-ticking range only suppresses the detector
while the entity *also* stays inside its section and leaves `Entity.needsSync`
clear. Either of those breaks it, which is why a far-off mob can freeze for a
long time and then correct itself in one jump.
### Gate 3, and the position it chooses
| condition | result |
|---|---|
| squared position delta below `ServerEntity.TOLERANCE_LEVEL_POSITION` and rotation within `ServerEntity.TOLERANCE_LEVEL_ROTATION` | nothing sent |
| otherwise, and no forcing condition | `ClientboundMoveEntityPacket.Pos`, `.Rot` or `.PosRot` |
| every `ServerEntity.FORCED_POS_UPDATE_PERIOD` calls, gated or not | a position packet regardless |
| delta beyond what a short can hold — about eight blocks | absolute sync |
| `ServerEntity.teleportDelay` past `ServerEntity.FORCED_TELEPORT_PERIOD` | absolute sync |
| the entity just dismounted, or its ground flag flipped | absolute sync |
| `Entity.getRequiresPrecisePosition` | absolute sync |
| the entity is a passenger | rotation only — the base is silently re-set, and the next free call forces an absolute sync |
Rotations are single bytes, so one unit is a little over a degree, and the
dead-reckoning base advances only when something was actually sent: that is
what keeps the two sides' arithmetic identical. An arrow never takes a partial
path — `AbstractArrow` is excluded from the position-only and rotation-only
branches, so every open gate sends it a full position-and-rotation packet. A
minecart on the new movement behaviour skips the table altogether:
`ServerEntity.handleMinecartPosRot` diverts it into
`ClientboundMoveMinecartPacket`, which carries a list of steps rather than one
position.
The first term in that decision has exactly one caller in the whole game, and
it is a happy ghast. `Entity.setRequiresPrecisePosition` is asked for by a
ghast on its still timeout and by nothing else — a large ridable platform is
the one entity whose rounding error a player has to stand on.
Velocity is the third decision and a separate channel. When the entity wants
deltas — it is in the `EntityType.trackDeltas` set, `Entity.needsSync` is set,
or it is a `LivingEntity` currently elytra-flying — `ServerEntity` compares the
current delta movement against `ServerEntity.lastSentMovement` and sends
`ClientboundSetEntityMotionPacket`, bundled with
`ClientboundProjectilePowerPacket` for a hurtling projectile.
`EntityType.trackDeltas` looks like a third tracking parameter but is a
hardcoded exclusion list: players, llama spit, the wither, bats, item frames,
leash knots, paintings, end crystals and evoker fangs are out, everything else
is in.
### What goes out around the gates
Four feeds ignore gate 3, and between them they explain most of what
still feels responsive about a distant mob. Only gate 3: all four are inside
`ServerEntity.sendChanges`, which gate 2 decides whether to call at all, so
none of them helps a mob outside entity-ticking range. `Entity.hurtMarked`
sends a motion packet to the trackers *and* the entity itself, which is why
knockback is immediate on a creeper whose position otherwise updates slowly. A
changed passenger list is diffed on every call and goes out *filtered*, and
the filter is the surprise: it excludes the player whose own passenger status
changed, because that player has already been told directly by
`ServerPlayer.startRiding` or `ServerPlayer.removeVehicle`. An `ItemFrame`
iterates *every player in the level* — not its trackers — every tenth
call, to flush its synched data and, if it holds a map, push map updates.
Equipment *changes* do not pass through `ServerEntity`.
`ClientboundSetEquipmentPacket` comes from
`LivingEntity.handleEquipmentChanges`, and it goes to the trackers *without*
the self-directed variant, so a player is never sent their own equipment. A
straight main-hand to off-hand swap does not even get that far:
`LivingEntity.handleHandSwap` compresses it into a one-byte entity event. And
damage crosses without a number — `ClientboundDamageEventPacket` carries the
source, not the amount, and the health bar moves because of a separate synched
value ([damage and death](../entities/damage-and-death.md)).
## Chunks arrive on a loop the client paces
Chunk visibility is the same kind of decision one level up: made per player
rather than per entity, and paced by a loop whose set point the client
supplies.
### Which chunks enter and leave
`ChunkMap.applyChunkTrackingView` diffs the player's old and new
`ChunkTrackingView`; entering chunks are queued with
`PlayerChunkSender.markChunkPendingToSend`, leaving chunks are dropped with
`ClientboundForgetLevelChunkPacket` — unless they were still only pending, in
which case they leave the queue silently, because you cannot forget what was
never delivered. A chunk that merely *becomes* ready is queued by
`ChunkMap.onChunkReadyToSend`, and a moved centre sends
`ClientboundSetChunkCacheCenterPacket` first.
The region is neither a disc nor a square. `ChunkTrackingView.isWithinDistance`
shrinks each axis delta by a small buffer *before* the squared compare, so it
reaches a chunk further along each axis than it does diagonally, and
`ChunkTrackingView.Positioned` iterates one chunk beyond the view distance for
exactly that reason. `ChunkTrackingView.difference` is what turns a movement
into an enter/leave pair.
### The rate the client asks for
Then, once per tick, `PlayerChunkSender.sendNextChunks` runs per player from
the phase `MinecraftServer.tickChildren` reaches after every level has ticked
([the server tick](../server/server-tick.md)):
- it stops if too many batches are unacknowledged —
`PlayerChunkSender.maxUnacknowledgedBatches` **starts at one** and is raised
to `PlayerChunkSender.MAX_UNACKNOWLEDGED_BATCHES` on the first reply, so the
first batch after login is a hard round-trip barrier;
- it accumulates `PlayerChunkSender.batchQuota` by the client's desired rate
and stops if it is below one;
- it takes that many chunks **nearest first** from
`PlayerChunkSender.pendingChunks` — or, on a memory connection, or whenever
fewer are pending than the budget allows, all of them at once;
- and it brackets them with `ClientboundChunkBatchStartPacket` and
`ClientboundChunkBatchFinishedPacket`.
The client times the bracket in `ChunkBatchSizeCalculator`, clamps the sample
against the running average by `ChunkBatchSizeCalculator.CLAMP_COEFFICIENT`,
folds it into a weighted mean against
`ChunkBatchSizeCalculator.MAX_OLD_SAMPLES_WEIGHT`, and reports a rate back in
`ServerboundChunkBatchReceivedPacket`. The server clamps that between
`PlayerChunkSender.MIN_CHUNKS_PER_TICK` and
`PlayerChunkSender.MAX_CHUNKS_PER_TICK` and uses it as next tick's budget. It
is a closed control loop, and it is the client that sets the set point.
**Seven milliseconds** — the client time per tick that
`ChunkBatchSizeCalculator.getDesiredChunksPerTick` divides by its running
estimate of nanoseconds per chunk. It starts pessimistic, at two milliseconds
a chunk — three and a half chunks a tick against
`PlayerChunkSender.START_CHUNKS_PER_TICK`, nine. That opening figure is never
actually sent: the client folds the first real batch into the average before
it answers, so the first number the server hears is already measured.
What is being measured is narrower than it looks.
`ClientPacketListener.handleChunkBatchStart` and
`ClientPacketListener.handleChunkBatchFinished` are two of the nine handlers on
the client's play listener that never hop off the network thread, so the
loop is timing packet decode, not mesh building (the nine are listed in
[threads](../../reference/threads.md#the-nine-client-handlers-that-never-hop)).
### What a chunk packet carries
A chunk packet — `ClientboundLevelChunkWithLightPacket` — carries only the
client-facing heightmaps, every section's paletted block states and biomes, a
block-entity entry per block entity holding `BlockEntity.getUpdateTag` rather
than its save data, and the light layers. See
[chunk anatomy](../world/chunk-anatomy.md) and
[lighting](../world/lighting.md).
## Block changes: one flush a tick, two audiences
`ServerLevel.sendBlockUpdated` sends nothing. It marks a section dirty on the
`ChunkHolder` — in `ChunkHolder.changedBlocksPerSection`, one short set per
section — and adds the holder to a set on `ServerChunkCache`, unless the chunk
is loaded but not ticking, in which case nothing is recorded and the change is
never broadcast to anyone. Once a tick
`ServerChunkCache.broadcastChangedChunks` drains that set through
`ChunkHolder.broadcastChanges`, early in the level tick and before entities
move ([the level tick](../server/server-level-tick.md)) — so one broadcast
carries this tick's block changes and the previous tick's entity-driven ones.
**Light goes first, and to a strictly smaller audience.** If either of
`ChunkHolder.skyChangedLightSectionFilter` and
`ChunkHolder.blockChangedLightSectionFilter` is non-empty, one
`ClientboundLightUpdatePacket` goes only to players for whom this chunk is on
the *border* of their sent region (`ChunkMap.isChunkOnTrackedBorder`). A player
standing in the middle of their own loaded area is never sent light for the
chunk they are standing in: their own light engine is expected to derive it
([lighting](../world/lighting.md)).
**Then blocks, to everyone tracking the chunk.** Exactly one changed block in
a section becomes a `ClientboundBlockUpdatePacket`, two or more become a
`ClientboundSectionBlocksUpdatePacket`, and every change within the tick
collapses into at most one packet per section.
**And block entities alongside the blocks, not after them.** The check runs
inside the same per-section loop, immediately after that section's own update
packet — interleaved, not a third pass.
`ChunkHolder.broadcastBlockEntityIfNeeded` calls `BlockEntity.getUpdatePacket`,
which returns null by default, so only overriding types produce a
`ClientboundBlockEntityDataPacket`. The fallback is not "it rides the chunk
packet instead": the chunk packet carries `BlockEntity.getUpdateTag`, which is
*also* empty by default, and an empty tag is stored as nothing at all. A block
entity that overrides neither tells the client its position and its type and
nothing else — which is why chest contents are invisible until the chest is
opened ([block entities](../blocks/block-entities.md)).
### The rest of the block-shaped traffic
It is small: `ClientboundBlockEventPacket` from the deferred event set
([pistons and block events](../blocks/pistons-and-block-events.md)),
`ClientboundBlockDestructionPacket` for other players' mining progress,
`ClientboundChunksBiomesPacket` when biomes are re-sent, and
`ClientboundBlockChangedAckPacket`, sent at most once per connection per tick
and on any tick where the client sent a block action, a use-on or a use —
**including an unsequenced abort, which produces an ack of zero and settles
nothing**. The rules that receipt obeys belong to
[prediction and acknowledgement](../client/prediction-and-acks.md).
## The level's own feeds
Entities and chunks are the two big feeds. The level itself has several small
ones, all bypassing the change detectors entirely — most on `ServerLevel`,
though time comes from `MinecraftServer` and the view distances from
`PlayerList`:
- **Time**, once a second. `MinecraftServer.forceGameTimeSynchronization` runs
every twentieth tick, and the packet carries a game time plus a map of clock
updates rather than a single day-time number
([environment attributes and timelines](../world/environment-attributes-and-timelines.md)).
- **Weather**, on change. `ServerLevel.advanceWeatherCycle` broadcasts rain-
and thunder-level changes and the start/stop pair as
`ClientboundGameEventPacket`s, and `PlayerList` re-sends the same set to a
joining player.
- **Sounds, level events, particles and entity events**, each with its own
helper and its own audience. Two radii are worth naming because they are not
the tracking distance: another player's mining progress reaches everyone
within thirty-two blocks except the miner, and a block event reaches
sixty-four.
- **View distances**, as `ClientboundSetChunkCacheRadiusPacket` and
`ClientboundSetSimulationDistancePacket` — the two integers that are the
client's entire knowledge of the ticket system. Receiving the first also
rebuilds the client's chunk storage array.
- **The debug feed.** `ServerLevel` owns a set of per-subscriber debug
synchronizers that push neighbour updates, POI state, chunk sends and entity
tracking to a client that has opted in. Everything in the next section is
invisible *except* through that channel.
## What the client is never told
| what never crosses | the server-side owner | where it is explained |
|---|---|---|
| all AI — targets, goals, brains, paths | `Mob.goalSelector`, `Mob.targetSelector`, `Mob.getTarget`, `Brain`, `Mob.navigation` | [AI, goals and brains](../entities/ai-goals-and-brains.md) |
| scheduled block and fluid ticks — the client's equivalents are empty | `ServerLevel.blockTicks`, `ServerLevel.fluidTicks` | [scheduled ticks](../world/scheduled-ticks.md) |
| points of interest, except through the debug channel | `ChunkMap.poiManager` | [points of interest](../world/points-of-interest.md) |
| the ticket graph — the client gets a radius and a simulation distance, as two integers | `TicketStorage`, `DistanceManager`, `ChunkHolder.ticketLevel`, `FullChunkStatus` | [tickets and loading](../world/tickets-and-loading.md) |
| worldgen, and **the world seed** — `ClientLevel` gets only a biome zoom seed | `ChunkGenerator`, `RandomState`, `ServerLevel.structureManager`, `StructureStart` | [the generation pipeline](../world/chunk-generation-pipeline.md) |
| the worldgen heightmaps, and any block-entity field outside `BlockEntity.getUpdateTag` | `BlockEntity` | [block entities](../blocks/block-entities.md) |
| non-syncable attributes, loot tables and loot seeds, the natural spawn state, raids and the dragon fight | `AttributeMap`, `LootTable`, `NaturalSpawner` | [attributes](../entities/attributes.md) |
| game rules — they reach the client only on request, and only for a player with the command permission | `GameRules` | [level data and rules](../../reference/level-data-and-rules.md) |
| the creeper's fuse length and its swell counter — of its three synched values, none is the counter | `Creeper` | [synched entity data](../entities/synched-entity-data.md) |
| everything outside the disc: entities past tracking range, chunks past the view, and every other level on the server | `ChunkMap`, `MinecraftServer.levels` | — |
## Questions players ask
**Why does a mob above me appear out of nowhere?** Because visibility ignores
Y. The test is a horizontal disc, so an entity directly overhead is in range
at any height, while one a few blocks further out is not, at any height.
**Why do I see mobs further away in singleplayer after changing a graphics
setting?** `IntegratedServer` scales every tracking range by the client's
Entity Distance video option. On a dedicated server the same hook reads the
*entity-broadcast-range-percentage* property instead.
**Why does a distant mob freeze and then jump?** Its chunk is out of
entity-ticking range, so gate 2 is shut — until the mob crosses a section
boundary or something sets `Entity.needsSync`, at which point one packet
carries the whole accumulated difference.
**Why is knockback instant when the same mob's walking looks choppy?**
`Entity.hurtMarked` is checked past gate 3, while the position it is
knocked into waits for the interval. Past gate 3 and no further: a mob outside
entity-ticking range that has not changed section fails gate 2, and its
knockback waits with everything else.
**Why can I not see what is in a chest until I open it?** Because
`BlockEntity.getUpdateTag` is empty by default, so the chunk packet carries the
chest's position and type and nothing else, and `BlockEntity.getUpdatePacket`
is null by default, so no update packet follows it.
**Why does the first bit of world take a moment, and then the rest floods
in?** The first chunk batch is a synchronous round trip: one batch in flight
until the client's first acknowledgement, ten after it.
**Why do I never see my own armour appear on my own body?** Equipment goes to
the trackers without the self-directed variant, and you are not one of your
own trackers.
## Choosing what the client may be wrong about
Every gate above is a decision *not* to say something, and the server can
afford them because the receiver is not passive. `ClientLevel` never admits a
chunk is missing, runs its own light engine and its own clock, ticks entities
it does not own — `Creeper.tick` runs locally, swell counter and all — and
guesses at block placements through a sequence-numbered ledger. That half of
the story is Part X's: [the client level](../client/the-client-level.md) for
what the receiver fakes and simulates,
[prediction and acknowledgement](../client/prediction-and-acks.md) for what it
guesses, and [authority](../entities/authority.md) for which side is allowed
to move what. The client also applies a whole burst of these packets once per
frame, before that frame's ticks, so at a high frame rate it takes the
server's updates far more often than it ticks — the two-loops figure in
[anatomy](../anatomy/anatomy.md) is the shape,
[the client loop](../client/the-client-loop.md) the detail.
Which is the design constraint under all of it: because the client will
happily simulate in the absence of data, **the server's job is not to keep the
client correct — it is to choose what the client is allowed to be wrong
about.**
## Where to look
`ChunkMap.tick` · `ChunkMap.TrackedEntity.updatePlayer` ·
`ChunkMap.isChunkTracked` · `ServerEntity.addPairing` ·
`ServerEntity.sendPairingData` · `ServerEntity.sendChanges` · `VecDeltaCodec` ·
`ChunkTrackingView.difference` · `PlayerChunkSender.sendNextChunks` ·
`ChunkBatchSizeCalculator` · `ChunkHolder.broadcastChanges` ·
`ServerChunkCache.broadcastChangedChunks` · `ServerLevel.sendBlockUpdated`
---
*Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes `tools/verify_names.py`.*
---
# Chat and signing
> Verified against **Minecraft 26.2** · Part IX · A player presses T, types a line and hits enter: the message is signed on the way out, taken apart on the way in, and verified again by every client that draws it.
A player presses T, types *hey* and hits enter. Before the line leaves the
machine, `ChatScreen.normalizeChatMessage` has squeezed the whitespace and cut
it to 256 characters, and `ClientPacketListener.sendChat` has taken a
timestamp, a random salt and the signatures of the twenty messages the player
most recently saw, and signed all of it with a key Mojang issued to that
account. The server pulls the packet apart on the Netty thread, hands the
cryptography to the Server thread, filters it, decorates it, broadcasts it —
and every receiving client verifies the signature again before drawing a
character. Every one of those steps can say no, and *no* does not mean the
same thing twice. Forge a signature and you stay connected: you get a red line
and your **chain** dies, so everything else you say this session fails too.
Miscount which messages you have *seen* — a number nothing in the game shows
you — and the server closes the connection mid-sentence. **The bookkeeping is
defended harder than the cryptography is**, and that is the right way round.
## The cast
| class | what it decides | thread |
|---|---|---|
| `ServerGamePacketListenerImpl` | the order the checks run in, and which failure closes the connection | Netty for the window, the characters and the chat-visibility refusal, Server for everything after |
| `LastSeenMessagesValidator` | whether the client's twenty-slot acknowledgement still matches the server's mirror | Netty, inside a lock on itself |
| `SignedMessageChain.Decoder` | whether this message continues the sender's chain — and whether the chain survives the answer | Server |
| `PlayerChatMessage` | what a signature covers: the chain link, the content, the timestamp, the salt, the window | wherever a message is built |
| `MessageSignatureCache` | which of those signatures travel as a small index instead of 256 bytes | both sides, one 128-slot cache each |
| `PlayerList` · `OutgoingChatMessage` | who gets a copy, and whether it goes as a signed message or a disguised one | Server |
| `SignedMessageValidator.KeyBased` | whether the receiving client believes the sender said this | Render |
| `ChatTrustLevel` | secure, modified or not secure — the tag drawn beside the line | Render |
## A message is not the text you see
A message on the wire is a `PlayerChatMessage`: a `SignedMessageLink` saying
where in the sender's chain it sits, a `MessageSignature`, a
`SignedMessageBody` of exactly four fields, and — optionally — a `Component`
to display *instead of* the signed string. That `Component` is Part II's
subject ([text components](../foundations/text-components.md)); all this page
needs from it is that it is a different object from the signed text and that
the signature does not cover it. Vanilla's *decorator* never produces one:
`MinecraftServer.getChatDecorator` is hard-coded to `ChatDecorator.PLAIN`, so
`PlayerChatMessage.withUnsignedContent` always finds the decorated copy equal
to the original and drops it. But vanilla sends unsigned content by another
road entirely — `MessageArgument.resolveChatMessage`, the message argument
behind `/msg`, `/say` and `/tell`, sets it on every message it resolves,
because it has expanded the entity selectors in the text. Type a selector into
a whisper and the recipient sees a string the signature does not cover.
## One line, typed and delivered
```mermaid
sequenceDiagram
participant CScr as ChatScreen
participant CPL as ClientPacketListener
participant SGPL as ServerGamePacketListenerImpl
participant PL as PlayerList
participant RCPL as ClientPacketListener
participant CLis as ChatListener
Note over RCPL: RCPL is the recipient's client, CPL the sender's
CScr->>CPL: whitespace squeezed, cut to 256 characters
CPL->>CPL: timestamp, salt, the last-seen window, then sign
CPL->>SGPL: ServerboundChatPacket
SGPL->>SGPL: Netty thread, apply the last-seen update, check the characters
Note over SGPL: everything below is a task queued on the Server thread
SGPL->>SGPL: SignedMessageChain.Decoder.unpack, which verifies the signature
SGPL->>SGPL: start the filter, decorate at once, join them in a FutureChain
SGPL->>PL: broadcastChatMessage, bound to ChatType.CHAT
PL->>RCPL: ClientboundPlayerChatPacket, signatures packed to cache ids
RCPL->>RCPL: check the global index, unpack the cache ids, verify the signature
RCPL->>CLis: handlePlayerChatMessage, trust level, blocklist, delay queue
CLis->>RCPL: markMessageAsProcessed
RCPL->>SGPL: ServerboundChatAckPacket, once the offset passes 64
```
Four things in that picture are worth naming before the checks are.
**The client signs the conversation, not just the sentence.** The window it
signs is the window it now treats as acknowledged, so the signature binds the
context the sender had in front of them — which is what makes a report show
what a message was a reply *to*.
**The hop is deliberate.** `ServerGamePacketListenerImpl.handleChat` never
calls the usual same-thread guard: the window and the character check run on
the Netty thread, and only then does
`ServerGamePacketListenerImpl.tryHandleChat` post the rest to the server. That
posted task, and the `FutureChain` continuation that joins the text filter to
it, drain with every other queued server task — so a slow filter service
delays delivery by however many ticks it takes
([the server tick](../server/server-tick.md)).
**Decoration is not sequenced after filtering.** The handler starts the filter
future, decorates immediately and synchronously, and only then registers the
continuation that joins the two. A decorator never sees filtered text.
**Broadcast is per recipient, and gated in three places** — two before the
message is built for that player, and `ServerPlayer.shouldFilterMessageTo`
inside it.
`PlayerList.broadcastChatMessage` logs the line — marked *Not Secure* by
`PlayerList.verifyChatTrusted` if it has no signature or has expired — and
then offers it to every player without testing anything. `ServerPlayer` drops
it unless that player's setting is `ChatVisiblity.FULL`;
`OutgoingChatMessage.Player` applies the per-recipient filter mask and skips a
copy that was filtered away entirely, telling the *sender* so. A message whose
sender is `Util.NIL_UUID` is a system message and leaves as an unsigned,
unreportable `ClientboundDisguisedChatPacket` instead.
## Three ways to say no
```mermaid
flowchart TD
P["ServerboundChatPacket, on the Netty thread"] --> W{"last-seen window agrees"}
W -- no --> X1["connection closed: chat_validation_failed"]
W -- yes --> C{"every character allowed"}
C -- no --> X2["connection closed: illegal_characters"]
C -- yes --> H["queued on the Server thread"]
H --> S{"SignedMessageChain.Decoder.unpack"}
S -- "no signature, or key expired" --> M["message dropped, red line to the sender, the next one may still land"]
S -- "out of order, or signature invalid" --> B["chain broken, every later message this session fails too"]
S -- "accepted" --> OK["filter, decorate, broadcast"]
```
Those three endings are the whole vocabulary of failure here, and every check
in the next section lands on exactly one of them.
The **message** dies alone: it is dropped, the sender usually gets a red
system line explaining why, and the next thing they send is judged on its own
merits. The **chain** dies for the session: `SignedMessageChain` clears the
link it was going to advance, and from then on no unpack can succeed — the
*chain broken* error if the message is otherwise well-formed, and a missing-key
or expired-key error before that if it is not. Only a
new session key, announced with `ServerboundChatSessionUpdatePacket`, restores
it. The **connection** dies immediately, and the player is back at the
multiplayer list.
## Every check, and what it costs
The first fifteen rows are the server treating the client as the adversary.
The last three are the client treating the server as one — the same design
mirrored, because a server can lie about who said what at least as easily as a
client can.
| the check | what it catches | what dies |
|---|---|---|
| `LastSeenMessagesValidator.applyOffset`, from a chat packet or a bare `ServerboundChatAckPacket` | a client advancing its window past messages the server has not sent it | **connection** |
| `LastSeenMessagesValidator.applyUpdate`, the acknowledged bits | a bit set longer than twenty, one naming a slot the server does not hold, or one un-acknowledging a slot already acknowledged | **connection** |
| `LastSeenMessages.Update.verifyChecksum` | the two sides holding different signatures in slots whose bits agree — a desync the crypto would otherwise report as a bad signature | **connection**, unless the client sent `LastSeenMessages.Update.IGNORE_CHECKSUM` |
| `ServerGamePacketListenerImpl.isChatMessageIllegal`, over `StringUtil.isAllowedChatCharacter` | section signs and control characters — formatting injected into everyone else's chat | **connection** |
| `ServerPlayer.getChatVisibility`, non-commands only | a player who turned chat off and sent a line anyway | **message**, with a red *chat.disabled.options* back to the sender |
| `SignedMessageChain.Decoder.unpack`, no signature present | an unsigned message once a chat session exists — unconditionally, whatever `MinecraftServer.enforceSecureProfile` says, which governs only the decoder used before one does | **message** |
| the same, `ProfilePublicKey.Data.hasExpired` | a session key past its expiry still being used to sign | **message** |
| the same, a timestamp before the last accepted one | a replayed or reordered message from this sender | **chain** |
| the same, `PlayerChatMessage.verify` | content, timestamp, salt or window that do not match the signature — a forgery, or a proxy editing text in flight | **chain** |
| `ServerGamePacketListenerImpl.collectSignedArguments`, an unknown argument name | a client signing arguments of a command the server's own parse does not have | **chain**, broken explicitly |
| the same, a signable argument with no signature | signatures stripped from *some* arguments of a signed command | **message** — the chain is left intact |
| `ServerGamePacketListenerImpl.performUnsignedChatCommand` | a signable command sent down the plain command packet with its signatures removed | **message**, and only when `MinecraftServer.enforceSecureProfile` is on |
| `ServerGamePacketListenerImpl.detectRateSpam`, a `TickThrottler` per player | flooding: each message costs 20 and one point decays per tick | **connection**, except for operators and the singleplayer host |
| `ServerGamePacketListenerImpl.sendPlayerChatMessage`, via `LastSeenMessagesValidator.trackedMessagesCount` | a client that is sent signed messages and never acknowledges them | **connection**, past 4,096 pending |
| `ServerGamePacketListenerImpl.handleChatSessionUpdate` | a key that expires *earlier* than the one it replaces, or one `RemoteChatSession.Data.validate` cannot trace to Mojang's services key | **connection** |
| `ClientPacketListener.handlePlayerChat`, the global index | a server dropping, duplicating or reordering messages beneath the player, which would falsify any report drawn from the log | **connection** |
| `MessageSignature.Packed.unpack` against the client's cache | a server naming a cached signature the client has never held | **connection** |
| `SignedMessageValidator.KeyBased` — expired key, failed signature, or a link that is not `SignedMessageLink.isDescendantOf` the last | a server inventing lines in another player's name | **chain**, latched: the validator never returns to valid |
Two rows want a sentence more. The checksum is the only check in the table a
client may decline: `LastSeenMessages.Update.verifyChecksum` passes anything
when the byte is zero, and a real checksum that computes to zero is bumped to
one so it can never be mistaken for the opt-out — though the vanilla client
never opts out, because `LastSeenMessagesTracker.generateAndApplyUpdate`
always computes one. And *chain broken* is a latch on both sides: the server's
`SignedMessageChain` and the receiving client's
`SignedMessageValidator.KeyBased` both refuse everything afterwards, so one
bad signature costs a sender their voice until a key rotation, not one line.
## Why losing the window is worse than losing the signature
The asymmetry looks backwards until you notice what the window is *for*. The
last-seen list is signed **in full** but sent as `MessageSignature.Packed`
indices into a cache both sides maintain identically. If those caches ever
diverge, the receiver reconstructs a different `SignedMessageBody`, and every
signature after that fails for a reason no cryptographic error message can
explain. There is no recovery from inside: the state is shared, and half of it
is wrong.
So the game ends the connection at the first sign of that divergence, and
`LastSeenMessagesValidator` is written to be suspicious. It rejects an
acknowledgement of a slot it does not hold, an *un*-acknowledgement of a slot
it already acknowledged, a negative offset, an offset larger than the number
of tracked messages outside the window it has
actually sent, and a bit set wider than the window; a checksum mismatch on top
of all that says *the client and server must have desynced* in as many words.
A bad signature is the opposite kind of problem: local, provable and
attributable. One sender is misbehaving, everyone else's conversation is
intact, and the proportionate answer is to stop trusting that sender — not to
end a session for every other player in the room.
**Sixty-four** — the acknowledgement offset a client may accumulate before
`ClientPacketListener.markMessageAsProcessed` sends a bare
`ServerboundChatAckPacket` unprompted. That packet exists so that a player who
only listens never reaches the server's 4,096 pending messages and gets
dropped for saying nothing all evening.
## What the signature covers
`PlayerChatMessage.updateSignature` feeds the signer a version constant, then
the link — sender id, session id, index — then the body: the salt, the
timestamp **in seconds**, the length of the content, the content bytes, the
count of last-seen signatures and each one's raw bytes. Not fed to it: the
decorated `Component`, the `FilterMask`, the `ChatType.Bound` that supplies the
*someone said* wrapper, and the global index the receiving client checks. A
server is free to change any of those. The signature is over what the player
typed and what they had seen when they typed it, and nothing else.
That gap is what `ChatTrustLevel` exists to expose, and it tests for it
crudely on purpose. `ChatTrustLevel.evaluate` calls a message *modified* the
moment the rendered string does not **contain** the signed string — the limb
that catches a server rewriting what someone said. Only if that passes does it
look at style, and only inside the unsigned copy — which vanilla does send,
for any command message carrying a selector. A
message with no signature at all, or one older than seven minutes, is *not
secure* instead. The tag is normally all that happens; with
`Options.onlyShowSecureChat` on, a not-secure message is discarded rather than
drawn — that test runs first, and `Minecraft.isBlocked` and
`Minecraft.isFriendOnlyRestricted` can swallow what survives it.
The session key is signed one level up. `ProfilePublicKey.Data` carries an
expiry, the public key and a signature over the profile id, the expiry **in
milliseconds** and the encoded key, checked against Mojang's services key. The
receiving client allows `ProfilePublicKey.EXPIRY_GRACE_PERIOD` — eight hours —
that the signing chain on the server does not.
## Commands: one signature per argument
`ClientPacketListener.sendCommand` parses the command locally and builds a
`SignableCommand`. If nothing in it needs signing it sends
`ServerboundChatCommandPacket`, which carries the string and nothing else. If
something does, it sends `ServerboundChatCommandSignedPacket` with
`ArgumentSignatures`: one signature per argument, each consuming its own chain
index, all of them sharing one timestamp, salt and window. *Signable* means
the argument type implements `SignedArgument`, and in 26.2 exactly one type
does — `MessageArgument`, behind the message-shaped commands. Its
`MessageArgument.Message.toComponent` is also the one place chat text has its
selectors expanded — each `MessageArgument.Part` resolved by
`EntitySelector.joinNames`, behind a permission — which is why `/say @a` names
people and a chat line saying the same thing does not.
The server re-parses its own copy and looks each signature up **by argument
name**, which is where two rows of the table above come from: a name its parse
does not have breaks the chain outright, while a signable argument the client
left unsigned only fails the command. Both sides cap the shape of the packet
at `ArgumentSignatures.MAX_ARGUMENT_COUNT` — eight — and
`ArgumentSignatures.MAX_ARGUMENT_NAME_LENGTH`, sixteen. Commands run against
their own `TickThrottler`, separate from chat's and with its own threshold. A
command message that ends up with no signed argument is broadcast as a
`ClientboundDisguisedChatPacket`: chat-type decorated, unsigned, unreportable.
## Questions players ask
**What actually makes the "Not Secure" tag appear?** No signature, or a
timestamp more than seven minutes old by the receiving client's clock. The
server calls the same message stale after five, and logs it as *Not Secure*
there too. Two machines whose clocks are a few minutes apart will flag
perfectly honest messages, and the server says so in its log.
**Why does a custom font not flag every line on my server?** Because the style
test only looks inside the unsigned, decorated copy — and vanilla never sends
one. The font check is dead on a vanilla server and live on a server that
decorates. A player's own lines on an integrated server skip both tests and
are secure by definition.
**Can a server delete a message from my chat?** It has the packet for it:
`ClientboundDeleteChatPacket` is registered and handled, and the handler will
pull the line out of the player's own chat-delay queue if their *chatDelay*
option means it has not been drawn yet. Nothing in the game constructs it.
**Why can I report some lines and not others?** `LoggedChatMessage.canReport`
needs a signature from the player being reported, and system messages,
disguised command output and anything a broken chain swallowed carry none.
What a report uploads is the *signed* material — index, session id, timestamp,
salt, the last-seen signatures and the signed content — so it can be
re-verified independently, with
`ChatReportContextBuilder.collectAllContext` walking the last-seen links
backwards for the conversation around it.
**Where does my signing key live?** Nowhere, in a shipped client:
`AccountProfileKeyPairManager` writes the key file only when
`SharedConstants.IS_RUNNING_IN_IDE` and deletes it otherwise, so each launch
re-fetches from the account service — and only if
`ClientboundLoginPacket.onlineMode` said the server was in online mode.
**Why can I not chat here even though nothing is wrong?** `ChatAbilities` and
`ChatRestriction` are a client-side layer the server has no part in: game
options, launcher policy and the account profile each strip permissions
independently, and what survives decides whether this client will send
messages, send commands, or accept player or system messages at all.
`ChatVisiblity` is only the sliver of that the server is told about, and it
has three values, not two — `ChatVisiblity.HIDDEN` still lets action-bar text
through.
**And if somebody's session key fails validation?** Which side notices decides
the cost. On the server it closes the connection. On another client,
`ClientPacketListener.initializeChatSession` merely calls
`PlayerInfo.clearChatSession`, and that player's lines arrive unsigned — taken
and tagged insecure by `SignedMessageValidator.ACCEPT_UNSIGNED`, or refused
by `SignedMessageValidator.REJECT_ALL` where secure profiles are enforced.
## Where to look
`ChatScreen.normalizeChatMessage` · `ClientPacketListener.sendChat` ·
`LastSeenMessagesTracker.generateAndApplyUpdate` ·
`ServerGamePacketListenerImpl.handleChat` ·
`ServerGamePacketListenerImpl.unpackAndApplyLastSeen` ·
`LastSeenMessagesValidator.applyUpdate` ·
`ServerGamePacketListenerImpl.tryHandleChat` · `SignedMessageChain.Decoder` ·
`PlayerChatMessage.updateSignature` · `PlayerList.broadcastChatMessage` ·
`OutgoingChatMessage.create` ·
`ServerGamePacketListenerImpl.sendPlayerChatMessage` ·
`ClientPacketListener.handlePlayerChat` · `MessageSignatureCache.push` ·
`SignedMessageValidator.KeyBased` · `ChatListener.handlePlayerChatMessage` ·
`ChatTrustLevel.evaluate` · `ClientPacketListener.markMessageAsProcessed` ·
`ChatReportContextBuilder.collectAllContext`
---
*Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes `tools/verify_names.py`.*
---
# X · The client
> Verified against **Minecraft 26.2** · Part X · One thread, one loop, and seven systems that differ mainly in how often the loop gets round to them.
Everything in this part that touches the game happens on one thread. Nothing
in the client's simulation is driven by a scheduler or a timer callback —
the two classes that own one, `PeriodicNotificationManager` and
`RemoteFriendListUpdateHandler`, hop back to the game thread before they
touch anything — and, despite the name printed in every stack trace, there
is no render thread: the thread called *Render thread* is the main thread,
and it is the same thread that ticks the world, applies packets, handles your
keyboard, decides what a screen looks like and asks the GPU to draw it. A
player recognises the part by its symptoms of that arrangement: the stutter
where the world moves on without you, the block that appears and then
disappears, the terrain filling in ahead of you as you fly, the sound that
arrives a beat after the packet.
## The shape of the part
Part X is a **hub and its spokes**, and the spokes are cadences rather than
stages: with one exception, noted below, nothing here hands off to anything.
`the-client-loop` is the hub because it is the one
page that says *when* anything on the client runs, and every other page in
the part answers the same question about itself: **when in that loop does
this happen?** Read the labels on the arrows as cadences, not as an order.
```mermaid
flowchart TD
LOOP["The client loop — the hub"]
LEVEL["The client level"]
PRED["Prediction and acknowledgement"]
INPUT["Input and keybinds"]
OPT["Options"]
GUI["The GUI stack — screens, the render tree, text, the HUD"]
SND["Sound — the engine, and what makes a sound happen"]
DBG["Debugging the running game"]
LOOP -- "per tick, and light per frame" --> LEVEL
LOOP -- "per action, in one synchronous window" --> PRED
LOOP -- "per GLFW callback, keys before the tick" --> INPUT
LOOP -- "per save, which a cycle button does on click" --> OPT
LOOP -- "per frame, recorded then drawn" --> GUI
LOOP -- "per event, then three more threads of its own" --> SND
LOOP -- "per tick, and a packet only when the set changes" --> DBG
```
The one genuine pipeline inside the part is the GUI stack: a screen records
itself into a tree, the text in it becomes glyphs, and the tree is then
sorted and batched into draws. Those three are three stages of one journey
and are watched in a different order from the one they run in — the tree
before the text, because the text's stages are easier to follow once you know
what they are recording into — with the HUD after them as the other thing
that records into the same tree. Everything else in the part is independent
of everything else in the part.
## Before you start
[Part IX](../networking/README.md), and not optionally — this part is the
same wire watched from the receiving end. Three pages here begin at a packet
that has already arrived, and [the connection](../networking/the-connection.md)
is what it took to get there.
[Part I's anatomy](../anatomy/anatomy.md) for the two-loops figure, which is
the premise of the whole part: the server's tick loop and the client's frame
loop are different clocks, and almost every surprise in Part X is a
consequence of one of them waiting on the other.
[Authority](../entities/authority.md) from Part VI, because "what the client
is allowed to decide" is the question `the-client-level` and
`prediction-and-acks` are both answering, and neither re-derives the five
predicates.
Two smaller ones, each for one page. [Part V](../blocks/README.md) before
[prediction and acknowledgement](prediction-and-acks.md): the ledger's six
windows open around rather more than a block placed and a block broken, but
those two are the ones a viewer needs to have seen, and Part V's landing
page already rules that its pages are watched first. And [text
components](../foundations/text-components.md) from Part II before [text and
fonts](text-and-fonts.md), which starts from "you have a `Component`".
## Watch in this order
1. [The client loop](the-client-loop.md) — the hub, and the one page every
other page in the part leans on. How much simulated time a frame owes,
what it spends it on, and what happens to the time it cannot afford.
Watch this before anything else in Parts X and XI.
2. [The client level](the-client-level.md) — the same `Level` class the
server runs, with its authority removed. A comparison: what the client
really simulates, and what it only pretends to.
3. [Prediction and acknowledgement](prediction-and-acks.md) — the block that
appears and then disappears. One ledger, one counter, and a receipt that
is not a verdict.
4. [Input and keybinds](input-and-keybinds.md) — everything between the
operating system and a key being *down*, and the five places a press can
be swallowed on the way.
5. [Options](options.md) — one flat file and nine fields the server ever
hears about. A policy page: what saving does, and who is told.
6. [GUI and screens](gui-and-screens.md) — what a screen *is*: the manager,
the lifecycle, the widget family, and the four routes by which a screen
comes to exist.
7. [The GUI render tree](the-gui-render-tree.md) — the second stage of the
same journey. Nothing in the 2D UI draws anything; it all appends to a
tree that infers its own layering from bounding boxes.
8. [Text and fonts](text-and-fonts.md) — the third stage, and a pipeline of
its own: six stages from a `Component` at one end to a quad with a glyph
on it at the other.
9. [The HUD](hud.md) — the other thing that records into that tree, and the
part's second policy page: what is drawn over the world, in what order,
and under exactly which conditions.
10. [Sound: the engine](sound-engine.md) — the page in the part with the
most threads in it: five take part, a block placed near you crosses four
of them on its way to an OpenAL source, and one hop it cannot skip.
11. [What makes a sound happen](what-makes-a-sound.md) — the content model:
three doors a sound comes through, only one of which names it.
12. [Debugging the running game](debugging-the-running-game.md) — the
closer, and the part's one *pattern* lecture: one subscription
mechanism, sixteen instances, all of them shipped and fifteen of them
unreachable without a JVM flag.
Two and three are a pair — the ledger lives on `ClientLevel` and is reached
through four of its methods — and six to nine are the GUI stack, watched
together. Ten and eleven are the two halves of sound and can be watched in
either order; the engine first is the easier way round.
## Reference this part uses
[Diagram lanes](../../reference/lanes.md) for the abbreviations these pages'
figures use, and [the threads](../../reference/threads.md), which is where
the sound engine's own thread sits among the rest of the game's. [HUD
elements](../../reference/hud-elements.md) is the gate table [the
HUD](hud.md) is built on, in record order.
[Packets](../../reference/packets.md) for everything arriving from Part IX,
and [the glossary](../../reference/glossary.md) for *partial tick*,
*prediction ledger* and *extract*.
Where the part stops: `Minecraft.renderFrame` from its *extract* zone onwards
is Part XI, which begins where [the client loop](the-client-loop.md) ends —
at [the frame](../rendering/the-frame.md), and the acquired surface. The
handful of statements before that zone are still this part's, which is why
the per-frame light pass is a Part X cadence even though it runs inside the
render method. What the *server*
chose to send is [what the client is
told](../networking/what-the-client-is-told.md) in Part IX.
---
*Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes `tools/verify_names.py`.*
---
# The client loop
> Verified against **Minecraft 26.2** · Part X · one turn of `Minecraft.run`: how much simulated time a frame owes, what it spends it on, and what happens to the time it cannot afford.
The client has one loop and no schedule. A tick is not a timer callback and
not a thread — it is something the loop does on its way to a frame, as many
times as the clock says it owes. The clock is asked once per iteration, it
answers in whole ticks, and the loop then runs at most **ten** of them. A
frame that earned fifteen runs ten and loses five: they are already gone from
the residual, nothing will ever run them, and the world you are standing in
has skipped forward without simulating the gap. The server drops ticks too,
but only once it is more than the overload threshold plus twenty ticks behind
— and it logs *Can't keep up!* when it does. The client does it on any frame
that needs to, at a ceiling of ten, and says nothing.
Everything on this page is one thread. The thread named `"Render thread"` is
the main thread — `Main.main` renames it and `RenderSystem.initRenderThread`
claims it — so `Minecraft.gameThread`, `BlockableEventLoop.isSameThread` and
`RenderSystem.assertOnRenderThread` all agree about the same thread. There is
no render thread, and there never was one in this version.
## The cast
| class | what it decides | thread |
|---|---|---|
| `Minecraft` | the loop itself, and — being a `ReentrantBlockableEventLoop` — the main thread's task queue | Render thread |
| `DeltaTracker.Timer` | how many whole ticks this frame owes, and what the leftover fraction is | Render thread |
| `PacketProcessor` | where packets decoded on Netty threads wait to be applied | filled on Netty, drained here |
| `TickRateManager` | the millisecond target the Timer divides by — a *server* object | read here, owned there |
| `FramerateLimitTracker` | what the frame cap actually is, which is not always the option | Render thread |
| `FramerateLimiter` | the park that enforces it | Render thread |
| `Main` | the process: the config, the shutdown hook, the thread's name | JVM main = Render thread |
## One turn of the loop
`Minecraft.run` spins until `Minecraft.running` goes false, and each
iteration is `RenderSystem.pollEvents` followed by `Minecraft.runTick`. The
figure is that iteration. It is drawn as a flowchart rather than a
conversation because the fact worth having is a *decision* — the clamp, and
what falls off the end of it.
```mermaid
flowchart TD
POLL["RenderSystem.pollEvents — GLFW callbacks run here, inline, on this thread"]
PRE["Pre render: Window.shouldClose, then any pending resource reload"]
ASK["DeltaTracker.Timer.advanceGameTime — how many whole ticks has the clock owed since last time?"]
DRAIN["PacketProcessor.processQueuedPackets, then BlockableEventLoop.runAllTasks"]
TEX["TextureManager.tick — once, and only if ticks are owed and the level is running normally"]
CLAMP{"more than ten ticks owed?"}
DROP["the excess is already out of the residual — nothing will ever run it"]
TICK["Minecraft.tick, up to ten times"]
PREFRAME["SoundManager.updateSource, then MouseHandler.handleAccumulatedMovement"]
FRAME["Render: renderFrame — its frameLimiter zone parks for the cap, then fpsUpdate samples the counters"]
POST["Post render: recompute Minecraft.pause, update the timer's pause and freeze"]
POLL --> PRE --> ASK --> DRAIN --> TEX --> CLAMP
CLAMP -- "yes" --> DROP --> TICK
CLAMP -- "no" --> TICK
TICK --> PREFRAME --> FRAME --> POST
POST -- "next iteration of Minecraft.run" --> POLL
```
Read it as **owe, spend, draw, settle**. The clock says how much simulated
time has passed; the loop spends it on packets, tasks and up to ten ticks;
the frame draws whatever the world looks like afterwards; and only then does
the loop notice whether the game is now paused — which is why the first
frame of a pause is drawn unpaused.
The quoted phrases are `Window.setErrorSection` calls, the crash report's
breadcrumb, so a client that dies takes *Pre render*, *Render* or *Post
render* to the report with it. What happens inside the frame is [the
frame](../rendering/the-frame.md); this page stops at the profiler's *frame*
zone. Note where the frame limiter sits: inside `Minecraft.renderFrame`,
after the present, with only the *fpsUpdate* zone after it, and *before* the
pause is recomputed.
## The ten, and the arithmetic behind it
`DeltaTracker.Timer.advanceGameTime` takes the elapsed milliseconds, divides
by whatever `DeltaTracker.Timer.targetMsptProvider` returns for
`DeltaTracker.Timer.msPerTick`, adds the result to
`DeltaTracker.Timer.deltaTickResidual`, takes the whole part out and returns
it. The fraction that stays behind is the partial tick everything
interpolates against. The clamp then happens in the loop, not in the Timer —
so the ticks above ten are not deferred to the next frame, because they left
the residual when they were counted.
**Ten** — the ceiling, named by `Minecraft.MAX_TICKS_PER_UPDATE`, though the
clamp in `Minecraft.runTick` is written as a literal and no reader of the
constant survives the decompile. *javac* inlines a `static final int` at
every use site, so a decompile can never tell a documented constant from a
dead one; what it does show is that the number the loop obeys is the literal.
The divisor is not the client's to choose. `DeltaTracker.Timer` gets its
target from `DeltaTracker.Timer.targetMsptProvider`, which is
`Minecraft.getTickTargetMillis`, which asks the level's `TickRateManager`
for `TickRateManager.millisecondsPerTick` whenever it
`TickRateManager.runsNormally`. **`/tick rate` is a server command that
changes the arithmetic inside the client's frame loop.** `/tick freeze` is
the same lever pulled the other way, and the loop reads it directly rather
than through the Timer: `Minecraft.isLevelRunningNormally` asks the level's
`TickRateManager` again, and that is what stops `TextureManager.tick` — which
is why freezing the world freezes the water texture — and what gates
`ClientLevel.animateTick` and `ParticleEngine.tick` inside the tick. The
Timer is *told* the same answer at the end of the iteration, through
`DeltaTracker.Timer.updateFrozenState`, so that the partial tick it hands out
stops moving too.
Alongside the game clock the Timer runs a second, unpausable one.
`DeltaTracker.Timer.advanceRealTime` produces
`DeltaTracker.getRealtimeDeltaTicks`, which is what a menu animates against
while the world is stopped. The two constants `DeltaTracker.ZERO` and
`DeltaTracker.ONE` — two instances of the one nested `DeltaTracker.DefaultValue`
— exist so that code which needs a partial tick can be handed *no*
interpolation or *complete* interpolation without a branch.
## What a tick is, in order
`Minecraft.tick` is one long method and its order is a dependency order. It
advances `Minecraft.clientTickCount`; then, when there is a level and the
game is not paused, it ticks the `TickRateManager`. Then in sequence: the
game mode; `Minecraft.pick` at a partial tick of one; `Tutorial.onLookAt`
with the result; the GUI block (`TextInputManager`, then `Gui.tick`, with
`Minecraft.missTime` pinned high while a screen is open); the keybind drain,
**only** when there is neither an overlay nor a screen; then
`GameRenderer.tick`, `ClientLevel.tickEntities` and `Level.tickBlockEntities`;
then the music and sound managers, which sit *outside* the level check and
run with no world at all; then the level block — the first-server toast,
`Tutorial.tick`, and then `ClientLevel.tick` alone inside a crash-report
handler; then
`ClientLevel.animateTick` and `ParticleEngine.tick`, both additionally gated
on the level running normally; then `ServerboundClientTickEndPacket`; and
last of all `KeyboardHandler.tick`, where the F3+C crash countdown lives.
With no level that whole middle collapses, but not into one branch: two
separate *else* arms at two points in the method clear any post-effect and
tick the pending connection, with the unconditional music and sound managers
running between them.
Two orderings in that list are load-bearing elsewhere in the book.
`ServerboundClientTickEndPacket` goes out once per unpaused client tick that
has a connection, and the server reads it to decide that a player who sent no
movement this tick is standing still. And `Minecraft.pick` runs **once per
tick and once per frame** — the tick's call at a partial tick of one, the
frame's at the real one, and it is the frame's result the crosshair and the
block outline use. A frame that runs three ticks calls it four times; a frame
that runs none calls it once.
## Where work leaves this thread, and where it comes back
Four queues and one re-entry that is not a queue.
- **Packets** decoded on Netty threads are parked by
`PacketProcessor.scheduleIfPossible` and drained in the
*scheduledPacketProcessing* zone — once per frame, not once per tick. That
single fact is behind most of what looks like network jitter; [the
connection](../networking/the-connection.md) is the other side of it.
- **Tasks** from other threads land in *scheduledExecutables* through
`BlockableEventLoop.execute`. From *this* thread the same call usually runs
inline instead of queueing — but not while a queued task is already running,
because `ReentrantBlockableEventLoop.scheduleExecutables` returns true for
the whole of `ReentrantBlockableEventLoop.doRunTask`, which is what stops a
task from re-entering itself. GLFW callbacks, dispatched inside
`RenderSystem.pollEvents`, are not inside one, so they execute *before* the
tick that will observe them — see [input and keybinds](input-and-keybinds.md).
- **Section meshing** goes to `Util.backgroundExecutor` and is collected by
`SectionRenderDispatcher` (Part XI).
- **GPU work** registered with `RenderSystem.queueFencedTask` is picked up by
`RenderSystem.executePendingTasks`, which stops at the first unsignalled
fence rather than waiting. It looks general and is not: the one thing in
the tree that queues a fenced task is the OpenGL backend's asynchronous
texture readback.
- And `BlockableEventLoop.managedBlock` pumps tasks while the loop is
*blocked* waiting for the integrated server — the mechanism
[`server-tick`](../server/server-tick.md) owns.
The profiler wraps all of it. `Minecraft.constructProfiler` picks per
iteration between `InactiveProfiler`, the frame-profile `ContinuousProfiler`
behind the F3 pie chart, the `MetricsRecorder` and a `SingleTickProfiler`,
and `Minecraft.finishProfilers` closes it. `RenderSystem.pollEvents` is
inside the profiler scope but outside `Minecraft.runTick`, so **input polling
lands in no named zone** and shows up on the pie chart as unspecified time.
That is not why
`RenderSystem.isFrozenAtPollEvents` exists, though: its one caller is
`ClientCommonPacketListenerImpl.handleKeepAlive`, which defers the keep-alive
reply while the poll is blocked, so that dragging the window does not look to
the server like a network stall.
## Pausing, which is two things and neither is the menu
`Minecraft.pauseIfInactive`, called during the frame, pauses the game when
the window has been unfocused for more than half a second and
`Options.pauseOnLostFocus` is on. `Minecraft.pause` — the field — is
recomputed at the very end of `Minecraft.runTick` as *singleplayer, and the
GUI says we are pausing, and the world is not open to LAN*. `Gui.isPausing`
asks the current screen and overlay, and `Screen.isPauseScreen` defaults to
**true**: this is why the options screen stops a singleplayer world and a
chest does not — `AbstractContainerScreen` overrides it to false. On the
rising edge the loop calls `SoundManager.pauseAllExcept`, sparing music and
UI sounds, and hands the new state to `DeltaTracker.Timer.updatePauseState`.
## The frame cap is usually the option, and sometimes is not
`FramerateLimitTracker.getFramerateLimit` returns the option unchanged
normally; caps it at thirty after a minute idle; replaces it with ten when
the window is iconified or after ten minutes idle; and replaces it with
**sixty** in a menu with no level — which can be *more* than the player
asked for. The two idle cases apply only when `Options.inactivityFpsLimit`
is set to the AFK behaviour, and the iconified test wins over both.
`FramerateLimiter.limitDisplayFPS` is skipped entirely at or above 260 — the
option's own maximum, i.e. "unlimited"; below it, it parks for most of the
remainder, correcting for how much the JDK's park habitually overshoots, and
busy-spins the last fraction. The profiler notices too, though it does not
stop: `FramerateLimitTracker.isHeavilyThrottled` is the `ContinuousProfiler`'s
*suppress warnings* predicate, so a throttled client still measures itself but
stops complaining that its frames are slow.
The numbers on the F3 screen are three different measurements and it is worth
knowing which is which. `Minecraft.fps` is a static field sampled once a
second. `Minecraft.frameTimeNs` is a CPU span that stops at the blit, before
the present and before the limiter, and is read by nothing but telemetry. The
graph uses wall-clock between frames, measured
*after* the limiter, so it includes the sleep.
## Starting, and the three ways of stopping
`Main.main` builds a `GameConfig` from the command line, installs a shutdown
hook, renames the thread, calls `RenderSystem.initRenderThread` and
constructs `Minecraft`; a `SilentInitException` out of that constructor exits
quietly rather than crashing. `Minecraft.running` is set true inside that
constructor, but five statements *after* the `Options` are read from disk —
which is why every `OptionInstance.set` performed while loading *options.txt*
silently skips its listener (see [options](options.md)). `Main.main` then
calls `Minecraft.exitWorldAndClose`, and its last statement arms
`ClientShutdownWatchdog.startShutdownWatchdog` over what follows.
Stopping has three doors and one corridor. `Minecraft.stop` sets
`Minecraft.running` false, and is what `Window.shouldClose` triggers at the
top of `Minecraft.runTick`. `Minecraft.emergencySaveAndCrash` is where a
`ReportedException` or any other throwable from the loop body ends up, by way
of `Minecraft.emergencySave`, which releases the reserved memory block, halts
the integrated server and shows the saving screen. And an out-of-memory error
does not necessarily end anything: the first one makes `Minecraft.run` stop
advancing game time altogether — GUI only, no ticks, no packets, no world —
after an emergency save; a second one rethrows.
The corridor is `Minecraft.exitWorldAndClose` and then `Minecraft.close`,
which tears down in a fixed order — the time source first, outside the try,
then the friends list, the timer query, telemetry, compliancies, the atlas and
font managers, the game renderer, the shader manager, the level renderer, the
sound manager, the two texture managers, resources, the Tracy capture, the
narrator, FreeType, the executors, the surface and the renderer — and then, in
a finally block, only the window, the monitor manager and GLFW's own termination.
There is no *Minecraft.destroy*.
> **For a 1.21-era reader.** Names to stop hunting for:
> *Minecraft.getPartialTick*, *Minecraft.noRender*, *Minecraft.tell*,
> *Minecraft.destroy*, *Minecraft.screen* and *Minecraft.setScreen* (both now
> on `Gui`), *Timer* (now `DeltaTracker.Timer`), and *initGameThread* /
> *isOnGameThread*, which do not exist because the second thread they
> distinguished does not either.
## Where to look
`Minecraft.run` and `Minecraft.runTick` — the loop is those two methods.
`DeltaTracker.Timer.advanceGameTime` for the tick arithmetic and
`Minecraft.getTickTargetMillis` for who sets its rate. `Minecraft.tick` for
the ordered contents of a tick. `FramerateLimitTracker.getFramerateLimit` for
the frame cap that is not the option. `Main.main` for how the process starts,
and `Minecraft.close` for the order in which it comes apart.
---
*Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes `tools/verify_names.py`.*
---
# The client level
> Verified against **Minecraft 26.2** · Part X · the same `Level` class the server runs, with its authority removed: what the client really simulates, and what it only pretends to.
Place a repeater on the client and it is *there* — drawn, collidable, part of
the world. Ask the client whether that repeater has a tick scheduled and it
will tell you, confidently, that it does not. `ClientLevel`'s two scheduled-tick
lists are a `BlackholeTickAccess`: it accepts a schedule, drops it, reports
*no* when asked whether a tick is pending, and counts zero. Shared block code
that consults the level therefore gets a wrong answer rather than an error,
and anything that reschedules itself looks inert until the server speaks.
That is the shape of the whole class. `ClientLevel` is not a passive receiver
and not an authority either ([authority](../entities/authority.md) is where
the five predicates that word stands for are set out): it simulates hard — every block entity ticks
regardless of distance, every local block change is relit locally, it keeps
its own clock and free-runs between corrections — while inheriting a set of
shared `Level` methods that have been quietly reduced to constants. Reading
`ClientLevel` is largely a matter of noticing which overrides are empty.
## The cast
| class | what it decides | thread |
|---|---|---|
| `ClientLevel` | what the client simulates, and what it answers when asked | Render thread |
| `ClientChunkCache` | which chunks exist, in a fixed-size array indexed modulo the view diameter | Render thread |
| `ClientLevel.ClientLevelData` | the client's own game time, difficulty, horizon height and void darkness | Render thread |
| `LevelLightEngine` | the light the client computes for itself, unbudgeted | Render thread |
| `LevelExtractor` | the main route from the level to the renderer — pushed *and* pulled | Render thread |
| `ClientPacketListener` | the view radius and simulation distance the server announced | Render thread |
| `TransientEntitySectionManager` | entity storage with no persistence, no chunk save, no index to disk | Render thread |
| `Entity` | whether a position update is a snap or an interpolation | Render thread |
## Where the two levels differ
The comparison is the page. Every row but the last is a method both sides
inherit from the shared hierarchy — `Level` itself, or one of the interfaces
above it — and that one side has hollowed out; the last row is a field.
| shared method | on the server | on `ClientLevel` |
|---|---|---|
| `Level.shouldTickBlocksAt` | ticket range | inherited — unconditionally true |
| `ScheduledTickAccess.scheduleTick` | real `LevelTicks` | both lists are `BlackholeTickAccess` |
| `Level.explode` | the real thing | empty override — particles arrive by packet |
| `LevelAccessor.gameEvent` | vibrations, sculk | empty override |
| `LevelReader.getUncachedNoiseBiome` | generates | returns plains |
| `LevelReader.hasChunk` | asks the source | unconditionally true |
| `Level.setBlocksDirty` | empty | the renderer notification |
| `Level.shouldTickDeath` | true | **stricter** — within the server's simulation distance |
| entity storage | persistent: a disk store, known UUIDs, per-chunk load states | transient: the same lookup, none of the bookkeeping |
Three of those deserve their own sentence. `LevelReader.hasChunk` returning
true unconditionally means that particular question is useless on the client —
though `Level.isLoaded` still works, because it goes through the chunk source
instead. `Level.explode` doing nothing is why an explosion you can see is not
a simulation: one `ClientboundExplodePacket` carries the sound, the particle
and the knockback, and the handler plays all of it. And `Level.shouldTickDeath` is the only row where the *client*
is the stricter of the two: it uses the server's announced simulation
distance to decide whether a dying mob plays its death animation.
**Two** — the number of things that read the server's announced simulation
distance off `ClientLevel`. That one, and `LevelExtractor`'s render-stats
string. (The client's own `Options.simulationDistance`, which is a different
number, has its own readers.) The value only
ever arrives from the server, on
`ClientPacketListener.serverSimulationDistance`, beside
`ClientPacketListener.serverChunkRadius`; both are seeded at login, updated
by their own packets, and handed to each new `ClientLevel` at construction.
## What it does simulate: the two cadences
**Per client tick**, from `Minecraft.tick`: `ClientLevel.tickEntities`, then
`Level.tickBlockEntities`, then `ClientLevel.tick` — which does
`Level.updateSkyBrightness` unconditionally and then, **only if the tick rate
manager is running normally**, the world border, the clock, the weather
*effects* and the breaking-progress sweep. After that, unconditionally again:
the sky flash countdown, the End flash state, and the explosion tracker.
`ClientLevel.animateTick` and `ParticleEngine.tick` follow, both gated on the
game not being frozen.
So "every block entity ticks, at any distance" is true of *distance* and
false of `/tick freeze`: `Level.shouldTickBlocksAt` is unconditionally true
here, but `Level.tickBlockEntities` still checks the tick rate manager, and
the loop skips the whole tick while paused.
**Per frame**, and only per frame: `ClientLevel.update`, which calls
`ClientLevel.pollLightUpdates` and then runs the light engine. It is gated on
the game being loaded, the level existing, and the frame being one that
advances game time — so the blocking loops that draw a frame without ticking
do no lighting either.
The light budget is a cliff rather than a slope, and it budgets the wrong
half of the work on purpose. Below `ClientLevel.LIGHT_UPDATE_QUEUE_SIZE_THRESHOLD`
the frame runs a tenth of `ClientLevel.lightUpdateQueue`, floored at
`ClientLevel.NORMAL_LIGHT_UPDATES_PER_FRAME`; at the threshold or above it
runs the entire queue. Then `LevelLightEngine.runLightUpdates` drains the
engine's own propagation queue **completely, every frame, with no budget at
all**. The budget controls how fast the client accepts the *server's* light,
not how fast it computes its own — and a chunk-load burst therefore produces
one long frame rather than a hundred slightly late ones.
## A chunk arrives
The grounding trace, and the one place the whole class is visible at once.
```mermaid
sequenceDiagram
participant CPL as ClientPacketListener
participant CCC as ClientChunkCache
participant CL as ClientLevel
participant LLE as LevelLightEngine
participant LX as LevelExtractor
CPL->>CPL: handleLevelChunkWithLight — already hopped to the client thread
CPL->>CCC: replaceWithPacketData — blocks now
CCC->>CCC: inRange? out-of-range chunks are logged and thrown away
CCC->>CL: unload(old) if the torus slot was occupied
CCC->>CL: onChunkLoaded — four tint caches invalidated, entityStorage.startTicking
CPL->>CL: queueLightUpdate(lambda) — light later
Note over CL: any ticks this frame owes, which above 20 fps is usually none
CL->>CL: tickEntities, then Level.tickBlockEntities — the new chunk's block entities tick at once
Note over CL: still inside the same runTick, in renderFrame
CL->>CL: update, then pollLightUpdates — the queued lambda finally runs
CPL->>LLE: applyLightData, then enableChunkLight, whose last act is setSectionRangeDirty over a 3x3 of columns
CL->>LLE: runLightUpdates (unbounded)
CCC->>LX: onLightUpdate, then setSectionDirty — straight to the extractor, bypassing the level
```
Three things about the shape. **Blocks and light are separated in the
handler**, so a chunk exists, ticks and can be walked on before it is lit.
**The separation is not a wait**: both notes fall inside the one
`Minecraft.runTick` that handled the packet, and above twenty frames a second
the frame usually owes no tick at all, so the light is applied with nothing
having ticked in between. And **the renderer is reached two ways** —
the level pushes (`ClientLevel.sendBlockUpdated`, `ClientLevel.setBlocksDirty`,
`ClientLevel.setSectionRangeDirty`), but the chunk cache and one packet
handler call `LevelExtractor` directly, and the extractor also *pulls*:
`ClientLevel.entitiesForRendering`, `ClientLevel.destructionProgress` and
`ClientLevel.getGloballyRenderedBlockEntities` are read each frame and are
mutated with no notification at all.
Unloading is the same trace backwards: `ClientChunkCache.drop` clears the
slot, `ClientLevel.unload` clears the chunk's block entities and stops
ticking its entities, and a light removal is queued for a later frame.
## The chunk cache is a torus
`ClientChunkCache` is not a map. `ClientChunkCache.Storage` is a flat
`AtomicReferenceArray` whose side is the view diameter, indexed by the chunk
coordinates *modulo* that diameter — so moving the origin evicts the ring
behind you by overwriting it. The array is atomic, the two centre coordinates
are declared *volatile*, and so is the reference to the
`ClientChunkCache.Storage` itself,
because the packet handlers are not the only readers. The section-compile
workers are: a `RenderSectionRegion` resolves biome tint *live* rather than
from its snapshot, so `RenderSectionRegion.getBlockTint` goes through
`ClientLevel.getBlockTint` into the chunk cache from a background thread while
the main thread is moving the origin. The `ThreadLocal` and the read-write
lock inside `BlockTintCache` are the same contract said out loud.
`ClientChunkCache.calculateStorageRange` makes the array a few rings wider
than the view distance, `ClientChunkCache.Storage.inRange` and
`ClientChunkCache.Storage.getIndex` decide where a chunk lands, and
`ClientChunkCache.updateViewCenter` moves the origin by assigning two
integers. Its verbs are `ClientChunkCache.replaceWithPacketData`,
`ClientChunkCache.drop`, `ClientChunkCache.replaceBiomes`,
`ClientChunkCache.updateViewRadius` and `ClientChunkCache.onLightUpdate`,
and it keeps four delta sets — `ClientChunkCache.addedEmptySections`,
`ClientChunkCache.removedEmptySections`, `ClientChunkCache.addedLoadedChunks`
and `ClientChunkCache.removedLoadedChunks` — double-buffered by
`ClientChunkCache.flipUpdateTrackingSets` so the renderer can ask what
changed since last frame.
## Who interpolates, and who snaps
An entity position arriving from the server does not simply become the
entity's position. `Entity.moveOrInterpolateTo` asks
`Entity.getInterpolation` for an `InterpolationHandler`; if there is one the
new position is handed to it as a target, and if there is not the position,
yaw and pitch are assigned directly. The base implementation returns
**null**, so the default across the entity tree is to snap, and interpolation
is opted into by exactly seven overrides.
| supplies an `InterpolationHandler` | snaps |
|---|---|
| `LivingEntity` — so every mob and every remote player | `AbstractArrow` and the other projectiles |
| `Display` | `PrimedTnt` |
| `ExperienceOrb` | `ItemEntity` — a dropped item |
| `Shulker` | `FallingBlockEntity` |
| `FishingHook` | everything else that does not override |
| `AbstractBoat` and `AbstractMinecart` | |
That table is the reason a dropped item's movement looks different from a
mob's over the same connection: nothing is smoothing it. The handler itself
— its three-tick window, and the 64-block distance past which
`ClientPacketListener` does not hand it the move at all and snaps instead — belongs to [movement and
collision](../entities/movement-and-collision.md); what this page owns is
*who has one*. `Entity.isInterpolating` is the question
`ServerboundMoveVehiclePacket` and `PositionMoveRotation` both ask before
deciding whether to publish the interpolation's target or the entity's
current position.
## Questions players ask
**Does the client model the speed of sound?** For a few sounds, yes — and
not for the one you would expect. `ClientLevel.playLocalSound` takes a
*distance delay* flag, and when it is set and the source is more than ten
blocks away the sound is deferred by its distance over a fixed rate. Firework
explosions set it, and so do a handful of level events — the trial spawner,
the vault, a cobweb placed. Thunder does **not**: `LightningBolt` passes the
flag as false, so the crack is instant and what makes it feel late is the
lightning being drawn first. See [the sound engine](sound-engine.md).
**Why do I hear my own footsteps instantly on a laggy server?**
`ClientLevel.playSeededSound` plays a sound locally when the *excluded*
player is the local one. The server tells everyone else and the client
produces its own copy. What lags is what you hear of other people.
**Who decides how hard it is raining?** The server, one hundredth at a
time. Weather on the client is presentation only:
`ClientLevel.tickWeatherEffects` spawns rain particles and picks rain sounds,
while the rain and thunder *levels* are ramped on the server by ±0.01 a tick
and broadcast on every tick they change — about a hundred packets across a
five-second transition. What the client does not do is interpolate *within* a
tick: `Level.setRainLevel` writes the old and new values to the same number,
so the partial tick buys nothing and the level steps twenty times a second
rather than smoothly.
**Why does the clock in a screenshot disagree with the server's?** The
client keeps its own. `ClientLevel.tickTime` increments
`ClientLevel.ClientLevelData.gameTime` unconditionally every tick and hands
the result to a `ClientClockManager` owned by `ClientPacketListener` and
reached through `ClientLevel.clockManager`.
`ClientLevel.setTimeFromServer` is the only correction, and its only caller
is `ClientPacketListener.handleSetTime`.
**Why does the crack overlay on someone else's block lag?** The
breaking-progress sweep over `ClientLevel.destroyingBlocks` and
`ClientLevel.destructionProgress` only runs on every twentieth tick. It is
approximate by construction.
**Why does the ambient particle load not scale with my machine?**
`ClientLevel.animateTick` samples 667 positions at radius sixteen and another
667 at radius thirty-two, every tick, regardless. `ClientLevel.doAddParticle`
culls by distance afterwards and can stochastically downgrade the particle
setting further.
## What else it holds, and what it will not tell you
`ClientLevel.tickingEntities` is an `EntityTickList`, fed by the callbacks
`ClientLevel.entityStorage` — a `TransientEntitySectionManager` — invokes when
a chunk starts or stops ticking.
`ClientLevel.tintCaches` holds four `BlockTintCache`s — grass, foliage, dry
foliage, water. `ClientLevel.globallyRenderedBlockEntities` is the set that
draws from anywhere, populated by `ClientLevel.onBlockEntityAdded`.
`ClientLevel.explosionTracker` is a `ClientExplosionTracker`, a per-tick
budget of at most 512 block particles that empties itself every tick rather
than deferring anything. `ClientLevel.blockStatePredictionHandler` is the
ledger [prediction and acknowledgement](prediction-and-acks.md) owns, and
`ClientLevel.levelExtractor` is the push half of the route to the renderer.
The client runs a real light engine — block light always, sky light only
where the dimension has it — and every client-side `Level.setBlock` relights,
but only when `LightEngine.hasDifferentLightProperties` says so — which is
emission or dampening differing, *or* either state using its shape for light
occlusion, so a purely cosmetic change to a stair or a slab relights anyway.
The test is in shared `LevelChunk` code, so the server applies it too. Its
collision world, on the other hand,
is one entity wide: `ClientLevel.getPushableEntities` returns at most the
local player.
And `ClientLevel` never notifies `LevelRenderer`. It has no reference to it,
and not one per-block or per-section dirty method is left on `LevelRenderer` —
they are all on `LevelExtractor` now. What `LevelRenderer` keeps is
whole-world invalidation, `LevelRenderer.invalidateCompiledGeometry` and its
neighbours, which the extractor calls.
> **For a 1.21-era reader.** Gone: *ClientLevel.levelRenderer*, every dirty
> method on *LevelRenderer* (now on `LevelExtractor`),
> *ClientLevel.getStarBrightness* and *ClientLevel.effects* (both now
> `EnvironmentAttribute` lookups — see [environment attributes and
> timelines](../world/environment-attributes-and-timelines.md)), and
> *ClientChunkCache.ChunkArray*.
## Where to look
`ClientLevel.tick` and `ClientLevel.update` for the two cadences.
`ClientChunkCache.Storage` for the torus, and
`ClientChunkCache.replaceWithPacketData` for what a chunk packet actually
does. `ClientLevel.pollLightUpdates` for the budget arithmetic.
`ClientLevel.tickEntities` for the entity walk and `ClientLevel.EntityCallbacks`
for what joins the ticking set. `Entity.moveOrInterpolateTo` for the
snap-or-smooth fork. Then read the empty overrides — `ClientLevel.explode`,
`ClientLevel.gameEvent`, `ClientLevel.getBlockTicks` — because they are the
page in miniature.
---
*Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes `tools/verify_names.py`.*
---
# Prediction and acknowledgement
> Verified against **Minecraft 26.2** · Part X · a block placed against a wall the server will not allow: the client shows it, the server refuses it, and a numbered receipt decides when the lie ends.
The client cannot wait a round trip to show you the block you just placed, so
it places it locally and tells the server afterwards. Everybody who has
described this system has described it as a rollback: the client guesses, the
server judges, the ack says yes or no. That is not what happens.
**`ClientboundBlockChangedAckPacket` is a receipt for a number, not a verdict
on an action.** It is sent for actions the server refused exactly as it is
sent for actions it allowed, and for an aborted dig it is sent carrying zero.
What makes the system correct is an ordering rule instead: any correction the
server intends travels *earlier in the stream* than the receipt for it,
because the correction is sent from inside the handler while the receipt is
only a number the connection flushes later.
Part V's [block interaction](../blocks/block-interaction.md) and [block
breaking](../blocks/block-breaking.md) are the two applications, and both
carry the same four-sentence statement of that contract. This page is the
machinery underneath: one ledger per level, one counter per connection, six
windows in which a prediction can be opened, and four methods that between
them decide whether the world moves.
## The cast
| class | what it decides | thread |
|---|---|---|
| `MultiPlayerGameMode` | when a prediction window opens, and what goes in it | Render thread |
| `BlockStatePredictionHandler` | the ledger: what was there before, under which sequence number | Render thread |
| `ClientLevel` | the four writes — the prediction, the absorbed correction, the trigger, the settle | Render thread |
| `PredictiveAction` | a one-method interface: given the sequence, produce the packet | Render thread |
| `ServerGamePacketListenerImpl` | one integer per connection, and when it is emitted | Server thread |
| `ServerPlayerGameMode` | the authoritative version of the same action | Server thread |
## Two state machines, running against each other
The whole mechanism is one small state machine on each side, and neither
knows the other's state. The client's runs per *position*; the server's runs
per *connection*.
```mermaid
stateDiagram-v2
state "Client — one position in the ledger" as CLIENT {
[*] --> Absent
Absent --> Retained : setBlock inside a window, filed under sequence n
Retained --> Retained : setBlock again here, only the sequence is refreshed
Retained --> Corrected : setServerVerifiedBlockState, the entry is overwritten and the world is untouched
Retained --> [*] : endPredictionsUpTo(n), nothing overwrote it, so syncBlockState puts the old state back
Corrected --> [*] : endPredictionsUpTo(n), syncBlockState writes the absorbed state, which is a no-op if it is already on screen
}
state "Server — one integer per connection" as SERVER {
[*] --> Idle
Idle --> Raised : ackBlockChangesUpTo(n), the maximum of current and incoming
Raised --> Raised : another acked action in the same tick
Raised --> Idle : emitted at the head of ServerGamePacketListenerImpl.tick, then back to minus one
}
```
Read the two columns as running at different rates. The client's machine
advances several times per tick, once per position touched. The server's
advances on every packet that reaches a
`ServerGamePacketListenerImpl.ackBlockChangesUpTo` call — the two
use packets, and the three destroy actions of `ServerboundPlayerActionPacket`
but not its other five — and *empties* once per connection tick, which is why
five acked actions in one tick produce one receipt carrying the highest
number, and why one ack can drive a dozen positions out of the ledger at once.
Note what the diagram does not contain: any transition on which the server
says *no*. There is none. Both of the client's exit transitions are driven by
the same packet, and which of the two a position takes is decided entirely by
whether a block update reached it first.
## The four writes
Everything the ledger does happens through four methods on `ClientLevel`, and
the difference between them is the whole mechanism.
**`ClientLevel.setBlock`** — the ordinary write. While a prediction is open,
and only if the write succeeded, it calls
`BlockStatePredictionHandler.retainKnownServerState` with the state that was
there *before*. If the position already has an entry, only the sequence is
refreshed: the ledger keeps the **first** pre-change state it ever saw for
that position, and the player position recorded with it.
**`ClientLevel.setServerVerifiedBlockState`** — every inbound block update
goes through here. If the position is in the ledger it overwrites the entry
and the world is not touched, so the prediction stays on screen. If it is
**not** in the ledger — the common case, for blocks the player did not touch
— it writes the world immediately. The absorption is per position, not per
packet.
**`ClientLevel.handleBlockChangedAck`** — the trigger. The ledger's only
entry point from the network: it hands the receipt's number straight to
`BlockStatePredictionHandler.endPredictionsUpTo`, which removes every entry at
or below it and passes each one's recorded state to the settle.
**`ClientLevel.syncBlockState`** — the settle. Applies the recorded state
only if it differs from what is there, with flags `Block.UPDATE_NEIGHBORS`
plus `Block.UPDATE_CLIENTS` plus `Block.UPDATE_KNOWN_SHAPE`. That third flag
suppresses the shape pass, and neighbour updates are inert on the client
anyway — so the restore is a bare state write plus a remesh. **The cascade
that produced the prediction does not re-run on the way back.**
Reconciliation is correct only because every position the cascade touched got
its own ledger entry on the way out.
## A placement the server refuses
The state diagram says what the states are; this says what order the packets
arrive in, which is the part correctness actually rests on.
```mermaid
sequenceDiagram
participant MPGM as MultiPlayerGameMode
participant BSPH as BlockStatePredictionHandler
participant CL as ClientLevel
participant SGPL as ServerGamePacketListenerImpl
participant SPGM as ServerPlayerGameMode
MPGM->>BSPH: startPredicting — currentSequenceNr becomes n
MPGM->>CL: performUseItemOn, then ItemStack.useOn, then setBlock
CL->>BSPH: retainKnownServerState(pos, air, LocalPlayer) — the truth, filed under n
MPGM->>SGPL: ServerboundUseItemOnPacket(hand, hit, n)
MPGM->>BSPH: close — the window shuts and the block is on screen
SGPL->>SGPL: hasClientLoaded? then ackBlockChangesUpTo(n) — the first statement
SGPL->>SPGM: useItemOn — the place fails canPlace, so nothing changes
SGPL->>CL: two ClientboundBlockUpdatePackets, sent whatever the outcome — the clicked block and the one past its face
CL->>BSPH: updateKnownServerState — the entry is overwritten, the world is not
Note over SGPL: the connection phase of the next tickChildren
SGPL->>CL: ClientboundBlockChangedAckPacket(n)
CL->>BSPH: endPredictionsUpTo(n), then syncBlockState — air goes back
CL->>CL: Entity.absSnapTo on the LocalPlayer — only if the restored block now intersects it
```
The ack is recorded **before** the action is attempted, so by the time the
refusal happens the receipt is already promised. The correction is not the
ordinary chunk broadcast but a targeted pair of resends the handler makes
unconditionally, which is what actually puts it ahead of the receipt: the
resends go out inside the handler, while the ack is only a field assignment
that `ServerGamePacketListenerImpl.tick` flushes later. The settle is what
finally moves the world, and it is a no-op when the absorbed state is already
what is on screen. And the snap only happens when the restored block turns
out to be inside the player.
Because the ack is buffered rather than sent, *where* a handler records it
does not affect stream order — `ServerGamePacketListenerImpl.handleUseItemOn`
and `ServerGamePacketListenerImpl.handleUseItem` record it as their first
statement, `ServerGamePacketListenerImpl.handlePlayerAction` after running the
break action, and in both cases the correction still reaches the client first.
## The six windows
A window is a few microseconds long and entirely synchronous: on the client
thread, inside one call from `Minecraft.tick`. The counter is raised, the
local effect runs, the packet is built with the new sequence and sent, and the
window closes — in that order, so the packet is constructed while the ledger
is still recording. `MultiPlayerGameMode.startPrediction` is the private
method all six go through, and `ClientLevel.getBlockStatePredictionHandler`
is package-private, so nothing outside `client/multiplayer` can reach the
ledger at all.
| where | what it predicts locally | what it sends |
|---|---|---|
| `MultiPlayerGameMode.startDestroyBlock`, creative | the block removed at once | `ServerboundPlayerActionPacket` START |
| `MultiPlayerGameMode.startDestroyBlock`, survival | `BlockBehaviour.attack`, then removal if the block breaks instantly | START |
| `MultiPlayerGameMode.continueDestroyBlock`, creative | removal, every sixth tick | START |
| `MultiPlayerGameMode.continueDestroyBlock`, at full progress | removal | STOP |
| `MultiPlayerGameMode.useItemOn` | `MultiPlayerGameMode.performUseItemOn` — the block's hook, the empty-hand hook, `ItemStack.useOn` | `ServerboundUseItemOnPacket` |
| `MultiPlayerGameMode.useItem` | `ItemStack.use`, including a transformed held item | `ServerboundUseItemPacket` |
So the ledger covers rather more than "blocks the player placed or broke": it
covers **every** `ClientLevel.setBlock` performed inside one of those windows.
That includes the second half of a door, every position a shape cascade
revisits, and — the only case of its own — `RedStoneOreBlock.attack`, whose
lighting change is not side-gated and therefore files a ledger entry when you
merely left-click redstone ore.
## What the ledger does not cover
Half the value of this page is the list of things people assume it handles.
`MultiPlayerGameMode`'s non-predicting verbs are the giveaway:
`MultiPlayerGameMode.attack`, `MultiPlayerGameMode.interact`,
`MultiPlayerGameMode.stopDestroyBlock`,
`MultiPlayerGameMode.releaseUsingItem`,
`MultiPlayerGameMode.piercingAttack` and every container verb open no window
at all.
- **Breaking progress.** `MultiPlayerGameMode.destroyProgress` and its
companions are a plain parallel clock with no sequence and no
reconciliation; the crack overlay is written straight into the client
level. Of the progress clock itself nothing is predicted — only the
removals at either end of it are.
- **Item use.** `MultiPlayerGameMode.useItem` opens a window, but a consumed
item, a started use and a cooldown are not block states and the ack does
nothing for them. They are corrected by other means: the living-entity
flags in synched data, `ClientboundCooldownPacket`, and the menu resend the
server performs when the stack changed.
- **Dropping.** `LocalPlayer.drop` predicts the removal from the selected
slot and sends its action packet with sequence **zero** — a local mutation
with no rollback path at all.
- **Movement.** Rubber-banding is a different mechanism entirely: an
id-matched teleport handshake with no sequence and no ledger, described in
[input to movement](../player/input-to-movement.md). The two systems touch
at one point in each direction — `ClientPacketListener.handleMovePlayer`
calls `BlockStatePredictionHandler.onTeleport`, so a teleport disarms the
ledger's position snap, and the settle calls `Entity.absSnapTo` on the
`LocalPlayer` when
the restored block is inside you.
## Questions players ask
**Why does the block come back and then vanish again?** Your client finished
its own progress clock first. It fires the STOP window and removes the block
locally, but the server recomputes the progress itself, finds it under
`0.7F`, and takes the delayed-destroy branch instead of breaking anything.
The action is acknowledged all the same, so the client settles the entry,
restores the stone, and the block reappears — until the server's own delayed
destroy completes a few ticks later and broadcasts air. Releasing the mouse
does *not* do this: that is `MultiPlayerGameMode.stopDestroyBlock`, which
opens no window at all.
**Why did that ack arrive with a zero in it?** The three-argument
`ServerboundPlayerActionPacket` constructor defaults the sequence to zero,
and aborting a dig uses it. The server dutifully sends
`ClientboundBlockChangedAckPacket` carrying zero, which settles nothing —
`BlockStatePredictionHandler.startPredicting` pre-increments, so the first
real sequence is one and no genuine prediction is ever numbered zero.
**Can a wrong guess get stuck on screen forever?** Yes. There is no timeout
and no cap: nothing clears the ledger except a settle, and while the server
considers the client not yet loaded, sequenced packets are dropped *before*
the ack is recorded. Predictions accumulate and the client's guess stands.
The only reset is a new `ClientLevel`.
**Does one ack do one thing?** It does three. Every entry at or below the
acknowledged sequence is removed and handed to the settle, and there the
paths part: write nothing, because the recorded state is already on screen
(the correct prediction); write the state; or write the state and snap the
player. A single ack can produce all three across the map in one pass. And
`BlockStatePredictionHandler.lastTeleportSequence` is compared against the
*acknowledged* sequence rather than each entry's, and is never reset, so one
teleport suppresses a whole batch of snaps.
**Why can I not right-click while mining?** `Minecraft.startUseItem` is gated
on `MultiPlayerGameMode.isDestroying`. Spectators are the odd case in the
other direction and are asymmetric about it:
`MultiPlayerGameMode.useItem` returns early, before the window opens, while
`MultiPlayerGameMode.useItemOn` returns *inside* it — so the sequence is
burned and the packet is sent anyway.
One last asymmetry worth carrying away: the outbound write and the inbound
restore use different flags. The predicted removal in
`MultiPlayerGameMode.destroyBlock` writes with `Block.UPDATE_IMMEDIATE` in
the mix; the restore writes with `Block.UPDATE_KNOWN_SHAPE`. One cascades,
the other deliberately does not.
## Where to look
`MultiPlayerGameMode.startPrediction` — the whole client side is that one
private method and its six call sites. `BlockStatePredictionHandler` end to
end; it is under a hundred lines and every one of them matters, including
`BlockStatePredictionHandler.currentSequence`,
`BlockStatePredictionHandler.isPredicting` and
`BlockStatePredictionHandler.close`. `ClientLevel.setBlock`,
`ClientLevel.setServerVerifiedBlockState`, `ClientLevel.handleBlockChangedAck`
and `ClientLevel.syncBlockState` for the four writes. `ServerGamePacketListenerImpl.ackBlockChangesUpTo` and
`ServerGamePacketListenerImpl.tick` for the receipt — the field and the
method that raises it share a name.
---
*Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes `tools/verify_names.py`.*
---
# Input and keybinds
> Verified against **Minecraft 26.2** · Part X · holding sneak: a GLFW callback, five chances to be swallowed, and a key that stays down while you are not touching it.
Turn on toggle sneak and hold the key. `ToggleKeyMapping.setDown` sees the
press, flips the mapping to *down*, and then swallows the release entirely —
so as far as the rest of the game is concerned you are still holding a key
you let go of. Open your inventory and the mapping is released along with
every other one; close it and the mapping *comes back on*, because the toggle
remembered it was released by a screen rather than by you. The press and the
release never involve the tick at all — they have already happened by the time
the tick that observes them runs. Opening the inventory is the exception that
shows where the seam is: that one is a *drain*, and the drain is inside the
tick.
That is the fact this page is built on. **GLFW callbacks are not queued.**
They are dispatched from inside `RenderSystem.pollEvents`, which
`Minecraft.run` calls immediately before `Minecraft.runTick`; the handlers
wrap their bodies in `BlockableEventLoop.execute`, but on the game thread
that call runs the task rather than queueing it. Any description of Minecraft
input that says a key press is "queued onto the client thread" is describing
a different game.
What the movement keys *mean* once they are down belongs to [input to
movement](../player/input-to-movement.md); this page stops at the mapping.
## The cast
| class | what it decides | thread |
|---|---|---|
| `KeyboardHandler` | the key, character and pre-edit callbacks, and the gauntlet a press runs | Render thread |
| `MouseHandler` | motion accumulation, the sensitivity curve, and who is allowed to turn the player | Render thread |
| `KeyMapping` | whether a mapping is down, and how many clicks are owed | Render thread |
| `ToggleKeyMapping` | the four mappings that can behave as toggles: sneak, sprint, use, attack | Render thread |
| `InputConstants` | the key universe, and the string a binding is saved as | Render thread |
| `InputQuirks` | four platform constants that visibly change behaviour | Render thread |
| `KeyboardInput` | which of the seven movement mappings are down, once per tick | Render thread |
| `Gui` | the housekeeping at both ends of a screen's life | Render thread |
## Holding sneak
```mermaid
sequenceDiagram
participant KH as KeyboardHandler
participant MC as Minecraft
participant KM as KeyMapping
participant Gui as Gui
participant KI as KeyboardInput
participant LP as LocalPlayer
participant MH as MouseHandler
KH->>KH: keyPress — inside glfwPollEvents, on the game thread, not queued
KH->>MC: handleGlobalKeyPress — fullscreen, screenshot, friends: handled here, so usually no click
KH->>Gui: screen keyPressed — a screen that consumes it ends the story here
KH->>KH: keyDebugModifier held? then handleDebugKeys instead
KH->>KM: set(down) and click — only with no screen open
KM->>KM: ToggleKeyMapping.setDown flips instead of following, and swallows the release
Note over MC: next client tick
LP->>KI: tick — from LocalPlayer.aiStep, reads isDown on seven mappings, not consumeClick
KI->>KI: the Input record, then a normalised move vector
Note over Gui: and when a screen opens
Gui->>MH: releaseMouse
Gui->>KM: releaseAll — every mapping goes up, and the toggle notes that a screen did it
Note over Gui: and when that screen closes
Gui->>KM: restoreToggleStatesOnScreenClosed — sneak comes back on
Gui->>MH: grabMouse
```
**A key press has five chances to be swallowed before it counts** — the
global-key check, an open screen, the debug modifier, the no-screen gate on
recording, and the no-screen-no-overlay gate on draining. And the two ends of
a screen's life are where the input system does its housekeeping: opening a
screen releases every mapping, and closing one restores those toggles that
asked to be restored — with default bindings, sneak and sprint, and only when
there is a level.
## Two ways gameplay reads a mapping, and they behave differently
`KeyMapping.isDown` is a boolean the movement code samples. `KeyboardInput.tick`
reads seven of them — forward, back, left, right, jump, sneak, sprint — once
per client tick, packs them into an `Input` record and derives a normalised
move vector. Nothing is consumed; a key held for ten ticks reads down ten
times.
`KeyMapping.consumeClick` is a **drain, not an edge**. It decrements a
counter that `KeyMapping.click` incremented, which means presses that
happened faster than the tick rate are not lost — and that binding two
*mappings* to one key code fires both, because `KeyMapping.click` increments
every mapping registered under that key. `Minecraft.handleKeybinds` is the
drain, and
it runs from `Minecraft.tick` only when there is neither a screen nor an
overlay.
`KeyMapping.matches` and `KeyMapping.matchesMouse` are the third way, used
where no counter is wanted: they test an event against the binding directly,
which is what screens do, and what `KeyboardHandler.handleDebugKeys` does
nineteen times over for the F3 combinations. `KeyMapping.same`,
`KeyMapping.isDefault` and
`KeyMapping.isUnbound` are what the binding screen asks.
## The bulk operations, and their single callers
`KeyMapping` keeps two static registries of every mapping ever constructed —
one by name, one by key — which is how a key code is turned back into the
mappings that want it. Five static operations walk them. Four of the five are
called from exactly one place each, and those four are the interesting ones —
the fifth, `KeyMapping.resetMapping`, is the binding screen's own reset and
has two callers.
| operation | called from | why |
|---|---|---|
| `KeyMapping.releaseAll` | `Gui.setScreen` | a screen may swallow a release, and a stuck-held mapping is worse than a lost press |
| `KeyMapping.restoreToggleStatesOnScreenClosed` | `Gui.setScreen` | put back the toggles that the release above turned off |
| `KeyMapping.resetToggleKeys` | `LocalPlayer.respawn` | you should not wake up sneaking |
| `KeyMapping.setAll` | `MouseHandler.grabMouse` | only where `InputQuirks.RESTORE_KEY_STATE_AFTER_MOUSE_GRAB` is set |
The asymmetry between a swallowed press and a swallowed release is worth
stating plainly, because it is the reason the first two rows exist. A press a
screen swallows is harmless: the mapping was never set down. A *release* a
screen swallows leaves the mapping down with nothing to clear it.
## The mouse: accumulate, apply, discard
`MouseHandler.onMove`, `MouseHandler.onButton`, `MouseHandler.onScroll` and
`MouseHandler.onDrop` are the callbacks; `MouseHandler.accumulatedDX` and
`MouseHandler.accumulatedDY` are the pending motion; and
`MouseHandler.handleAccumulatedMovement` applies it — from `Minecraft.runTick`,
between the sound update and the frame, **once per frame rather than once per
tick**. So a look is applied at frame rate and a step is applied at tick rate,
on the same input device.
Accumulated motion goes to whatever is in front of it and is then cleared
unconditionally. With a screen open the delta goes to the screen's move and
drag handlers, and not to the player — not because the two are exclusive in
`MouseHandler`, which tests them separately, but because opening a screen
released the mouse. With the window unfocused nothing
accumulates in the first place; and the reset at the end runs either way, so
motion is never banked.
`MouseHandler.turnPlayer` holds the sensitivity curve, and it has an
arithmetic surprise in it. The curve is a **cube** of the slider, and the
ordinary and smooth-camera paths multiply the result by eight while the
scoped path does not — so **aiming a spyglass is exactly eight times slower,
by construction.** The scoped path additionally requires the smooth camera to
be off, the camera to be first-person, and the player to be actually scoping.
Minimum sensitivity is not zero either: the cubed term is taken of the slider
scaled and offset, so the slowest setting still turns.
`MouseHandler.grabMouse` and `MouseHandler.releaseMouse` are two directions
of one edge with `Gui.setScreen`, guarded so the two cannot recurse —
though only `MouseHandler.releaseMouse` has the single caller;
`MouseHandler.grabMouse` has five, and also clears the screen. `MouseHandler.isMouseGrabbed` is the state,
`MouseHandler.setIgnoreFirstMove` suppresses the jump after a resize, and
`MouseHandler.simulateRightClick` is macOS-only and fires on a
control-modified left click rather than on a long one, whatever its constant
is called.
Double-click is a threshold plus two identities: the two clicks must be
within a quarter of a second, on the same button, **and** on the same screen
instance.
## Questions players ask
**I bound two things to one key and both happen.** They will. Conflict
detection lives only in the binding screen and is purely cosmetic — nothing
in the input path resolves a collision. It also refuses to flag one when
*both* mappings are still at their defaults, which quietly exempts the pairs
the game itself ships colliding.
**Why does F3 toggle on release?** F3 is a key binding, and the debug
modifier and the overlay toggle are the same key by default — so the overlay
can only fire on the release, and only when no combination was used in
between. Rebind either and that behaviour disappears.
**Why can I not rebind that debug shortcut?** Twenty debug shortcuts are
ordinary mappings in the debug-keys array. A second family is a raw switch on
key codes in `KeyboardHandler.handleChunkDebugKeys`, gated on the game's
debug flag and bindable to nothing.
**Can a mod add a keybind category?** `KeyMapping.Category` is a registrable
record, not an enum, so yes; registering a duplicate id throws. Ordering is
plain registration order into one list — the eight built-ins come first only
because the record's own static initialiser registers them first.
Almost nothing on this page sends a packet, and the exceptions are worth
naming because they are the shortcuts: `KeyboardHandler` sends
`ServerboundChangeGameModePacket` for F3+N, and `Minecraft.handleKeybinds`
sends the swap-offhand action straight out of the drain. Everything else a
key press means reaches the server later and by an entirely different route.
The two smaller supporting types are `ScrollWheelHandler`, the wheel's
accumulator, and `InputType`, the four-valued "what did the player last use"
that decides initial keyboard focus, narration timing and whether a focused
widget shows its tooltip.
> **For a 1.21-era reader.** Input events are **records** now. The
> `(key, scancode, modifiers, action)` integer tuple is gone from every
> screen signature, replaced by `KeyEvent`, `MouseButtonEvent`,
> `CharacterEvent` and `PreeditEvent` in `client/input`, with shared helpers
> on `InputWithModifiers` — so a widget asks an event whether it *is* a
> confirmation or a paste rather than decoding modifiers itself. Also gone:
> *Options.keyBindings* (now the key-mappings array), categories as
> translation-key strings, and *MouseHandler.lastMouseEventTime*.
## Where to look
`KeyboardHandler.keyPress` — the whole gauntlet is one method, read top to
bottom. Then `ToggleKeyMapping` end to end, which is under sixty lines and
explains
this page's opening paragraph; `Minecraft.handleKeybinds` for the drain;
`KeyboardInput.tick` for the other way a mapping is read; `Gui.setScreen` for
the housekeeping at both ends of a screen; and `MouseHandler.turnPlayer` for
the sensitivity curve and its three gates.
---
*Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes `tools/verify_names.py`.*
---
# Options
> Verified against **Minecraft 26.2** · Part X · the render-distance slider: a value that takes effect on a delay, rebuilds the world on a later frame, and reaches the server only when a screen closes — with no reply.
There is no settings-changed hook in the Minecraft client. **Saving is the
event system.** The server learns your new view distance because something
called `Options.save`, and `Options.save` is the only caller of
`Options.broadcastOptions` — which sounds like a tight, tidy rule until you
notice that every cycle-option button calls `Options.save` on click. Change
chat visibility and your `ClientInformation` is on the wire before you have
left the screen; drag the render-distance slider and nothing is sent until
the screen closes.
This page is the policy: what a setting *is*, when a change takes effect, who
finds out, and what the server does with the nine fields it is told. The
bindings that also live on `Options` are [input and
keybinds](input-and-keybinds.md).
## The cast
| class | what it decides | thread |
|---|---|---|
| `Options` | the file, the fields, and when to save | Render thread |
| `OptionInstance` | one setting: value, codec, caption, listener, and the widget | Render thread |
| `OptionInstance.ValueSet` | the legal values — and, by its subtype, whether the widget saves on click | Render thread |
| `ClientInformation` | the nine-field record the server is told, kept in `server/level` | crosses the wire |
| `LevelExtractor` | notices, next frame, that the effective render distance changed | Render thread |
| `ChunkMap` | clamps the request and re-tracks, silently | Server thread |
| `IntegratedServer` | the singleplayer back door: reads the sliders directly, every tick | Server thread |
## What happens when a setting changes
Four decisions, and none of them is "run the listener and be done".
```mermaid
flowchart TD
CHANGE["a widget changes a value"]
KIND{"slider or cycle?"}
ARM["slider that opted out of applying immediately: arm 600 ms, checked during the extract pass"]
EARLY{"screen dismissed inside the window?"}
SET["OptionInstance.set"]
RUNNING{"is Minecraft.running true?"}
SILENT["assign the field, skip the equality test and the listener — this is what loading does"]
LISTEN["run the listener"]
SAVE["Options.save — write options.txt"]
BCAST["Options.broadcastOptions — build a ClientInformation"]
SAME{"identical to the last one sent?"}
SEND["ServerboundClientInformationPacket"]
NOTHING["nothing is sent"]
CHANGE --> KIND
KIND -- "slider" --> ARM --> EARLY
EARLY -- "yes, apply at once" --> SET
EARLY -- "no, apply on a later frame" --> SET
KIND -- "cycle: apply now" --> SET
SET --> RUNNING
RUNNING -- "no" --> SILENT
RUNNING -- "yes" --> LISTEN
LISTEN --> SAVE
SAVE --> BCAST --> SAME
SAME -- "yes" --> NOTHING
SAME -- "no" --> SEND
```
The cycle branch reaches `Options.save` on every click; the slider branch
reaches it when the screen closes. That single asymmetry is the whole
behaviour difference between the two widget families, and it comes from the
value set's subtype: `OptionInstance.SliderableValueSet` against
`OptionInstance.CycleableValueSet`.
## The three ways a setting is stored
`OptionInstance` is the modern shape — a value, a codec, an initial value, a
caption, a listener, and an `OptionInstance.ValueSet` that decides both the
legal values and the widget. The concrete sets are
`OptionInstance.IntRange`, `OptionInstance.ClampingLazyMaxIntRange`,
`OptionInstance.UnitDouble`, `OptionInstance.Enum`,
`OptionInstance.LazyEnum`, `OptionInstance.AltEnum` and
`OptionInstance.SliderableEnum`.
**Plain fields** are the older shape, read and written by name in
`Options.processOptions`: the language code, the resource-pack lists,
`Options.tutorialStep`, `Options.smoothCamera`,
`Options.advancedItemTooltips`, `Options.joinedFirstServer`,
`Options.startedCleanly` and a dozen others. They have no listener and no
widget machinery at all — and `Options.smoothCamera`, which a keybind
toggles, is not persisted anywhere.
**The key-mapping array** is the third, and is the input page's subject.
`Options.processOptions` is worth naming twice: it is the file format and the
field list in one method, which is why it is the first place to look for
anything about *options.txt*. `Options.dumpOptionsForReport` and
`Options.processDumpedOptions` are the smaller subset that goes into the
profiling report.
## The delay, and why the world rebuilds anyway
The render-distance slider's listener does exactly one thing: it marks the
graphics preset custom. It does not invalidate a single chunk. It is also one
of only three options in the game that defer their value at all — with
simulation distance and biome blend, it is built to *not* apply immediately,
which is what arms the 600 ms; every other slider applies on release.
The world rebuilds because `LevelExtractor` notices, on the *next frame*,
that `Options.getEffectiveRenderDistance` differs from the last value it saw,
and calls its own full invalidation — tint caches cleared, the tracker
rebuilt, all geometry dirty. The setting and the consequence are joined by a
poll, not by a callback.
Seven of the other quality options are the opposite way round: their
listeners reach straight into the level extractor. Nine are not — their
listeners do nothing but flip the preset back to custom, exactly as render
distance's does. Elsewhere in the file the immediate listeners are real
enough: the window, the sound device and the font manager all have one. `Options.graphicsPreset` and
`Options.setGraphicsPresetToCustom` are the preset machinery — a preset
writes a batch of settings at once, and almost every graphics listener flips
the preset back to custom, which is how "Custom" appears without anyone
selecting it.
Some of those listeners are far more interesting than their options. GUI
scale resizes whichever screen is open. Vsync invalidates the surface configuration.
Fullscreen toggles the window and then writes the option back from what the
window actually did. The unicode-font toggle throws away every glyph atlas.
High contrast adds and removes a resource pack.
## Who is told, and what is not said
`Options.buildPlayerInformation` assembles a `ClientInformation`: language,
view distance, chat visibility, chat colours, skin model parts, main hand,
text filtering, listing permission, particle status. **Nine fields, and
simulation distance is not one of them** — the record has no place to put it.
The packet is `ServerboundClientInformationPacket`, and it is a *common*
packet rather than a play one: the first is sent during configuration,
straight from `ClientHandshakePacketListenerImpl`, before the play phase
exists. Every later one comes from `Options.broadcastOptions`.
**There is no acknowledgement, and the absence is the point.** The one thing
a client-information packet can provoke is a hat-visibility broadcast to the
whole player list, and nothing in it tells you what happened to what you
asked for. The only thing that ever sets
`Options.serverRenderDistance` is the server announcing its *own* view
distance — in the login packet, or by broadcasting
`ClientboundSetChunkCacheRadiusPacket` when an operator changes it. Your
request is clamped by `ChunkMap.getPlayerViewDistance` and used for chunk
tracking, and you are never told what it was clamped to; the client clamps
itself, with `Options.getEffectiveRenderDistance`.
`ClientboundSetSimulationDistancePacket` is likewise an announcement, not a
reply.
Singleplayer short-circuits half of it. `IntegratedServer.tickServer` reads
both the simulation and render sliders off the client's options every unpaused
server tick and pushes them into the player list, so both drive the server
directly without waiting for a packet. Render distance travels as client
information anyway — it is field two of the record, sent over the memory
connection exactly as over a socket — and simulation distance never does, in
singleplayer or out of it, because that is the one number the client has no
say in.
## Questions players ask
**Why does my render-distance slider stop short?** Its maximum is computed
once, from the JVM's maximum memory, and the option is a plain
`OptionInstance.IntRange` built around that bound. It depends on your heap,
not on your graphics card. (The genuinely lazy
`OptionInstance.ClampingLazyMaxIntRange` is GUI scale's, and reads the
window.)
**Why did none of my settings' side effects run at startup?** Because
`Minecraft.running` is still false. `OptionInstance.set` checks it and, when
the game is not running, assigns the field and skips both the equality test
and the listener. That is not a special path for loading — it silences *any*
set performed before the loop starts, and loading happens in the `Options`
constructor, which runs inside the `Minecraft` constructor five statements
before `Minecraft.running` is set.
**Which settings really need a restart?** Two, and they say so. The graphics
backend and exclusive fullscreen are compared against snapshots taken at
startup, and `Options.isRestartRequiredToApplyVideoSettings` is what the
screen asks. Related: `Options.startedCleanly` is written false at startup
and true once the game is up, so a crash during boot can drop the client back
to a safe backend.
**Why does the tutorial never come back?** Options are per installation,
never per world — including `Options.tutorialStep`. Once any world drives the
tutorial to the end, no later world shows the toasts again, and an
unrecognised step name silently reads as *finished*.
**I edited options.txt and broke it. Why did nothing complain?** Failure is
quiet by design: a bad line is logged and skipped, a bad value for an
`OptionInstance` is logged and dropped back to the initial value, and a bad
number keeps the current one. Nothing about a corrupt *options.txt* stops the
game starting. The file carries a version line and is run through the data
fixer on load.
> **For a 1.21-era reader.** *Options.mouseSensitivity* and the other bare
> public fields are gone — most settings are now private with an accessor of
> the same name, so `Options.fov` and `Options.guiScale` are calls. Two traps:
> mouse sensitivity was *renamed* as well as encapsulated, to
> `Options.sensitivity` (only *options.txt* still says *mouseSensitivity*);
> and *Options.keyBindings* was renamed to `Options.keyMappings` but is still
> a public field, not a call.
## Where to look
`Options.processOptions` for the settings table. `OptionInstance.set` for the
listener guard that explains the loading behaviour, and
`OptionInstance.CycleableValueSet` for the button that saves on click.
`Options.buildPlayerInformation` for the nine fields the server hears.
`ChunkMap.getPlayerViewDistance` for what the server does with the one that
matters, and `IntegratedServer.tickServer` for the singleplayer back door.
---
*Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes `tools/verify_names.py`.*
---
# GUI and screens
> Verified against **Minecraft 26.2** · Part X · pressing E: a screen the server is not told about until you close it, opened onto a menu that was built when you spawned.
Press E in survival and no packet is sent, no packet is received, and nothing
on the server changes. `Player.inventoryMenu` has existed since the player
object was constructed, it has **no `MenuType` at all**, and `MenuScreens`
could therefore never build an `InventoryScreen` from a packet even if one
arrived. The *opening* is entirely a client-side event. Press E again and the
symmetry breaks: `LocalPlayer.closeContainer` sends
`ServerboundContainerClosePacket`, and the server empties your 2×2 crafting
grid on the way out. The screen the server is never told about is one the
server is told about exactly once, at the end.
The menu underneath it is not symmetric in the same way, and the qualification
matters: `InventoryMenu` is constructed on both sides, and its crafting
result is recomputed only on the server — so the client is rendering a result
it did not compute, in a screen the server does not know is open.
This page is what a screen *is*: the manager that holds one, the lifecycle it
runs through, the widget and layout families it is built from, and the four
routes by which one comes to exist. How its contents become pixels is [the
GUI render tree](the-gui-render-tree.md); how a `Component` becomes glyphs is
[text and fonts](text-and-fonts.md).
## The cast
| class | what it decides | thread |
|---|---|---|
| `Gui` | which screen and which overlay exist, and what an absent screen means | Render thread |
| `Screen` | one screen's lifecycle, children, focus and narration | Render thread |
| `AbstractWidget` | the final outer shape of a widget, and one inner hook per subclass | Render thread |
| `Layout` over `LayoutElement` | where widgets end up, re-arranged on most screens whenever the window changes | Render thread |
| `AbstractContainerScreen` | a screen mirroring a server-side menu, and the slot geometry | Render thread |
| `MenuScreens` | `MenuType` to screen class — the only registry of screens in the game | Render thread |
| `Overlay` | suppresses the screen's record pass, its mouse and its typing — but not its key presses | Render thread |
| `ScreenNarrationCollector` | what has already been said, so it is not said twice | Render thread |
## The objects, and what contains what
```mermaid
flowchart TD
Gui["Gui — the manager, once per game"]
Screen["Screen — zero or one"]
Overlay["Overlay — zero or one, and it wins"]
Hud["Hud — reached as Gui.hud"]
Toasts["ToastManager, ChatListener, SplashManager"]
Children["Screen.children — GuiEventListener, gets input"]
Rend["Screen.renderables — Renderable, gets recorded"]
Narr["Screen.narratables — NarratableEntry, gets described"]
Widget["AbstractWidget — usually in all three lists at once"]
Layout["Layout over LayoutElement — arranges, then forgets"]
ACS["AbstractContainerScreen — a Screen with a menu behind it"]
Menu["AbstractContainerMenu — shared with the server"]
Gui --> Screen
Gui --> Overlay
Gui --> Hud
Gui --> Toasts
Screen --> Children
Screen --> Rend
Screen --> Narr
Children --> Widget
Rend --> Widget
Narr --> Widget
Layout --> Widget
Screen --> ACS
ACS --> Menu
```
The three lists on `Screen` are the shape worth remembering: a widget added
with `Screen.addRenderableWidget` joins all three, and the sibling add methods
exist precisely so that something can be in one or two of them and not the
rest. A `Tooltip` is in none of them — it is held by a `WidgetTooltipHolder`
— and `MultiLineLabel` is an interface rather than a widget at all.
## `Gui`, which is not the HUD
`Gui` owns `Gui.screen` and `Gui.overlay` — set through `Gui.setScreen` and
`Gui.setOverlay` — plus `Gui.hud`, `Gui.toastManager`, `Gui.chatListener`,
`Gui.splashManager` and the reference to the frame's render state. It has
**three** cadences, not two: `Gui.tick` once per client tick, `Gui.update`
once per frame — which advances toasts and fires delayed narration — and
`Gui.extractRenderState` once per frame in the record pass. The rest of its
surface is `Gui.isPausing`, `Gui.handleKeybinds`, `Gui.openChatScreen`,
`Gui.canInterruptScreen`, `Gui.buildInitialScreens` and
`Gui.setClientLevelTeardownInProgress`.
Two of its behaviours are the sort of thing a reader assumes and gets wrong.
**`Gui.setScreen` with a null screen does not mean "close the screen".** It means "decide
what should be up instead". With no level it substitutes the title screen;
with a dead player it substitutes the death screen, or respawns; otherwise it
restores the chat screen if one was saved. During a level teardown it throws,
rather than return you to a world that is being dismantled.
**`Gui.isPausing` is what stops the integrated server**, and it asks the
screen. `Screen.isPauseScreen` defaults to **true** and
`AbstractContainerScreen` overrides it to false — which is the whole reason
the options screen pauses a singleplayer world and a chest does not. An
overlay pauses by default too.
And an overlay does not stack on a screen: in the record pass it *replaces*
it. Nothing draws both. `LoadingOverlay` is the only implementation of
`Overlay` in the game.
## The lifecycle, and what is final
`Screen.init` is **final**, and a resize goes through `Screen.resize` to
`Screen.repositionElements`. The default `Screen.repositionElements` rebuilds
every widget through `Screen.rebuildWidgets`, which does re-enter the
overridable `Screen.init` hook — so on a plain screen everything really is rebuilt.
Forty-one screens override `Screen.repositionElements` instead and keep their
widgets, most of them just re-arranging their `Layout`. "Everything is rebuilt
on resize" is true of a plain screen and false of most interesting ones.
The rest of the lifecycle is `Screen.added`, `Screen.tick`, `Screen.removed`
and `Screen.onClose`, and the record entry point is the final
`Screen.extractRenderStateWithTooltipAndSubtitles`.
The framework fixes the outer shape everywhere and hands the subclass one
inner hook. `Screen.init`,
`Screen.extractRenderStateWithTooltipAndSubtitles`,
`AbstractWidget.extractRenderState`, `AbstractWidget.updateNarration`,
`AbstractButton.extractWidgetRenderState` and `AbstractContainerScreen.tick`
are all final; `AbstractWidget.extractWidgetRenderState` and
`AbstractButton.extractContents` are the hooks they leave open. The widget
family under them is `Button`, `EditBox`, `Checkbox`, `CycleButton`,
`AbstractScrollArea` and the selection lists over it —
`AbstractSelectionList`, `ObjectSelectionList`,
`ContainerObjectSelectionList` and `OptionsList`.
Layout is `Layout` over `LayoutElement`: `LinearLayout`, `GridLayout`,
`FrameLayout`, `EqualSpacingLayout`, `HeaderAndFooterLayout` and
`SpacerElement`, configured by `LayoutSettings` and resolved by
`Layout.arrangeElements` and `Layout.visitWidgets`. Input arrives as the
`client/input` records through `GuiEventListener` and
`ContainerEventHandler`; focus is a `ComponentPath` moved by a
`FocusNavigationEvent`, ordered by `TabOrderedElement.getTabOrderGroup`; and
geometry is `ScreenRectangle`, `ScreenPosition`, `ScreenAxis` and
`ScreenDirection`.
One consequence of doing all this in a record pass:
`AbstractWidget.extractRenderState` computes *hovered* as "inside my
rectangle **and** inside the current scissor", so a widget scrolled out of a
list does not light up.
## Pressing E
```mermaid
sequenceDiagram
participant KH as KeyboardHandler
participant MC as Minecraft
participant Gui as Gui
participant InvS as InventoryScreen
participant MPGM as MultiPlayerGameMode
KH->>KH: keyPress — no screen is open, so the mapping records a click
Note over MC: next client tick
MC->>MC: handleKeybinds — only with no screen and no overlay
MC->>MPGM: isServerControlledInventory? false for a player on foot
MC->>MC: Tutorial.onOpenInventory
MC->>Gui: setScreen(new InventoryScreen(player))
Gui->>Gui: MouseHandler.releaseMouse, then KeyMapping.releaseAll — both before init
Gui->>InvS: removed on the old screen, then added, then Screen.init
InvS->>InvS: init — creative? replace myself with CreativeModeInventoryScreen
Note over Gui: next frame, record
Gui->>InvS: extractRenderStateWithTooltipAndSubtitles
InvS->>InvS: extractBackground — in-game UI, so a gradient, no blur, no panorama
InvS->>InvS: extractContents, then labels, slots, the hovered highlight, the carried item
```
The busiest screen in the game is `AbstractContainerScreen`, and its record
pass is worth following once: `AbstractContainerScreen.extractContents` draws
the widget list, translates to the container origin, and runs
`AbstractContainerScreen.extractLabels`,
`AbstractContainerScreen.extractSlots` and the two slot-highlight passes;
then `AbstractContainerScreen.extractCarriedItem`, then
`AbstractContainerScreen.extractTooltip`. It holds
`AbstractContainerScreen.menu`, `AbstractContainerScreen.leftPos`,
`AbstractContainerScreen.topPos`, `AbstractContainerScreen.hoveredSlot` and
the quick-craft state, and a click goes
`AbstractContainerScreen.slotClicked` to
`MultiPlayerGameMode.handleContainerInput` — see [containers and
menus](../items/containers-and-menus.md).
The final `AbstractContainerScreen.tick` closes the container when the player
is dead or removed. The *client* notices first.
## Who opens a screen
| route | examples |
|---|---|
| entirely client-side | title, pause, options, chat, advancements, social interactions, the survival and creative inventories |
| `ClientboundOpenScreenPacket` | every menu with a `MenuType` — chests, furnaces, anvils, and a chest boat |
| `ClientboundMountScreenOpenPacket` | a horse's or a nautilus's own inventory |
| other packets | the book viewer, the sign editor, the death screen, the win screen, the demo popup, the level-loading screen, dialogs |
Three entities implement `HasCustomInventoryScreen`, and they do not agree:
two use the mount packet and one falls back to the ordinary menu packet.
The screens you see *first* are a chain rather than a screen.
`Gui.buildInitialScreens` composes accessibility onboarding, ban notices, a
forced name change and a banned-skin notice ahead of the title screen or a
quick-play launch. And `Minecraft.setScreenAndShow` sets a screen and then
renders one frame on the spot — synchronously — which is how progress appears
during blocking main-thread work such as a world load, a data fix or a save.
Narration, finally, is mostly timed rather than immediate — mostly, because
`Screen.init` narrates the new screen at once before arming anything.
Thereafter `Screen.handleDelayedNarration` fires from `Gui.update` once two
clocks have passed — one delay after a mouse move, a shorter one after a
keyboard action, and a two-second suppression after a screen is built — and
then picks a *single* widget to narrate, by tab-order group and priority.
> **For a 1.21-era reader.** `Minecraft.screen` is gone: the current screen
> belongs to `Gui`, which is now the screen-and-overlay manager rather than
> the HUD — the HUD is `Hud`, reached as `Gui.hud`. Gone with it:
> *Screen.render*, *renderBackground* and *renderDirtBackground*;
> *AbstractContainerScreen.renderBg* / *renderLabels* / *renderSlot*;
> *AbstractWidget.renderWidget*; *ClickType* (now `ContainerInput`);
> *MultiPlayerGameMode.handleInventoryMouseClick* (now
> `MultiPlayerGameMode.handleContainerInput`); and *Minecraft.setScreen*.
> A screen no longer draws — it *records*.
## Where to look
`Gui.setScreen` — the substitution tree and the input housekeeping at both
ends of a screen's life. `Screen.init` and `Screen.resize` for the lifecycle,
`Screen.extractRenderStateWithTooltipAndSubtitles` for the record pass, and
`Gui.extractRenderState` for the frame's contributor order.
`AbstractContainerScreen.extractContents` for the busiest screen in the game,
and `MenuScreens` for the screen registry the menu types use — `DialogScreens`
is the second one.
---
*Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes `tools/verify_names.py`.*
---
# The GUI render tree
> Verified against **Minecraft 26.2** · Part X · a chest full of the same item: how the 2D UI decides what is in front of what, without anybody ever saying so.
Nothing in the client's 2D UI draws anything. Every call a screen, a widget
or the HUD makes appends a *render state* to a tree; later in the same frame
a second pass resolves that tree, sorts it, batches it and issues the draws.
And the tree is not told what order to put things in. **Layering is inferred
from bounding boxes**: a new element goes above the highest existing element
whose box it *intersects*, and two elements that do not overlap can share a
node and be reordered freely by the batching sort. There is no Z value, no
layer index, and no declared order beyond the call order and two explicit
barriers.
That inference is what makes the batching possible, and the batching is why a
chest full of identical stacks is cheap: each distinct item model is rendered
into a dynamic atlas **once and reused for as long as it stays resident** —
one 3D render ever, not one per frame, and not one per stack.
## The cast
| class | what it decides | thread |
|---|---|---|
| `GuiGraphicsExtractor` | what a screen is handed — every drawing verb, and the scissor stack | Render thread |
| `GuiRenderState` | the tree: strata, nodes, and where a new element belongs | Render thread |
| `GuiRenderState.Node` | one layer: a list of elements and a separate list of glyphs | Render thread |
| `GuiElementRenderState` | one recorded thing, and the bounds the layering algorithm reads | Render thread |
| `GuiRenderer` | resolving, sorting, coalescing and issuing the draws | Render thread |
| `GuiItemAtlas` | which item models are already rendered, and which age out | Render thread |
| `GameRenderState` | who actually owns the tree — not `Gui` | Render thread |
## The tree, and where a new element lands
```mermaid
flowchart TD
GRS["GuiRenderState — a list of strata"]
S1["stratum 0"]
S2["stratum 1 — opened by nextStratum, a hard floor for the search"]
N1["Node"]
N2["Node — above"]
N3["Node — above that"]
EL["elements: BlitRenderState, TiledBlitRenderState, ColoredRectangleRenderState — the list the sort works on"]
GL["glyphs: GlyphRenderState, in a second list the sort never touches"]
OTH["and three more lists beside them: items, text, pictures in picture"]
NEW["a new element arrives"]
FAST{"does the previous element's box contain it?"}
UP["up one node — no intersection test at all"]
WALK["walk up from the current stratum to just above the highest box it touches"]
NONE{"has bounds?"}
DROP["silently discarded"]
GRS --> S1
GRS --> S2
S2 --> N1 --> N2 --> N3
N2 --> EL
N2 --> GL
N2 --> OTH
NEW --> NONE
NONE -- "no" --> DROP
NONE -- "yes" --> FAST
FAST -- "yes" --> UP
FAST -- "no" --> WALK
```
Three consequences fall straight out of that picture.
**An element with no bounds is silently discarded.** Every *recording* add
verb is conditional on the tree finding a node, and finding a node requires
bounds. Glyph states deliberately have none — which is exactly why they are
added through the layer-bypassing verb,
`GuiRenderState.addGlyphToCurrentLayer`, which the draw pass calls rather than
the record pass, and emitted after their node's geometry. **Glyphs are never sorted**, and
that is the whole mechanism behind "text draws on top of its own background".
**The search never descends below the current stratum**, which is what makes
`GuiRenderState.nextStratum` a hard barrier rather than a hint. It is how the
HUD keeps the hotbar block out of the crosshair's layering, and how chat
stays clear of the scoreboard.
**The fast path is the common case.** If the previous element's box
*contains* the new one — a label inside a button, a sprite inside a slot — it
goes straight up with no test.
The recording verbs are `GuiRenderState.addGuiElement`,
`GuiRenderState.addText`, `GuiRenderState.addItem`,
`GuiRenderState.addPicturesInPictureState`,
`GuiRenderState.addBlitToCurrentLayer` and
`GuiRenderState.addGlyphToCurrentLayer`; the structural ones are
`GuiRenderState.nextStratum` and `GuiRenderState.blurBeforeThisStratum`. The
states themselves are `BlitRenderState`, `TiledBlitRenderState`,
`ColoredRectangleRenderState`, `GuiTextRenderState`, `GlyphRenderState`,
`GuiItemRenderState`, `PanoramaRenderState`, and the
`PictureInPictureRenderState` family — `GuiEntityRenderState`,
`GuiSkinRenderState`, `GuiBookModelRenderState`,
`GuiBannerResultRenderState`, `GuiProfilerChartRenderState` and
`OversizedItemRenderState`.
## The draw pass
Two phases, one thread, one frame. It is a *data* split rather than a thread
split: the record phase touches game state, the draw phase touches the
recorded objects and the GPU.
```mermaid
flowchart TD
REC["Gui.extractRenderState — reset the tree, build a fresh extractor, call the contributors in order"]
PREP["GuiRenderer.prepare"]
PIP["pictures-in-picture: 3D content rendered to textures"]
ITEM["items: models rendered into GuiItemAtlas, once each, then reused"]
TEXT["text: prepared text expanded into per-glyph states"]
SORT["sortElements — per node, by scissor, then pipeline, then texture"]
MESH["addElementToMesh — a new Draw only when pipeline, scissor or texture changes"]
DRAW["GuiRenderer.draw"]
BEFORE["everything before the blur"]
BLUR["clear depth, run the post effect"]
AFTER["everything after"]
END["GuiRenderer.endFrame — called by GameRenderer, ages the item atlas"]
REC --> PREP --> PIP --> ITEM --> TEXT --> SORT --> MESH --> DRAW
DRAW --> BEFORE --> BLUR --> AFTER --> END
```
One thing happens eagerly during recording that looks like it should not:
adding text forces the text to be **prepared**, because the tree needs its
bounds to place it. Only the expansion into per-glyph states waits for the
draw pass — see [text and fonts](text-and-fonts.md).
The three sort comparators, `GuiRenderer.ELEMENT_SORT_COMPARATOR`,
`GuiRenderer.SCISSOR_COMPARATOR` and `GuiRenderer.TEXTURE_COMPARATOR`, are
what turns a node's element list into as few `GuiRenderer.Draw`s as possible.
Both the sort and the coalescing happen inside `GuiRenderer.prepare`;
`GuiRenderer.draw` only replays the list they produced.
## Blur is a barrier, and it is fussy
`GuiRenderState.blurBeforeThisStratum` splits the draw list in two. Asking
for it twice in one frame **throws**. It is conditional on the
menu-background blurriness option being at least one, and screens that
declare themselves in-game UI — container screens, sign editors, book screens
— take the transparent-background path and never request it. That is why the
pause menu blurs the world and a chest does not.
## Questions a reader asks
**Does the item atlas ever get expensive?** Whenever a slot is not already
resident and current — a slot that has gone stale, or was never filled, is
redrawn with no invalidation involved. Wholesale invalidation is the loud
case: changing the GUI scale throws the atlas away, and an atlas that cannot
grow logs that some items will be skipped. Animated models are the exception
to residency: they are redrawn every frame. The aging that evicts a slot happens
in `GuiRenderer.endFrame`, which `GameRenderer` calls — not
`GuiRenderer.render`.
**Does the extractor really have no side effects?** It has two, just not
drawing ones. The scissor stack is real state, and the cursor shape requested
during the record pass is applied to the window at the end of it. It also
holds the deferred tooltip and the pre-edit overlay.
**Can a 2D flag change how the world is drawn?** Yes. The HUD's hidden flag
and a clear-colour override live on `GuiRenderState` and are read by
`GameRenderer` — every site is `GameRenderer`'s, not `LevelRenderer`'s. The
tree also belongs to `GameRenderState` rather than to `Gui`: the GUI holds a
reference to it. Nor is this the only reach backwards: the HUD's boss bar
reads world fog, the lightmap and the level render state as well.
**What if the batching sort were wrong?** There are debug switches for
exactly that. One promotes every element into its own layer and outlines it;
another shuffles each node's element list and re-seeds the sort keys, to shake
out accidental order dependence. The second one is how you would find out.
> **For a 1.21-era reader.** There is no *GuiGraphics*. The class is
> `GuiGraphicsExtractor`, and the name is the whole design — it extracts, it
> does not paint. *LayeredDraw* is gone too: ordering is the literal call
> order plus explicit barriers. *GuiGraphics.renderTooltip* is gone, and so
> is `PoseStack` in 2D GUI code — the GUI transform is a 2D affine stack now,
> though a real `PoseStack` still lives inside the item atlas, where actual
> 3D models are drawn.
## Where to look
`GuiRenderState.nextStratum` and the node-placement logic beside it — the
layering rule is thirty lines and explains most of the UI's behaviour.
`GuiGraphicsExtractor` for what a screen is actually handed.
`GuiRenderer.prepare` for where items, text and picture-in-picture content
are resolved, and `GuiRenderer.draw` for the batching rule and the blur split.
---
*Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes `tools/verify_names.py`.*
---
# Text and fonts
> Verified against **Minecraft 26.2** · Part X · a chat line in a language whose characters are not in the default font: six stages from a `Component` to a quad, one of which uploads a texture while pretending to measure.
Ask the client how wide a piece of text is and it will stitch a glyph into a
texture sheet and upload it to the GPU. `Font.width` resolves each codepoint
to a **baked** glyph and reads its advance — so measuring a codepoint nobody
has drawn yet is neither free nor read-only, and a layout pass over a
never-before-seen script is a series of texture uploads wearing arithmetic's
clothes.
This page is the pipeline that ends in that glyph: flattening a component
tree into styled runs, measuring and wrapping it, reordering it for
bidirectional scripts, choosing a glyph for every codepoint from a chain of
providers, baking it the first time anyone asks, and emitting the vertices.
What a `Component` *is* — its contents kinds, its `Style`, how it is
serialised — is Part II's [text
components](../foundations/text-components.md), and how a chat message is
signed is [chat and signing](../networking/chat-and-signing.md). This page
starts from "you have one".
## The cast
| class | what it decides | thread |
|---|---|---|
| `StringDecomposer` | where one styled run ends and the next begins, and what a section sign means | Render thread |
| `StringSplitter` | width, line breaks and word boundaries, given a width provider | Render thread |
| `FormattedBidiReorder` | visual order, by handing the flattened text to ICU | Render thread |
| `FontManager` | which `GlyphSource` a `FontDescription` resolves to — three different ways | reload workers, then Render thread |
| `FontSet` | the provider chain for one font id, its codepoint cache, and the fishy split | Render thread |
| `GlyphStitcher` | where a bitmap lands in a `FontTexture`, and the upload | Render thread |
| `Font` | the per-codepoint loop, and the `Font.PreparedText` everything else walks | Render thread |
| `GuiRenderer` | expanding prepared text into one `GlyphRenderState` per glyph | Render thread |
## The six stages
```mermaid
flowchart TD
C["a Component"]
RUNS["1 · flatten — Component.visit yields (style, string) runs in logical order, Style.applyTo down the sibling tree"]
DEC["StringDecomposer walks them codepoint by codepoint and interprets legacy section-sign codes"]
WRAP["2 · measure and wrap — StringSplitter cuts at a character offset, each surviving run keeping its own Style"]
BIDI["3 · reorder — Language.getVisualOrder, ClientLanguage, FormattedBidiReorder, ICU"]
RESOLVE["4 · resolve — FontManager answers a FontDescription with a GlyphSource, then the first provider that has the codepoint"]
BAKE["5 · bake — first sight only: GlyphBitmap into a FontTexture, uploaded, producing a BakedGlyph"]
EMIT["6 · emit — a TextRenderable per glyph, plus effect glyphs, into a Font.PreparedText"]
GUI["GuiRenderer expands it into GlyphRenderState"]
WORLD["SubmitNodeCollection.submitText and submitNameTag feed the world renderers"]
C --> RUNS --> DEC --> WRAP --> BIDI --> RESOLVE --> BAKE --> EMIT
WRAP -. "measuring a codepoint resolves and bakes it too" .-> RESOLVE
EMIT --> GUI
EMIT --> WORLD
```
The numbering is the order the stages *matter* in, not a pipeline anything
walks straight through. Stages one to three run whenever the text changes, and
four to six run inside `Font.prepareText`, which the GUI calls **during the
record pass**, because [the render tree](the-gui-render-tree.md) needs the
text's bounds before it can place it. But four and five are also reached from
*two*: the width function `Font` hands its `StringSplitter` asks the glyph
source for each codepoint, and that call resolves the provider and forces the
bake. Measuring a string you never draw still uploads its glyphs. Only the
expansion into per-glyph states waits for the draw pass.
All of it is on the Render thread, glyph baking and GPU uploads included. The
only work that leaves the thread is *loading*: `FontManager`'s prepare phase
parses the font definitions, loads each provider, resolves references between
them, and pre-warms every provider by asking it for every codepoint it
claims, on the reload workers. The apply phase — closing the old font sets
and building new ones — is back on the Render thread.
### 1 · Flatten
`Component` and `FormattedText` provide the walk: `Component.visit` yields
(style, string) runs in logical order, applying `Style.applyTo` down the
sibling tree. `StringDecomposer` iterates those runs codepoint by codepoint
and is where a legacy section-sign colour code is interpreted.
`FormattedCharSequence` is the end product — a one-method interface that
pushes (index, style, codepoint) triples at a `FormattedCharSink` — and
`ComponentCollector` reassembles pieces back into a component.
### 2 · Measure and wrap
`StringSplitter` owns width and line breaking: `StringSplitter.stringWidth`,
`StringSplitter.splitLines`, `StringSplitter.headByWidth`,
`StringSplitter.findLineBreak` and `StringSplitter.getWordPosition`. It works
against a `StringSplitter.WidthProvider`, and on the real `Font` that
provider is what bakes. `Font.width`, `Font.split`,
`Font.splitIgnoringLanguage`, `Font.wordWrapHeight` and `Font.getSplitter`
are the public face of it.
**Wrapping preserves styles rather than ignoring them.** It cuts at a
character offset, captures the style in force at the cut, and re-applies it to
the continuation — which is why a colour code before a wrap point still
colours the line after it. And
`Font.split` reorders while `Font.splitIgnoringLanguage` does not — anything
that will re-measure or re-wrap must use the second.
### 3 · Reorder
`Language.getVisualOrder` is the entry point; on the client `ClientLanguage`
implements it with `FormattedBidiReorder`, which builds a `SubStringSource` —
the flattened plain text plus one `Style` per character — runs ICU's bidi
algorithm over it, and re-emits each run through
`SubStringSource.substring`. `MutableComponent.getVisualOrderText` caches
the result against the identity of the current `Language`.
`Font.bidirectionalShaping` is a separate, much smaller thing: it shapes a
bare string, and beside `Font.prepareText`'s own use of it the sign editor is
the only caller.
### 4 · Resolve
`FontManager` is the reload listener and the resolver: `Font.Provider` asks
it for a `GlyphSource` given a `FontDescription`, and it answers three
different ways.
A `FontDescription.Resource` resolves to a `FontSet` — the per-font-id object
holding the provider list, the codepoint cache (`CodepointMap`), a
`GlyphStitcher` and the by-width table used for obfuscation. A
`FontDescription.AtlasSprite` or `FontDescription.PlayerSprite` resolves
instead to a `SingleSpriteSource` from `AtlasGlyphProvider` or
`PlayerGlyphProvider` — a one-glyph font that returns the same sprite for
every codepoint, with no texture sheet of its own; `FontManager` still keeps a
`FontSet` behind it as the fallback.
Below that: `GlyphProvider` implementations chosen by `GlyphProviderType` —
bitmap, TrueType, space, unihex, reference — declared in *font/* JSON as
`GlyphProviderDefinition`s.
### 5 · Bake
A provider returns an `UnbakedGlyph`, whose `GlyphBitmap` is handed to
`GlyphStitcher.stitch`, placed into a `FontTexture` sheet and uploaded,
producing a `BakedGlyph`. `BakedGlyph` is an interface; the sheet
implementation is `BakedSheetGlyph`, and `EffectGlyph` covers the solid quads
used for underlines and backgrounds.
There is **one glyph atlas family per font id**, each with its own stitcher,
and colour and greyscale glyphs never share a sheet. The atlas is discarded,
never compacted: a resource reload throws it away — and so does toggling the
force-unicode or Japanese-variants option, which rebuilds every font set with
no reload at all.
### 6 · Emit
What comes out is a `TextRenderable` per glyph inside a `Font.PreparedText`.
In the GUI, `GuiGraphicsExtractor.text` and its siblings build a
`GuiTextRenderState` that `GuiRenderer` later expands into
`GlyphRenderState`s. In the world, `SubmitNodeCollection.submitText` and
`SubmitNodeCollection.submitNameTag` feed `TextFeatureRenderer` and
`NameTagFeatureRenderer`, and `GlyphRenderTypes.select` picks between the
normal, see-through and polygon-offset render types by `Font.DisplayMode`.
## A chat line, through all six
```mermaid
sequenceDiagram
participant ChatC as ChatComponent
participant CRU as ComponentRenderUtils
participant SSpl as StringSplitter
participant FBR as FormattedBidiReorder
participant Font as Font
participant FSet as FontSet
participant GStit as GlyphStitcher
participant GuiR as GuiRenderer
ChatC->>CRU: wrapComponents — the chat width divided by the chat scale
CRU->>SSpl: splitLines — translation happens here, on first visit
SSpl->>SSpl: break at a char offset, each surviving run keeping its own Style
CRU->>FBR: Language.getVisualOrder(line)
FBR->>FBR: SubStringSource, ICU bidi, one substring per run
Note over ChatC: next frame, record
ChatC->>Font: prepareText — via GuiTextRenderState asking for its own bounds
loop per codepoint
Font->>FSet: getGlyph — the first provider that has it
FSet->>GStit: first sight only: stitch into a FontTexture and upload
Font->>Font: emit a TextRenderable, add underline or strikethrough, advance the pen
end
Note over GuiR: same frame, draw
GuiR->>GuiR: walk the PreparedText with a Font.GlyphVisitor
GuiR->>GuiR: one GlyphRenderState per glyph — the shadow pass, the bold copy and the italic shear are the glyph's own, inside BakedSheetGlyph.renderChar
```
Two moments beyond the baking are worth pausing on. **Translation is lazy and
cached on the `TranslatableContents` itself**, so the first thing to visit a
message is what translates it — usually a measure, sometimes a log line one
statement earlier. And the continuation indent on a wrapped chat line is a
**literal space codepoint** prepended by `ComponentRenderUtils` — the one
place in the pipeline where a character is invented rather than derived.
## Questions a reader asks
**Why is this glyph a hollow box?** Four different causes, one appearance. No
provider has the codepoint; the font id is unknown, so the whole font set is
the missing-font set; the glyph's advance is "fishy" and the caller asked for
the filtered font; or the bitmap fit no sheet.
**What is a "fishy" glyph?** An advance outside a sane range. Every font set
stores two suppliers per codepoint because of it, and the game builds a
second `Font` that filters the fishy ones out. That second font has exactly
one use in the entire client: the chat input box — a font that cannot be made
to draw a character three screens wide.
**How is a block icon in a chat line drawn?** As a glyph. An object component
emits a single object replacement character with a synthetic font
description, which resolves to a one-glyph sprite font. A block icon or a
player head is, mechanically, one character in a font with one character in
it — and the plain-text walk of the same component yields a bracketed
fallback string instead, which is what the narrator and `Component.getString`
see. A data pack cannot do this: the codec behind `FontDescription` only
encodes the resource kind, so **a style can never name a sprite font**. The
sprite descriptions arise only from object contents.
**Why does obfuscated text not shift the layout?** The glyph is swapped for a
random one *of the same width*, from a table built once per font set. And
because the swap happens inside `Font.prepareText`, which already runs once
per frame, the animation costs nothing extra.
**Is bold a font?** No. Bold draws the glyph twice with a small offset and
thickens it; italic shears the top and bottom edges. Nothing in the font
pipeline knows what a bold face is. Shadow is a colour rather than a boolean,
and zero means none. Underlines, strikethroughs and text backgrounds always
come from the **default** font whatever the style names, because the effect
glyph is looked up separately.
**How does hovering a link work?** `ActiveTextCollector` walks the very same
`Font.PreparedText` that will be drawn, looking for the style under the
cursor — which is why preparation can be asked to record *empty* areas, so
that hovering the space inside a hover-event run still finds the style. Glyph
areas deliberately extend to the full advance, so there are no dead gaps
between characters.
**Are the caches safe?** Mostly by being single-threaded rather than by
being locked: they are meant to be touched only on the Render thread. It is
not a clean rule — some are keyed on identity and some on equality, and the
glyph layer does use *volatile* fields and a Guava cache. A component's cached visual order is invalidated by the `Language`
object changing, and a translatable component's decomposition likewise. The
one place that leaks: the sign block entity caches its rendered lines on a
class the *server* also ships, and that cache does not notice a font reload.
> **For a 1.21-era reader.** `Font` cannot draw. Every *drawInBatch* and
> *drawString* is gone; `Font.prepareText` returns a `Font.PreparedText` that
> somebody else walks later. `Style.getFont` no longer returns an identifier
> — it returns a `FontDescription`, which may not name a font file at all.
> Also gone: *Font.StringRenderOutput*; *RawGlyph* and *SheetGlyphInfo* (now
> `UnbakedGlyph` and `GlyphBitmap`); *BakedGlyph* as a class;
> *GlyphProviderBuilder* (now `GlyphProviderDefinition`); and
> `FontSet.getGlyph` as public API. `StringSplitter`, `StringDecomposer`,
> `FormattedCharSequence`, `SubStringSource`, `GlyphStitcher` and
> `FontTexture` all survive under their old names.
## Where to look
`Font.prepareText` — the per-codepoint loop is the centre of the page.
`FontSet` for the provider chain and the fishy-advance split, `FontManager`
for how a `FontDescription` becomes a `GlyphSource`, and
`GlyphStitcher.stitch` for the moment a glyph acquires a texture.
`StringSplitter.splitLines` for how a line break preserves styles, and
`FormattedBidiReorder.reorder` for the heaviest of the four places ICU is
used.
---
*Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes `tools/verify_names.py`.*
---
# The HUD
> Verified against **Minecraft 26.2** · Part X · press F1 and the interface goes away — except for the thing that can black out your whole screen.
`Hud.extractRenderState` is a single ordered method wrapped in two
hidden-gated blocks, and one element sits *between* them: the sleep fade. So
F1 hides the hearts, the hotbar, the crosshair, chat and the tab list, and
does not hide the black screen that comes over you when you get into bed.
Four more elements are recorded by `Gui`, after the screen rather than with
the rest of the HUD — the saving indicator, the debug overlay, the deferred
subtitles and the toasts — and only the toasts are outside `Hud`'s own
methods. Only the saving indicator ignores the flag.
That is what this page is: not a tour of hearts and hotbars but a **policy**.
What is drawn over the world, in what order, under exactly which conditions,
and which of those conditions is a surprise. The full per-element table is
[what the HUD draws, and when](../../reference/hud-elements.md) in Reference;
the machinery underneath every one of them is [the GUI render
tree](the-gui-render-tree.md).
## The cast
| class | what it decides | thread |
|---|---|---|
| `Hud` | the order, the two hidden-gated blocks, and the health animation state | Render thread |
| `Gui` | the four elements recorded *after* the screen | Render thread |
| `ContextualBar` | which of four things occupies the one slot above the hotbar | Render thread |
| `BossHealthOverlay` | the bars, and three questions the world renderer asks it | Render thread |
| `ChatComponent` | the message list, its wrapped lines, and what is faded out | Render thread |
| `DebugScreenEntries` | the F3 registry: what an entry is, and whether it is on | Render thread |
| `DebugScreenEntryList` | the per-entry status, its presets, and its own save file | Render thread |
| `GuiGraphicsExtractor` | everything the HUD records into | Render thread |
## The order, and the two blocks
```mermaid
flowchart TD
PUB["publish GuiRenderState.isHudHidden — before any check, so the flag is always current"]
LLS{"is a LevelLoadingScreen up?"}
STOP["record nothing"]
H1{"hidden?"}
A["camera overlays, then the crosshair, then a new stratum, then the hotbar block, effects, boss bars"]
SLEEP["the sleep fade — ungated"]
H2{"hidden?"}
B["demo text, scoreboard sidebar, action bar, title, chat, tab list, subtitles"]
B2["subtitles only, and only if an in-game-UI screen is up"]
GUI["Gui continues: saving indicator, toasts, debug overlay, deferred subtitles"]
PUB --> LLS
LLS -- "yes" --> STOP
LLS -- "no" --> H1
H1 -- "no" --> A --> SLEEP
H1 -- "yes" --> SLEEP
SLEEP --> H2
H2 -- "no" --> B --> GUI
H2 -- "yes" --> B2 --> GUI
```
Two structural facts follow from the shape. The hidden flag is published
*before* the loading-screen short-circuit, so the renderer's copy is correct
even on a frame where the HUD records nothing. And toasts and the debug
overlay are always **above** a screen, because `Gui` records them after it —
while the deferred subtitles are called from a screen's *background* pass and
therefore land under the screen's widgets.
`Hud.tick` runs from `Gui.tick` once per client tick and takes a pause flag:
the autosave indicator animates either way, everything else only when the
game is not paused.
## The hidden flag travels two ways
The interesting one is the smaller. `GuiRenderState.isHudHidden` is read by
`GameRenderer` in three places, to suppress the held item, the
three-dimensional crosshair and — the smallest of the three — the totem-pop
animation. The block-in-eyes, water and fire overlays are drawn whatever F1
says. So a 2D flag does change how the *world* is drawn, in three narrow
ways. But `Hud.isHidden` itself is read directly by six other places across
the client, including two entity renderers that suppress name tags.
## Four states, one slot, and an asymmetric rule
`Hud.contextualInfoBar` holds one of four states — nothing, experience, the
locator, a jumpable vehicle — and `Hud.nextContextualInfoState` re-decides
which every frame. It is a rule, not a state machine, and the rule is not
symmetric. With waypoints present, a jumping vehicle or a *recently changed*
experience total beats the locator; with no waypoints, a jumpable vehicle
beats experience unconditionally — so **mounting a horse silently takes your
XP bar away.** The level number is recorded separately from the bar, so it
survives whichever bar wins.
The three implementations are `ExperienceBar`, `LocatorBar` — backed by
`ClientWaypointManager` and styled by `Hud.waypointStyles`, a
`WaypointStyleManager` that is the HUD's own reload listener — and
`JumpableVehicleBar`. `Hud.ContextualInfo` is the enum of the four states.
## The hearts, which are three numbers at once
```mermaid
sequenceDiagram
participant CPL as ClientPacketListener
participant LP as LocalPlayer
participant Hud as Hud
participant GGE as GuiGraphicsExtractor
CPL->>LP: handleSetHealth — hurtTo, which sets hurtTime and invulnerableTime
Note over Hud: next frame
Hud->>Hud: extractPlayerHealth — health fell while invulnerable
Hud->>Hud: healthBlinkTime becomes tickCount plus 20 (a heal sets 10)
Hud->>Hud: displayHealth catches up only once the second has elapsed
Hud->>Hud: random.setSeed(tickCount times a constant) — the jitter is per tick, not per frame
Hud->>Hud: extractHearts — one descending pass: container, absorption, ghost, truth
Hud->>GGE: blitSprite per heart, chosen by HeartType.getSprite
```
**The shake is seeded from the tick counter**, so it jitters at 20 Hz and is
identical across two frames of the same tick — and the same seeded stream
drives the hunger jitter and the air-bubble wobble, which is why they shake
together. **The blink is a square wave** with a three-tick half-period,
running for twenty ticks after damage and ten after a heal, and what it draws
is the *ghost*: the HUD keeps three health numbers at once — last frame's, a
lagging display value that catches up about once a second, and the truth — and
the blinking layer shows the one that is out of date on purpose.
`Hud.HeartType` is six constants, one of which Mojang spells
`Hud.HeartType.POISIONED`, each with eight sprites.
One gate silences four elements at once: armour, hearts, food and air are all
recorded inside `Hud.extractPlayerHealth`, which is gated on the game mode
being able to hurt you — which is why creative has no armour bar either. Food
and mount health share a slot, and the air bubbles shift up when either is
drawn. And the HUD makes a sound: `Hud.playAirBubblePoppedSound` ramps its
volume and pitch with how many bubbles are *gone*, so it climbs as you drown.
## Questions players ask
**Why does the boss bar change the sky?** The overlay answers three questions
— should the screen darken, should world fog be created, should the End music
play — read from five places between the frame, the fog environment and the
lightmap. The bar itself interpolates against wall-clock time inside
`LerpingBossEvent`, so discrete packet progress becomes a smooth bar.
**Why do subtitles appear under an open chest?** They are deferred past the
screen, along with the tooltip and the pre-edit overlay the extractor holds. The deferral fires when there is no screen at all *or* the screen
declares itself in-game UI — the common case, not the rare one — and the
deferred call is made from a screen's background pass.
**Are the chat HUD and the chat screen the same thing?** The same code in
different modes. The HUD bails out entirely when the chat screen is focused.
Message age is measured in HUD ticks rather than timestamps, and a message the
server asked to delete keeps its original timestamp when it is replaced by a
marker — so the marker fades on the original message's schedule.
`ChatComponent` holds **four** collections, not two: every message and every
wrapped *line* are separate lists with separate caps, and the deletion queue
and the recent-input history are the other two. A fifth, the delay-option
queue, lives on `ChatListener`. Signing belongs to [chat and
signing](../networking/chat-and-signing.md); this page owns display only.
**Is the pumpkin blur hardcoded?** No. The camera overlay list is
data-driven: every equipment slot is asked whether its item declares a camera
overlay.
**Can I turn a debug line on without pressing F3?** Yes, and the game saves
that you did. `DebugScreenEntries` holds every entry by `Identifier`, each a
`DebugScreenEntry` writing lines through a `DebugScreenDisplayer`;
`DebugScreenEntryList`, reachable as `Minecraft.debugEntries`, stores a
`DebugScreenEntryStatus` per entry, ships `DebugScreenProfile` presets and
persists to its own file with its own data-fixer type. An entry set to
always-on renders with F3 never pressed. The screen that edits it is
suppressed by `Gui`, not by the overlay. The charts are `FpsDebugChart`,
`TpsDebugChart`, `PingDebugChart` and `BandwidthDebugChart` over
`AbstractDebugChart` — plus `ProfilerPieChart`, which is not one of them.
**Why is that F3 shortcut not rebindable?** Twenty are ordinary key mappings;
a second family is a raw switch on key codes behind the game's debug flag,
bindable to nothing. Several of the mappings toggle debug entries that print
nothing at all and exist only to carry a flag the world renderer reads.
**What counts as "HUD state"?** Whatever `Hud.onDisconnected` resets — tab
list, boss bars, toasts, debug overlay, chat and titles, together. It is the
only place that clears the HUD as a whole, and it is a better definition than
any list of fields.
> **For a 1.21-era reader.** `Gui` is not the HUD any more. The class that
> drew the hotbar and hearts is `Hud`; the name `Gui` was reused for the
> screen and overlay manager that used to be fields on `Minecraft`. The
> canonical path is `Gui.hud`. Every *render\** method on `Gui` is now an *extract\** on `Hud`, and gone with them: *LayeredDraw*, *Minecraft.screen*,
> *Minecraft.getToastManager*, *Minecraft.fpsString*, *Options.hideGui*, and
> *DebugScreenOverlay.render* along with its two information-gathering
> methods — the line content moved out into the entry registry.
## Where to look
`Hud.extractRenderState` — the whole HUD is one ordered method, and the two
hidden-gated blocks are visible at a glance. Then `Hud.extractPlayerHealth`
for the most-loved fifty-seven lines in the client,
`Hud.nextContextualInfoState` for the bar arbitration, `Gui.extractRenderState`
for the four recorded after the screen, `DebugScreenEntries` for the F3
registry, and `ChatComponent` for the message list.
---
*Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes `tools/verify_names.py`.*
---
# Sound: the engine
> Verified against **Minecraft 26.2** · Part X · a block placed near you: from a packet on the game thread to an OpenAL source, across five threads and one hop the sound cannot skip.
`SoundEngine.play` never starts a sound. It resolves the name, picks a
variant, tells the subtitle overlay, computes the volume and asks for a
channel — and then posts *attach buffer, play* as a task on another thread.
Even when the decoded audio is already in the cache, **a sound always starts
at least one hop after the packet that asked for it**, because the engine has
no path that calls `Channel.play` itself. Preloading removes the decode from
that latency; it does not remove the hop.
This page is the machine: the five threads that take part, the borrowed
OpenAL source, the buffer that arrives afterwards, and the arithmetic that
decides how loud it is. What *decides that a sound should happen at all* —
and the fact that most world sounds are not named on the wire — is [what
makes a sound happen](what-makes-a-sound.md).
OpenAL is touched **only** inside `com/mojang/blaze3d/audio`, plus
`NativeLibrariesBootstrap`, which loads the native library. Nothing in
`client/sounds` makes an AL call itself: it calls into that wrapper, and
everything outside calls `SoundManager.play` and forgets.
## The cast
| class | what it decides | thread |
|---|---|---|
| `SoundManager` | the loaded `sounds.json` map, and the public front door | Render thread |
| `SoundEngine` | which instances are playing, how loud, and what to drop | Render thread |
| `SoundInstance` | one playing-or-wanting-to-play sound: position, pitch, looping, attenuation | Render thread |
| `SoundEngineExecutor` | a `BlockableEventLoop` around one daemon thread — every *per-source* AL call | Sound engine |
| `ChannelAccess` | acquiring, configuring and releasing a channel, as tasks | posts to Sound engine |
| `Library` | the OpenAL device, context, listener and channel limits | Render thread opens it |
| `SoundBufferLibrary` | decoded `.ogg` data, cached per path | Download pool |
| `AbstractDeviceTracker` | noticing that the default device changed | IO-Worker, plus OpenAL's own callback |
## Five threads, and one the game does not own
The page is mostly about which thread does what, so it is worth having the
list before the trace.
| thread | its part in a sound |
|---|---|
| **Server** | decides a sound happens, computes who is in range, sends packets. Never audio. |
| **Render** (the client game thread) | receives the packet, builds a `SoundInstance`, calls `SoundManager.play`. Also everything OpenAL that is *not* per-source: opening and closing the device and context, resetting the `Listener`, deleting buffers. |
| **Sound engine** | every per-source AL call: channel acquisition, parameter setting and release through `ChannelAccess`, plus the listener transform, which `SoundEngine.updateSource` posts to the executor directly. |
| **`Util.nonCriticalIoPool`** (the *Download-* threads) | reads and decodes `.ogg` files with `JOrbisAudioStream` into a `SoundBuffer`, inside `SoundBufferLibrary.getCompleteBuffer`. |
| **`Util.ioPool`** (the *IO-Worker-* threads) | device enumeration — `AbstractDeviceTracker.tick` dispatches `DeviceList.query` there, so the periodic poll of the ALC device list does not stall a frame. A forced refresh still queries on the Render thread. |
And one the game does not own: OpenAL Soft's own event-callback thread,
which invokes the callback `CallbackDeviceTracker` installs to notice that
the default device changed.
The Render thread's two cadences: once per client tick `Minecraft.tick` calls
`MusicManager.tick` and then `SoundManager.tick`, which walks the ticking
sounds, updates positions and volumes, releases finished channels and drains
the delayed queue; once per *frame* `Minecraft.runTick` calls
`SoundManager.updateSource` with the camera.
## A block is placed near you
```mermaid
sequenceDiagram
participant SL as ServerLevel
participant PL as PlayerList
participant CPL as ClientPacketListener
participant CL as ClientLevel
participant SndE as SoundEngine
participant SBL as SoundBufferLibrary
participant ChanA as ChannelAccess
participant Library as Library
SL->>SL: BlockItem.place, then Level.playSound(player, pos, event, BLOCKS, volume, pitch)
SL->>PL: broadcast to everyone in range except the placer, with a seed
PL-->>CPL: ClientboundSoundPacket — a holder, a position in eighths of a block, a seed
CPL->>CL: handleSoundEvent, then playSeededSound, after ensureRunningOnSameThread
CL->>SndE: SoundManager.play(SimpleSoundInstance) — seeded, so every client picks the same variant
SndE->>SndE: resolve, pick by weight, calculateVolume, tell every SoundEventListener, then drop a silent one
SndE->>ChanA: createHandle(STATIC or STREAMING limit) — a task on the sound thread
ChanA->>Library: acquireChannel — generate an OpenAL source, or null if the limit is reached
SndE->>ChanA: ChannelHandle.execute — setPitch, setVolume, linearAttenuation, setSelfPosition
SndE->>SBL: getCompleteBuffer(path) — decode off-thread, cached per path
SBL-->>ChanA: thenAccept, then ChannelHandle.execute — attachStaticBuffer, play
loop every client tick
SndE->>ChanA: scheduleTick — pump streams, release channels OpenAL reports stopped
end
```
The four beats worth narrating.
**The name is resolved, in an order you can hear.** The instance's
`Identifier` is looked up in the `SoundManager` registry for a
`WeighedSoundEvents`, and `WeighedSoundEvents.getSound` rolls the weighted
choice — following event-to-event redirects — to a concrete `Sound`. Then, in
this order: the unknown-event and empty-sound cases return early; the volume
is computed; every registered `SoundEventListener` is told; and only *then* is
a zero-volume sound abandoned. So a sound whose
category is muted still produces a **subtitle**, and a sound with no
`sounds.json` entry does not. `SubtitleOverlay` is the only
`SoundEventListener` in the game, and that ordering is what it is for.
**A channel is borrowed, on the sound thread.**
`ChannelAccess.createHandle` posts a task to the `SoundEngineExecutor`; on
that thread `Library` generates a new OpenAL source, provided the static or
streaming limit — chosen by `Sound.shouldStream` — has room. The Render
thread *blocks* on that future, which is the shorter of the two places the
game thread waits on the sound thread, and gets a handle or null. Null means
the sound is silently dropped.
**Parameters go first, data arrives later.**
`ChannelAccess.ChannelHandle.execute` posts the
pitch/volume/attenuation/position setup, while
`SoundBufferLibrary.getCompleteBuffer` returns a future for the decoded
buffer. When that completes, its continuation posts *attach buffer, play* to
the sound thread. That second post is the hop this page opens with.
`Sound.shouldPreload` and `SoundEngine.requestPreload` remove the decode from
the latency, not the hop.
**Streams are pumped by the tick.** Long sounds — music, records — are
streamed: `Channel.attachBufferStream` queues
`Channel.QUEUED_BUFFER_COUNT` buffers of `Channel.BUFFER_DURATION_SECONDS`
each, four seconds in all, and `ChannelAccess.scheduleTick`, posted once per
client tick from `SoundEngine.tick`, calls `Channel.updateStream` on each to
refill. The same pass releases channels whose source reports stopped.
## The channel limits are counters, not pools
`Library` asks the device how many mono sources it offers, falling back to
`Library.DEFAULT_CHANNEL_COUNT` — thirty. The streaming limit is the square
root of that, clamped between two and eight; the static limit is a *clamped*
remainder, floored at eight and capped at 255, so it is not simply "the
rest". Sources are generated on acquire and deleted on release: one OpenAL
source per playing sound, and when a limit is reached new sounds are dropped
rather than queued. **The game does not steal channels by priority.**
Nor does muting free one. `SoundEngine.refreshCategoryVolume` pushes the new
volume to every playing channel of that category and stops nothing, so a
looping sound muted to zero holds its OpenAL source until it ends on its own.
Muting suppresses *new* allocations; it does not reclaim old ones. The
OpenAL source itself, though, goes back the moment the channel reports
stopped: `ChannelAccess.scheduleTick` releases it with no lifetime gate at
all. `SoundEngine.MIN_SOURCE_LIFETIME` holds something else for twenty ticks
— the engine's *bookkeeping* entry for the instance, long after the source it
named has been deleted.
`SoundEngine`'s own state is `SoundEngine.instanceToChannel`,
`SoundEngine.instanceBySource`, `SoundEngine.queuedSounds` (delayed),
`SoundEngine.tickingSounds`, `SoundEngine.gainBySource` and
`SoundEngine.soundBuffers`. `SoundEngine.play` returns a
`SoundEngine.PlayResult` — started, started silently, or not started — and
`SoundManager.play` passes it through; `MusicManager` is the only caller that
reads it.
## Volume is three factors, and looping is three mechanisms
`SoundEngine.calculateVolume` multiplies the instance's own volume, the
options volume (`Options.getFinalSoundSourceVolume`, itself category times
master) and the runtime gain in `SoundEngine.gainBySource`. The third exists
so that `MusicManager` can fade a category without touching the player's
slider — which is why the music slider and the music *fade* are two different
numbers.
A computed volume of zero is abandoned **unless it is music**:
`SoundEngine.play` drops it only when the instance does not say
`SoundInstance.canStartSilent` *and* the category is not
`SoundSource.MUSIC`. Music always starts, silently if need be, which is how a
track fades in from nothing.
Looping happens three different ways. Static sounds loop in OpenAL, with
`Channel.setLooping`. Streamed sounds loop by wrapping the decoder in a
`LoopingAudioStream`, since the source only ever holds a few seconds. And a
looping instance *with a delay* is looped manually —
`SoundEngine.shouldLoopManually` — by re-queueing it into
`SoundEngine.queuedSounds` when its channel stops.
One more attenuation subtlety, because the obvious explanation is wrong. UI
sounds do not attenuate because of their **attenuation**, not their
relativity: `SimpleSoundInstance.forUI` sets both
`SoundInstance.Attenuation.NONE` and the relative flag, and it is the former
that makes the engine call `Channel.disableAttenuation`. A relative sound
offset from the listener would still fall off.
## The instance family, and the decode stack
`SoundInstance` lives in `client/resources/sounds` and carries event, source,
volume, pitch, position, looping, relative and attenuation.
`SimpleSoundInstance` is a one-shot at a point; `EntityBoundSoundInstance`
follows an entity; the `TickableSoundInstance` subclasses —
`AbstractTickableSoundInstance`, minecarts, elytra, bees, ambient loops —
re-evaluate themselves every tick.
Below the engine, `com/mojang/blaze3d/audio` is the OpenAL wrapper: `Library`
(device, context, listener, channel limits), `Channel` (one source),
`SoundBuffer` (one buffer), `Listener` (the ear, set from a
`ListenerTransform`), `DeviceList`, and the device-tracker family
(`AbstractDeviceTracker`, `CallbackDeviceTracker`) that notices headphones
being unplugged.
The decode stack is three interfaces deep: `AudioStream`, then
`FiniteAudioStream`, then `FloatSampleSource`, with `ChunkedSampleByteBuf`
assembling the samples and `JOrbisAudioStream` — JOrbis, a Java Vorbis
decoder — as the one real implementation. `LoopingAudioStream` wraps any of
them.
## Questions a reader asks
**Why does sound cut out when I plug in headphones?** Because reload is
destroy-and-rebuild, and it arrives from three different doors. The resource
reload arrives as `SoundManager.apply`, which ends by reloading the engine;
`SoundManager.reload` is the *options* path, taken when the audio device is
changed; and `SoundEngine.tick` reloads itself when the device tracker reports
the default device changed. All three tear the OpenAL context down in
`Library.cleanup` and call `SoundEngine.loadLibrary` again.
**Is the sound thread the mixer?** No. `SoundEngineExecutor` does nothing but
run tasks; OpenAL, the native library, does the mixing on its own threads.
The Java thread exists so that per-source AL calls are serialised, and almost
all of them go through it — the exceptions are the bulk teardowns,
`ChannelAccess.clear` and `Library.cleanup`, which release handles directly on
the Render thread once the sound thread is already joined. Confusingly, the
*device* is not the sound thread's either: opening and closing it, resetting
the listener and deleting buffers all happen on the Render thread, and
teardown happens deliberately **after**
`SoundEngineExecutor.shutDown` has joined the sound thread — which makes
`SoundEngine.stopAll` the longer of the two places the game thread blocks on
it.
**Why is there no sound for the first few frames of a world?**
`SoundEngine.updateSource` posts a `ListenerTransform` — position, forward,
up — from the `Camera` every frame, and it no-ops until
`Camera.isInitialized`. The same is true again after
`GameRenderer.resetData`. And the ear is always one frame stale:
`Minecraft.runTick` posts the transform *before* it renders, and the camera
is only advanced inside the frame, so the ear is where the eye was last
frame. At 60 fps nobody hears it — but it is worth knowing before blaming
OpenAL.
**What does the game demand of an audio device?** Three things.
`Library.init` refuses a device without the OpenAL distance-model and
linear-distance extensions, or with an ALC older than 1.1. HRTF is enabled
from `Options.directionalAudio`.
**Does pausing stop everything?** No: `SoundManager.pauseAllExcept` leaves
`SoundSource.MUSIC` and `SoundSource.UI` running, and
`SoundEngine.tickMusicWhenPaused` is the pause-menu tick.
## Where to look
`SoundEngine.play` — the whole resolution-and-dispatch order is one method,
and the early returns in it are audible. `ChannelAccess` and
`ChannelAccess.ChannelHandle` for how a per-source call becomes a task, and
`SoundEngineExecutor` for the thread it becomes a task on.
`SoundEngine.calculateVolume` for the three factors, `Library.init` for the
limits and the device requirements, `SoundBufferLibrary.getCompleteBuffer`
for the decode, and `SoundEngine.tick` for the once-a-tick sweep.
---
*Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes `tools/verify_names.py`.*
---
# What makes a sound happen
> Verified against **Minecraft 26.2** · Part X · you break a block and you hear it: three doors a sound can come through, and only one of them says what the sound is.
Watch someone place a block and the server sends `ClientboundSoundPacket`,
naming the sound. Watch them break the same block and it sends nothing of the
kind: `Block.spawnDestroyParticles` fires a **level event**, and
`ClientboundLevelEventPacket` carries an int and a block-state id. The client
decides for itself what that int means — for a break, `SoundType.getBreakSound`
from the block's own `SoundType` — and plays the result locally. So the two
halves of one interaction reach you by different mechanisms, and the second
one is not a sound at all until your client makes it one.
Then there is the third door, the one people are most surprised by. Both of
those calls name the acting player as the *excluded* entity, so neither packet
is sent to whoever did it — and they do not need to be, because the same
shared code runs on that player's own client, where
`ClientLevel.playSeededSound` plays a sound exactly when the excluded entity
*is* the local player. Your own place and break are predicted, not delivered.
The rule does not generalise, though: `Player.playServerSideSound`, which
plays the six attack sounds, excludes nobody, so your own critical hit is one
of the sounds that does travel the whole way out and back.
This page is the content model and those three doors. The machine that turns
any of them into an OpenAL source is [the sound engine](sound-engine.md).
## The cast
| class | what it decides | thread |
|---|---|---|
| `SoundEvent` | a name and an optional fixed range — never a file | both sides |
| `SoundEvents` | the static registry of every event the game defines | both sides |
| `SoundSource` | which volume slider applies | both sides |
| `SoundEventRegistration` | what one `sounds.json` entry says, and whether it replaces or appends | Render thread |
| `WeighedSoundEvents` | the weighted list a name resolves to, and the redirects in it | Render thread |
| `LevelEventHandler` | what an int from `ClientboundLevelEventPacket` means | Render thread |
| `BiomeAmbientSoundsHandler` | the loop, the random additions and the cave mood | Render thread |
| `MusicManager` | which track, how often, and the fade that is not the slider | Render thread |
## The three doors
```mermaid
flowchart TD
SERVER["something happens on the server"]
NAMED["Level.playSound — the server names a SoundEvent"]
EVENT["Level.levelEvent — the server sends an int and a block-state id"]
CLIENTONLY["nothing is sent at all"]
P1["ClientboundSoundPacket, ClientboundSoundEntityPacket, or the sound inside ClientboundExplodePacket"]
P2["ClientboundLevelEventPacket"]
LEH["LevelEventHandler decides what the int means, using this client's block data"]
LOCAL["ClientLevel.playSeededSound — which plays only when the excluded entity is the local player, so the same call is the prediction path too"]
AMB["BiomeAmbientSoundsHandler, MusicManager, the underwater and bubble-column handlers"]
SM["SoundManager.play"]
SERVER --> NAMED --> P1 --> LOCAL --> SM
SERVER --> EVENT --> P2 --> LEH --> SM
CLIENTONLY --> AMB --> SM
```
The distinction matters for anyone reasoning about the wire.
| | a named sound | a level event | client-side ambience |
|---|---|---|---|
| what crosses | a `SoundEvent` holder, a position in eighths of a block, a seed | an int and a block-state id | nothing |
| who chooses the sound | the server | **this client**, from its own resource pack and block data | this client |
| can it name a sound in no registry | **yes** — `SoundEvent.STREAM_CODEC` sends either a registry id or an id plus a range | no | no |
| examples | a block placed, `/playsound`, a mob's voice | a block broken, a dispenser, fire extinguished, a ghast warning, the wither spawn, the dragon's death | biome loops, cave mood, music, underwater, bubble columns |
That third row is a genuine hole in the usual summary. Data packs cannot
*register* sound events — `Registries.SOUND_EVENT` is a static registry —
but a packet may carry an **inline** `SoundEvent`, so a server can name a
sound that is in no registry at all. The two statements are both true and are
usually run together into a false one.
The other clientbound members of the family are
`ClientboundSoundEntityPacket`, which follows a moving entity,
`ClientboundStopSoundPacket`, which `/stopsound` sends, and
`ClientboundExplodePacket`, which carries its own sound alongside everything
else an explosion needs. There is **no serverbound sound packet** anywhere: the server infers what you did from
other packets and tells everyone else about the sound.
## What a sound *is*, as data
`SoundEvent` — in `net/minecraft/sounds`, and therefore shared — is a record
of an `Identifier` and an optional fixed range. `SoundEvents` is the
2,000-line static registry of every one the game defines. **It is a name, not
a file.**
The file comes from `sounds.json`, one per namespace in every resource pack,
which maps an event name to a `SoundEventRegistration`: a weighted list of
`Sound` entries — a file, or a redirect to another event, per the
`Sound.Type` enum — each with volume, pitch, weight, attenuation distance,
and whether to *stream* rather than load whole. `SoundManager` owns the
loaded form, a map of `Identifier` to `WeighedSoundEvents`, rebuilt on every
resource reload.
Packs merge rather than replace, unless told otherwise.
`SoundEventRegistration` carries a replace flag; without it a higher pack's
entries are **appended** to the lower pack's list, so a pack that adds one
variant gets a mix rather than an override. A redirect entry multiplies
volume and pitch through and ORs the streaming flag.
And there are two kinds of silence. The identifier
`SoundManager.INTENTIONALLY_EMPTY_SOUND_LOCATION` is short-circuited by name
in `AbstractSoundInstance.resolve` before the registry is consulted at all, so
anything asking for it is silenced with no log warning — as distinct from an
event that simply does not resolve, which logs. A pack that empties an event's
list gets the warning, not the silence. `SoundEngine.MISSING_SOUND` is the
development counterpart, which makes a missing sound *audible*, and
`SharedConstants.DEBUG_SUBTITLES` the one that makes every sound *visible*.
`SoundSource` is the volume category and each one is an options slider:
`SoundSource.MASTER`, `SoundSource.MUSIC`, `SoundSource.RECORDS`,
`SoundSource.WEATHER`, `SoundSource.BLOCKS`, `SoundSource.HOSTILE`,
`SoundSource.NEUTRAL`, `SoundSource.PLAYERS`, `SoundSource.AMBIENT`,
`SoundSource.VOICE`, `SoundSource.UI`. Two of those are read wrongly from the
options screen alone: `SoundSource.RECORDS` is the jukebox slider and
`SoundSource.WEATHER` the rain one. Neither is `SoundSource.AMBIENT`.
## Who hears it
`ServerLevel.playSeededSound` asks `SoundEvent.getRange` for the audible
radius — a fixed range if the event declares one, otherwise sixteen blocks
scaled up by volumes above one — and `PlayerList.broadcast` sends the packet
to every player in that dimension within range, **skipping the excluded
player**. The seed travels in the packet so that every client picks the same
random variant and the same pitch, which is why a sound that is one of eight
variants sounds the same to two players standing together.
The position is quantised on the way:
`ClientboundSoundPacket.LOCATION_ACCURACY` is eight, so the wire carries
three ints in eighths of a block.
And **sound has a speed**, for the few callers that ask for it.
`ClientLevel.playLocalSound` takes a distance-delay flag, and when it is set
and the source is more than ten blocks off the sound is deferred by its
distance over a fixed rate, through `SoundManager.playDelayed` into
`SoundEngine.queuedSounds`. Firework explosions and a handful of level events
set it. Thunder, the sound everyone assumes is the reason it exists, does not:
`LightningBolt` passes the flag as false and the crack is instant.
`LocalPlayer.playSound` goes
the other way and calls `ClientLevel.playLocalSound` directly, skipping even
the exclusion check.
## Music and ambience are environment attributes
This is the biggest 26.2 change in the system and the one a 1.21-era reader
will get wrong. *BiomeSpecialEffects* no longer carries music, ambient loops,
additions or mood — it is block tint only. Every one of those is now an
`EnvironmentAttribute` (see [environment attributes and
timelines](../world/environment-attributes-and-timelines.md)):
`EnvironmentAttributes.BACKGROUND_MUSIC`,
`EnvironmentAttributes.MUSIC_VOLUME`,
`EnvironmentAttributes.AMBIENT_SOUNDS` and
`EnvironmentAttributes.FIREFLY_BUSH_SOUNDS`, all syncable, all resolved
through the same dimension-then-biome-then-timeline-then-weather layer stack
as fog and sky colour.
`Minecraft.getSituationalMusic` reads `EnvironmentAttributes.BACKGROUND_MUSIC`
off the camera's attribute probe and asks `BackgroundMusic.select` for the
creative or underwater variant; the End boss fight overrides it directly.
`Minecraft.getMusicVolume` reads `EnvironmentAttributes.MUSIC_VOLUME` the
same way, which is how a biome dims its own music without touching the
slider.
`BiomeAmbientSoundsHandler` reads `EnvironmentAttributes.AMBIENT_SOUNDS` from
`Level.environmentAttributes` and drives three things from it: a cross-faded
loop, random additions on a per-tick chance (`AmbientAdditionsSettings`), and
the cave "mood" that accumulates in darkness (`AmbientMoodSettings`).
`UnderwaterAmbientSoundHandler` and `BubbleColumnAmbientSoundHandler` are the
two that remain plain client-side handlers with no attribute behind them.
`MusicManager` owns the rest: a `MusicManager.MusicFrequency` setting that
scales the gap between tracks, a fade implemented by driving
`SoundManager.updateCategoryVolume` — which is why the music slider and the
music fade are two different numbers — and the now-playing toast, shown or
not depending on the `SoundEngine.PlayResult` the engine returned.
The remaining callers are worth naming because they are the ones that are
neither the world nor the wire: `PlaySoundCommand`, the ambient handlers in
`client/resources/sounds` for loops that exist only on the client, and
`SoundPreviewHandler`, which previews a representative sound per category
while a volume slider is dragged outside a world.
## Where to look
`LevelEventHandler` — the whole second door is one switch, and reading it is
the fastest way to see how much of the game's audio is not named on the wire.
`ServerLevel.playSeededSound` and `PlayerList.broadcast` for who is told;
`ClientLevel.playSeededSound` for the local-player branch that closes the
loop. `SoundEventRegistration` and `WeighedSoundEvents` for the pack model,
and `BiomeAmbientSoundsHandler` for the three ambience mechanisms in one
class.
---
*Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes `tools/verify_names.py`.*
---
# Debugging the running game
> Verified against **Minecraft 26.2** · Part X · a villager's brain drawn over its head: one subscription mechanism, sixteen instances, all of them in the jar you downloaded and fifteen of them unreachable without a JVM flag.
Every one of these sixteen debug subscriptions is compiled into the shipped
client and the shipped dedicated server. Every subscription is in the
registry, every packet is in the protocol, every producer call site is there.
Nothing is stripped. **The client simply never asks** — fifteen of the sixteen
are behind a JVM system property read at startup, and the server has to agree
besides. The idle cost is small but it is not nothing: the producers check for
a subscriber before they work, and the server sweeps every online player's
permissions once a tick whether or not anyone has asked for anything.
That is the pattern the page is about: a registry of subscription kinds, a
per-level engine that sleeps until somebody asks, a poll-and-diff sender, and
about two dozen renderers that turn the results into floating text and boxes.
It is only half a client system — the machinery ships on the dedicated server
— but the client is the only thing that ever asks and the only thing that
draws, and the trace ends in a renderer.
## The cast
| class | what it decides | thread |
|---|---|---|
| `DebugSubscription` | what one kind of debug value is: a stream codec, and an expiry | both sides |
| `DebugSubscriptions` | the sixteen kinds | both sides |
| `DebugValueSource` | which objects can answer which subscriptions | Server thread |
| `ServerDebugSubscribers` | who is subscribed, rebuilt every tick, and the permission rule | Server thread |
| `LevelDebugSynchronizers` | one synchronizer per subscription per level, and the sleep flag | Server thread |
| `TrackingDebugSynchronizer` | the engine: registration, the diff, and the tracking filter | Server thread |
| `ClientDebugSubscriber` | what to ask for, and the maps holding what came back | Render thread |
| `DebugRenderer` | the renderer list, rebuilt when the enabled entries change | Render thread |
## The idea
A `DebugSubscription` is a registry object — held in
`BuiltInRegistries.DEBUG_SUBSCRIPTION` under `Registries.DEBUG_SUBSCRIPTION`
— carrying exactly two things: a nullable stream codec for its value type,
and an expiry in ticks, which is `DebugSubscription.DOES_NOT_EXPIRE` for
most. Its payload wrappers are the records `DebugSubscription.Update` — a
subscription plus an *optional* value, so absence is expressible — and
`DebugSubscription.Event`, a subscription plus a value. Both are serialised
by dispatching on the registry id onto the subscription's own codec.
That is the whole abstraction, and it replaces what used to be a fixed
packet type per kind of information.
`DebugValueSource` is the supply side, implemented by `Entity`, `Mob`, `Bee`,
`Breeze`, `BlockEntity`, `BeehiveBlockEntity` and `LevelChunk`;
`DebugValueSource.registerDebugValues` hands back one
`DebugValueSource.ValueGetter` per subscription the object can answer. On the
client, `ClientDebugSubscriber` keeps what came back, keyed by chunk
position, block position or entity UUID, plus a list of expiring events, and
`ClientDebugSubscriber.createDebugValueAccess` hands renderers a read-only
`DebugValueAccess` view. `DebugRenderer` is a plain list of
`DebugRenderer.SimpleDebugRenderer`s, rebuilt by
`DebugRenderer.refreshRendererList` — and they do not draw either: they emit
through `Gizmos`.
## The sixteen instances
| subscription | carries | fed by |
|---|---|---|
| `DebugSubscriptions.BRAINS` | `DebugBrainDump` | `Mob.registerDebugValues` |
| `DebugSubscriptions.GOAL_SELECTORS` | `DebugGoalInfo` | `Mob.registerDebugValues` |
| `DebugSubscriptions.ENTITY_PATHS` | `DebugPathInfo` | the navigator's current `Path` |
| `DebugSubscriptions.BEES` / `DebugSubscriptions.BEE_HIVES` | `DebugBeeInfo` / `DebugHiveInfo` | `Bee` and `BeehiveBlockEntity` |
| `DebugSubscriptions.BREEZES` | `DebugBreezeInfo` | `Breeze` |
| `DebugSubscriptions.POIS` | `DebugPoiInfo` | `TrackingDebugSynchronizer.PoiSynchronizer`, event-driven |
| `DebugSubscriptions.VILLAGE_SECTIONS` | nothing but presence | `TrackingDebugSynchronizer.VillageSectionSynchronizer` |
| `DebugSubscriptions.RAIDS` / `DebugSubscriptions.STRUCTURES` | positions / `DebugStructureInfo` | `LevelChunk.registerDebugValues` |
| `DebugSubscriptions.GAME_EVENT_LISTENERS` | `DebugGameEventListenerInfo` | the listener registry |
| `DebugSubscriptions.GAME_EVENTS` | `DebugGameEventInfo` | the dispatcher — an *event*, expiring after 60 ticks |
| `DebugSubscriptions.NEIGHBOR_UPDATES` | a position | a listener installed on the neighbour updater — an event, 200 ticks |
| `DebugSubscriptions.ENTITY_BLOCK_INTERSECTIONS` | `DebugEntityBlockIntersection` | `Entity`, pushed directly, 100 ticks |
| `DebugSubscriptions.REDSTONE_WIRE_ORIENTATIONS` | an `Orientation` | the experimental wire evaluator, 200 ticks |
| `DebugSubscriptions.DEDICATED_SERVER_TICK_TIME` | **no value at all** | see *the sample path* below |
"Expires after *n* ticks" means two different things across those four
expiring rows. For the two *event* kinds it is how long the event stays on
screen; for the two *pushed-value* kinds it is a time-to-live on a stored
value. Only the client purges, and only for subscriptions that declare an
expiry at all.
## One instance traced: a villager's brain
```mermaid
sequenceDiagram
participant CDS as ClientDebugSubscriber
participant CPL as ClientPacketListener
participant SGPL as ServerGamePacketListenerImpl
participant SDS as ServerDebugSubscribers
participant LDS as LevelDebugSynchronizers
participant TDSS as TrackingDebugSynchronizer.SourceSynchronizer
participant BDR as BrainDebugRenderer
CDS->>CDS: requestedSubscriptions — the JVM was started with the brain flag
CDS->>SGPL: ServerboundDebugSubscriptionRequestPacket with BRAINS
SGPL->>SGPL: ServerPlayer.requestDebugSubscriptions — stored, not yet honoured
Note over SDS: end of the next server tick
SDS->>SDS: tick — is this player op, or the owner of an IDE singleplayer world?
Note over LDS: the tick after that
LDS->>LDS: tick — subscribers exist, so wake up
LDS->>TDSS: registerChunk and registerEntity for everything already tracked
TDSS->>TDSS: Mob.registerDebugValues gives a ValueGetter for BRAINS
loop every server tick
TDSS->>TDSS: pollUpdate — takeBrainDump, compare with the last value sent
TDSS->>CPL: ClientboundDebugEntityValuePacket — only if it differs
end
CPL->>CDS: updateEntity — stored under the villager's UUID
Note over BDR: next frame
BDR->>BDR: emitGizmos — reads through DebugValueAccess
BDR->>BDR: Gizmos.billboardTextOverMob — appended, drawn later in the frame
```
The engine is the middle three steps, and it has three properties worth
naming. **Nothing exists until somebody asks**: the level's synchronizers
start asleep, and the first non-empty subscriber set wakes them and
retroactively registers every ready chunk and every tracked entity.
**Nothing is sent twice**: each value source keeps the last value it sent and
compares. And **nothing reaches a player who cannot see it**: sending is
filtered by subscription *and* by whether that player is tracking the chunk
or entity. When the last subscriber goes away the whole thing is cleared.
The three cadences: `ClientDebugSubscriber.tick` runs from
`ClientPacketListener.tick`, once per client tick, and sends only when the
wanted set differs from the last one sent.
`ServerDebugSubscribers.tick` runs from `MinecraftServer.tickChildren`
*after* the levels have ticked, while each `LevelDebugSynchronizers.tick`
runs *inside* its level's tick — so **every level acts on the previous tick's
subscriber snapshot**, a built-in one-tick lag. And `DebugRenderer.emitGizmos`
runs inside `LevelExtractor.extract`, after entities, block entities,
particles, sky and clouds, fetching one `DebugValueAccess` for the whole
pass.
## The exceptions
Every pattern page's real content.
**Two gates, and the second is not a flag.** Fifteen of the sixteen kinds
are behind `SharedConstants.DEBUG_ENABLED` *and* an individual flag, both
read from JVM system properties at startup — the only subscription an F3 key
can reach is the dedicated server's tick time, through the FPS charts. And
the server still has to agree: `ServerPlayer.debugSubscriptions` returns
nothing unless `ServerDebugSubscribers.hasRequiredPermissions` passes, which
means op on the player list, or the owner of a singleplayer world run from an
IDE. On a normal singleplayer world that means cheats must be on.
**Producers check before they work.** Path finding only records its open and
closed node sets when somebody wants paths; entities only collect block
intersections when somebody wants them; the neighbour updater's debug
listener is only installed while someone is subscribed. There are two gates
of that name and the producers do not agree on which to use: path finding and
block intersections ask `ServerDebugSubscribers.hasAnySubscriberFor`, the live
map, while the neighbour updater asks
`LevelDebugSynchronizers.hasAnySubscriberFor`, which reads the level's
snapshot from the previous tick. The change detection, by contrast, is
*record equality*: a
brain dump is rebuilt every tick per villager and compared with the last one
sent — so the saving is in bandwidth, not in server time.
**About half the renderers do not use this system at all.** The chunk debug
renderer reaches directly into `Minecraft.getSingleplayerServer` and shows
nothing in multiplayer; the entity hitbox renderer reaches for it too, but
only for its optional *server* hitbox — its ordinary client hitboxes are
drawn for every visible entity, on any server, from an F3 entry rather than a
flag. And a
whole family of them — chunk borders, light, collision boxes, height maps,
the section octree — are purely client-side views that need no server.
**One flag combination under-delivers, and says so.** The POI renderer's
ticket-holder rows are behind an explicit `SharedConstants.DEBUG_BRAIN` test,
so running with the POI flag alone gives POI boxes with their own two labels
and none of the brain ones. The bee flag avoids needing the test at all by
explicitly also requesting goal selectors.
**Subscriptions survive a dimension change and a death, but not a
reconnect.** A dimension change keeps the same `ServerPlayer`, so the set is
simply never touched; a respawn builds a new one and `ServerPlayer.restoreFrom`
copies the requested set across. A fresh login starts empty, and the client
re-sends on its next tick because `ClientDebugSubscriber` was cleared at
login.
## The sample path, which shares only the subscriber map
The performance charts are a separate and much simpler system. A
`SampleLogger` takes a vector of longs; partial values are logged during a
tick and a final call flushes the whole vector. There are two implementations
and the difference is the whole story: `LocalSampleLogger` **is** the storage
— a `SampleStorage` ring buffer the charts read directly — while
`RemoteSampleLogger` stores nothing and broadcasts a
`ClientboundDebugSamplePacket` if anyone is subscribed.
So a dedicated server measures its tick with a remote logger and sends, while
`IntegratedServer.getTickTimeLogger` hands back **the client's own** local
logger and reports logging as unconditionally enabled — singleplayer TPS
never touches the network. Client-side, `DebugScreenOverlay` owns four local
loggers with four different feeders: the frame time from the loop, the tick
time from either of the two paths above, the ping from `PingDebugMonitor`
(which sends its own ping requests, and only while the network charts are
shown), and the bandwidth from a `BandwidthDebugMonitor` that counts bytes on
the Netty thread and is drained by `Connection.tick`.
Six packets carry all of this: `ServerboundDebugSubscriptionRequestPacket`
outbound, and `ClientboundDebugChunkValuePacket`,
`ClientboundDebugBlockValuePacket`, `ClientboundDebugEntityValuePacket`,
`ClientboundDebugEventPacket` and `ClientboundDebugSamplePacket` inbound —
for a system that used to have one per subject.
> **For a 1.21-era reader.** The fixed set of debug packets is gone. Instead
> of one packet type per kind of debug information there is one
> `DebugSubscription` registry and three generic value packets that dispatch
> on the registry id. The debug *screen* is a different system again — the F3
> entry registry described in [the HUD](hud.md) — and it is not a light
> touch: the F3 entries decide whether eleven of the twenty-five renderers
> exist at all when the list is rebuilt, and the FPS charts gate the tick-time
> subscription.
## Where to look
`DebugSubscriptions` for the catalogue and `DebugSubscription` for how little
a subscription is. `TrackingDebugSynchronizer` for the engine — the tracking
diff, the back-fill and the equality check are all in that one class.
`LevelDebugSynchronizers.tick` for the sleep flag, `ClientDebugSubscriber`
for both ends of the client's half, and `DebugRenderer.refreshRendererList`
for which renderers exist and why.
---
*Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes `tools/verify_names.py`.*
---
# XI · Rendering
> Verified against **Minecraft 26.2** · Part XI · one thread, a hundred-odd times a second, turning a world nobody can see into a picture — and two layers of machinery underneath that never touch the world at all.
A frame is one method call. Most of this part happens inside
`Minecraft.renderFrame`, on the same thread that ticked the world a moment
earlier, and it happens twice over: once to *copy* the live game into a pile
of immutable value objects, and once to *draw* those objects with the live
game held at arm's length. The exceptions are worth naming up front, because
each is a page: particles are stepped from the *tick*, sections are meshed on
a background pool, and the atlases are built by a resource reload. A player recognises the part by the seams in
that arrangement — terrain filling in outward as you fly, a block you placed
appearing before the server has agreed to it, a mob that pins in place under
`/tick freeze` while you keep moving, a resource pack that makes the game
stop for a second and come back looking different.
It is also the largest thing on the client by a distance. Counting
`client/renderer`, `client/model` and `com/mojang/blaze3d` together — one class per file, one
line per line of decompiled source, the same way [the atlas](../../maps/README.md)
counts everything else — the renderer is **1,179 classes and 87,000 lines**,
against 420 classes and 53,000 for the whole of `net/minecraft/server`. This
part does not cover all of it, and [what this book
skips](../anatomy/what-this-book-skips.md) says which parts are declined and
why.
## The shape of the part
Part XI is **a substrate under a pipeline**. Two of its pages — the window
and Blaze3D — are what the renderer stands on: neither has a trace through
the world, and both are cited from the pages above rather than the other way
round — Blaze3D by eight of the other ten, the window by two. The rest really is a
pipeline, in the order things happen inside one frame. `the-frame` opens the
part because it is the shortest way to see the whole shape at once, and
because a reader who has watched one frame end to end has a reason to care
what a `GpuDevice` is.
```mermaid
flowchart TD
FRAME["The frame — extract, then draw"]
subgraph SUB["the substrate — no trace through the world, made once at startup"]
direction LR
WIN["The window"]
B3D["Blaze3D"]
end
subgraph PIPE["the pipeline — the order to watch them in, and what each page needs from the last"]
direction TB
VIS["Visibility and the frame graph"]
MESH["Section meshing"]
MOD["Models and atlases"]
ENT["Entity rendering"]
BEN["Block-entity rendering"]
SKY["Lightmap, fog and sky"]
PART["Particles"]
POST["Post-processing"]
VIS -- "the sections it decided to draw" --> MESH
MESH -- "and where a section's quads came from" --> MOD
MOD -- "the same atlases, a different pipeline" --> ENT
ENT -- "and the things that are neither terrain nor entity" --> BEN
BEN -- "and every draw needs a colour" --> SKY
SKY -- "plus the quads that are not geometry" --> PART
PART -- "then the finished picture, read back" --> POST
end
SUB --> FRAME
FRAME --> PIPE
```
Read the substrate arrow as *depends on*, not as *happens before*: the window
and the device are made once at startup and never again. The pipeline arrows
are not frame order either — inside a frame the sky pass is declared before the
main one, and the lightmap is built before the world is drawn at all. They are
the order to watch the pages in, each labelled with what the next one needs
from the one before. The last arrow is the one the figure flatters: **two**
of the six post chains append their passes to the very frame graph the
visibility page describes, and the other four build a graph of their own and
throw it away. They are one machine because they are one loader, one schema
and one pass class — not because they all end up in one graph.
## Before you start
[The client loop](../client/the-client-loop.md) from Part X, and not
optionally: it is the page that says *when* a frame happens and how many
ticks ran before it, and this part begins exactly where that page's *frame*
zone opens. It is one of the two longest pages in Part X, so budget for it.
[The resource system](../foundations/resource-system.md) from Part II before
[models and atlases](models-and-atlases.md), which is a reload listener and
leans on the barrier semantics taught there rather than restating them.
[Environment attributes and timelines](../world/environment-attributes-and-timelines.md)
from Part IV before [lightmap, fog and sky](lightmap-fog-and-sky.md). Part IV
owns that system; Part XI is its client-side consumer and deliberately does
not re-teach it.
[The client level](../client/the-client-level.md), for what the thing being
drawn actually is — a `Level` with its authority removed — and for the two
ways `LevelExtractor` is reached, pushed and pulled.
## Watch in this order
1. [The frame](the-frame.md) — one method, two halves, and a wall between
them. Nine profiler zones, six clocks disagreeing on purpose, and a failed
surface acquisition that costs you the picture but not the work.
2. [The window](the-window.md) — the substrate nothing else admits to
needing. A retry loop that creates a window and a graphics backend
together, six operating-system callbacks of which the game hears two, and
`NativeImage`, the seam between a file and a texture.
3. [Blaze3D](blaze3d.md) — the game's own graphics API, and the part's
vocabulary page. Four validating façades over two real backends, one of
which is Vulkan and is the larger of the two.
4. [Visibility and the frame graph](visibility-and-the-frame-graph.md) — what
the frame decides to draw, and in what order. A reachability walk whose
asymmetry — uncompiled sections stop it, empty ones do not — is why
terrain reveals itself outward.
5. [Section meshing](section-meshing.md) — where the triangles came from. A
block is placed, a halo of positions goes dirty, a worker compiles a
snapshot, and the swap happens frames later and all at once.
6. [Models and atlases](models-and-atlases.md) — the reload pipeline behind
every quad. Thirteen atlases stitched in parallel, one barrier, and a
quad whose chunk layer is read out of its sprite's pixels.
7. [Entity rendering](entity-rendering.md) — everything in the world that is
not terrain, in four stages, none of which is called *render*. The zombie
is animated at least twice per frame.
8. [Block-entity rendering](block-entity-rendering.md) — the same four
stages with three differences that show. A chest's block model is empty,
a block entity is culled by its section rather than by the frustum, and
the chest in your hand is drawn by a different renderer at a different
partial tick.
9. [Lightmap, fog and sky](lightmap-fog-and-sky.md) — what colour all of it
is. One question asked five times over, by renderers that mostly no
longer know what time it is.
10. [Particles](particles.md) — the part's policy page: three distance rules
enforced in three places and three readers of one setting who disagree
about what its values mean, with a break puff that answers to almost
none of them.
11. [Post-processing](post-processing.md) — the closer. Six JSON-declared
shader chains, which is how the pause-menu blur and the creeper
spectator shader turn out to be the same machine — and a resource pack
can rewrite all six and add none.
Four and five are a pair — they were one page until pass 3, and they are
still one journey seen from its two ends — and so are seven and eight, the
second of which is written as the differences from the first. One to three can be watched in
order or in the order one, three, two; the window is the page a viewer is
most likely to skip and least likely to regret.
## Reference this part uses
[Submit phases and feature renderers](../../reference/submit-phases.md) is
the catalogue behind [entity rendering](entity-rendering.md) and
[block-entity rendering](block-entity-rendering.md): the fifteen phases a
submitted feature can land in, in declaration order, and the thirteen
renderers that write the vertices. [Diagram
lanes](../../reference/lanes.md) for the abbreviations these figures use, and
[the threads](../../reference/threads.md) for the two that matter here — the
one the whole part runs on, and the background pool that meshes sections.
[Naming drift](../../reference/naming-drift.md) is worth having open for this
part in particular: the client was rewritten around extract-then-render, so
almost nothing at the top of the render stack kept the name a 1.21-era
reader knows it by. [The glossary](../../reference/glossary.md) for *extract*, *render
state*, *frame graph*, *partial tick*, *atlas*, *special model renderer* and
*built-in block model*.
Where the part stops: what draws *over* the world rather than in it — screens,
the HUD, the render tree they record into and the text inside them — is Part
X, from [the GUI render tree](../client/the-gui-render-tree.md) onward. What
the server chose to tell this client in the first place is Part IX.
---
*Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes `tools/verify_names.py`.*
---
# The frame
> Verified against **Minecraft 26.2** · Part XI · one frame: from acquiring a surface texture to handing it back, and the wall in the middle that the drawing half is not allowed to look past.
The number in the top-left says 143 fps, and each of those 143 is one call to
`Minecraft.renderFrame` — one method with two halves. The first walks the
live game and copies everything drawable into a `GameRenderState`; the second
draws that state and nothing else. Before either half runs, the frame asks
[the window](the-window.md) for somewhere to put the picture, and the
surprising thing is what happens when that request fails. It does not skip
the frame. It skips the *picture*. The world still renders in full into
`GameRenderer.mainRenderTarget`, the GUI still goes on top, the framerate
limiter still parks; only the blit and the present are guarded on a surface
having been acquired, and both are quietly skipped. A minimized window is the same story with
no attempt made at all — a client drawing complete frames that nobody will
ever see.
All of this is one thread — the same one that ticked the world a moment
earlier. How many ticks ran before the
frame, and what paced it afterwards, is [the client
loop](../client/the-client-loop.md); this page starts where that page's
*frame* zone opens.
## The cast
| class | what it decides | thread |
|---|---|---|
| `Minecraft` | the zones, the acquire guard, and whether this frame advances game time at all | Render thread |
| `GameRenderer` | the frame's owner of everything drawn — the snapshot target, the main render target, the camera, the lightmaps | Render thread |
| `GameRenderState` | what the drawing half is allowed to read, and therefore where the wall is | written in *extract*, read in *render* |
| `Camera` | where the eye is, how wide the view is, and how wide the *cull* frustum is — which is not the same number | Render thread, but ticked from `GameRenderer.tick` |
| `LevelExtractor` | which of the live world becomes drawable state, at one partial tick **per entity** | Render thread |
| `LevelRenderer` | the world half — handed a `CameraRenderState`, never a `Camera` | Render thread |
| `GuiRenderer` | the GUI half, with its own `StagedVertexBuffer` and its own `FeatureRenderDispatcher` | Render thread |
| `GpuSurface` | whether there is anywhere to put the picture: acquire, blit, present | Render thread |
## Nine zones, which are the frame's table of contents
Naming the profiler zones in order is the shortest honest description of what
a frame is: *update window* · *update* · *extract* · *gpuAsync* · *render* —
which pushes *world* and *gui*, and inside the world *matrices*, *fog*,
*level*, *hand*, *screenEffects* — then *present* · *swapBuffers* ·
*frameLimiter* · *fpsUpdate*. One of those names is a lie, and the section on
presentation below says which.
```mermaid
sequenceDiagram
participant MC as Minecraft
participant GpuS as GpuSurface
participant Camera as Camera
participant GR as GameRenderer
participant LX as LevelExtractor
participant LR as LevelRenderer
participant GuiR as GuiRenderer
MC->>GpuS: update window — reconfigure if needed, then acquireNextTexture
Note over MC,GpuS: vsync lives here, as a GpuSurface.PresentMode
MC->>MC: update — advanceRealTime, the timer query, pauseIfInactive, Gui.update
MC->>MC: ClientLevel.update — the client's own light engine, ticking frames only
GR->>Camera: Camera.update — align, fov, cull frustum, perspective
MC->>MC: Minecraft.pick writes Minecraft.hitResult
GR->>LX: extract — window, options, lightmap, camera, the level, the GUI
Note over GR,LX: the wall. Everything after this reads GameRenderState
MC->>MC: gpuAsync — RenderSystem.executePendingTasks drains signalled fences
GR->>GR: render — resize if needed, clear, the lightmap, then the world
GR->>LR: LevelRenderer.render with a CameraRenderState, no live game object
GR->>GR: the held item under a second projection, then the screen effects
GR->>GuiR: render, then endFrame
MC->>GpuS: present — blitFromTexture from GameRenderer.mainRenderTarget
MC->>GpuS: swapBuffers — CommandEncoder.submit, then GpuSurface.present
Note over MC: frameLimiter, then fpsUpdate
```
Read it as **acquire, snapshot, draw, present** — and note that only the
first and last of those four touch the surface.
## Acquire, and the frame that carries on without one
The *update window* zone reconfigures the surface if it needs it and then
calls `GpuSurface.acquireNextTexture`. Vsync is not a swap interval any
more: it is a `GpuSurface.PresentMode` baked into the surface configuration,
so toggling it in the options forces a reconfigure —
`Minecraft.invalidateSurfaceConfiguration` — rather than setting a flag.
When the acquisition throws, the surface is marked invalid, a reconfigure is
scheduled, and the frame goes on as if nothing had happened but for a line in
the log. Exactly two later statements re-test whether a surface is actually
held, and both are in `Minecraft.renderFrame` itself: the blit and the
present are each guarded on `GpuSurface.isAcquired`, with nothing on the
other branch.
The tolerance is the caller's, not the surface's — `GpuSurface.blitFromTexture`
and `GpuSurface.present` both throw if you reach them without one. Everything
between those two guards — the extract, the world, the GUI — is paid in
full.
There is one guard on the whole method, and it is about re-entry rather than
failure: if the surface is *already* acquired when `Minecraft.renderFrame` is
called, the call is a silent no-op.
## Update and extract: six clocks in one frame
The *update* zone advances the real-time clock, reads
`Minecraft.timerQuery` — the GPU-side stopwatch behind the F3 utilisation
figure — runs `Minecraft.pauseIfInactive` and updates the GUI. On ticking
frames `ClientLevel.update` runs the client's own light engine. Then
`GameRenderer.update` calls `Camera.update`, and `Minecraft.renderFrame`
follows it with `Minecraft.pick` — a private method of `Minecraft`, not the
renderer's — which writes `Minecraft.hitResult` for the crosshair and the
block outline to find later.
`Camera` is split three ways across the client, and two of the three are here.
`Camera.tick` — driven from `GameRenderer.tick`, not from the frame — smooths
the eye height and the field-of-view modifier and advances the camera's
`EnvironmentAttributeProbe`. `Camera.update` does the frame's work:
`Camera.alignWithEntity`, `Camera.calculateFov`, `Camera.prepareCullFrustum`,
`Camera.setupPerspective`. `Camera.extractRenderState` then copies the result
across the wall.
*Extract* opens by snapshotting the framerate limit into
`GameRenderState.framerateLimit` and goes on to copy the window, the options,
the lightmap, the camera and the level. It is here that the frame's oddest
number appears: there is no such thing as *the* partial tick of a frame.
There are six, they disagree on purpose, and one of them is not a partial
tick at all.
| who is interpolated | which value | what it ignores |
|---|---|---|
| the world | `DeltaTracker.getGameTimeDeltaPartialTick` | nothing — frozen time is honoured |
| the camera and the held item | `Camera.getCameraEntityPartialTicks` | freezing, unless the *camera entity itself* is frozen, which a player never is |
| the lightmap | a literal one | everything — it is extracted fully advanced |
| screens and overlays | `DeltaTracker.getGameTimeDeltaTicks` | the fraction — this is the whole delta since the last frame, not a position inside a tick |
| the autosave indicator and the title-screen panorama | `DeltaTracker.getRealtimeDeltaTicks` | the game clock, and anything past seven ticks, where it clamps to a half |
| each entity, separately | its own frozen-honouring value, asked for by `LevelExtractor` | the world's single answer |
The last row is the one you can see. Under `/tick freeze` a mob pins at the
end of its last tick while players go on interpolating — in the same frame,
from the same extract. The exception is worth knowing, because it is the one
a player rides: `TickRateManager.isEntityFrozen` excludes anything with a
player aboard, so the horse under you keeps moving smoothly while the horse
beside you is a statue.
## The wall, and the one level at which it is real
`LevelRenderer.render` is handed state and reads no live *game* object: no
`ClientLevel`, no `Minecraft`. That is the wall, and one level down it holds.
It still reaches back into live *renderer* objects for the main target and
the shader manager, but no live game object is among them.
At the top it leaks, and the leak is sharper than the naming suggests.
`GameRenderer.render` reads whether the game has finished loading, whether a
level exists, and the world's game time, every frame including GUI-only ones.
Inside the world half, `GameRenderer.shouldRenderBlockOutline` reads the
**live camera entity during rendering**, and in adventure or spectator mode
goes further: for a player who may not build it reads `Minecraft.hitResult`,
looks a `BlockState` up in the level and asks the game mode what it is, all
mid-draw. The interesting fact is not that a wall exists but that it is
drawn one level below where *extract then render* implies it is.
A resize is handled inside the render half rather than before it:
`GameRenderer.render` opens by comparing `GameRenderState.windowRenderState`
against the main target's size and resizing the renderer inline when they
differ. The snapshot is what the frame believes the window size to be, even
if the window has changed since.
The buffers the halves share are far smaller than a 1.21-era reader expects.
`RenderBuffers` holds `RenderBuffers.fixedBufferPack`, the section-meshing
scratch `RenderBuffers.sectionBufferPool` — capped by processor count and
again by a memory budget — and a single shared
`RenderBuffers.stagedVertexBuffer` released by `RenderBuffers.endFrame`, with
the GUI keeping a second staged buffer of its own inside `GuiRenderer`.
Geometry submission moved to `SubmitNodeCollector` and `SubmitNodeStorage`,
drawn either by the passes of the frame graph in [visibility and the frame
graph](visibility-and-the-frame-graph.md) or by
`FeatureRenderDispatcher.renderAllFeatures` — which, despite the name, is
**not** how the level is drawn. It has four call sites: the held item, the
screen effects, the GUI's item atlas and picture-in-picture. The last two are
why the GUI needs submit storage at all. The world's submitted features are
prepared into the frame graph and drawn by its passes.
## Present, swapBuffers, and which of the two names lies
**Nothing is presented in the zone called *present*.** That zone does the
blit from `GameRenderer.mainRenderTarget` to the acquired surface texture.
The submit and the actual `GpuSurface.present` happen in the *swapBuffers*
zone after it. Only one of the two names lies: *swapBuffers* is honest, since
on the OpenGL backend `GpuSurface.present` is a single call to GLFW's
buffer swap.
The last two zones are bookkeeping. *frameLimiter* spends the limit that
*extract* snapshotted, parking only below a threshold, so the top slider
position never parks at all; `FramerateLimitTracker` is what may have
overridden the player's option before the snapshot was taken, when the window
is iconified, after a spell of idleness, or in a menu with no level. Then
*fpsUpdate*, and the frame is over.
## Questions players ask
**Why does lowering a spyglass never reveal a hole in the world?** Because
the cull frustum is deliberately wider than the camera.
`Camera.createProjectionMatrixForCulling` builds its matrix from the larger
of the current and the *configured* field of view; `Camera.prepareCullFrustum`
turns that into `Camera.cullFrustum`, and `Camera.extractRenderState` copies
it across the wall into `CameraRenderState.cullFrustum`. Sprinting and flying raise the live FOV
above the option, so for them the maximum does nothing. It bites when
something **narrows** the view — a spyglass, a drawn bow, the dying-camera
effect — where culling against the configured FOV keeps the geometry a
narrowed view would have thrown away.
**Why is the HUD not shaded by the light the player is standing in?**
Because it is lit by a one-pixel white texture. `GameRenderer.lightmap` hands
out `GameRenderer.uiLightmap` for as long as `GameRenderer.useUiLightmap` is
set, which is exactly the GUI block; `GameRenderer.levelLightmap` always
returns the real one.
**Why does the world go strange when spectating a creeper?** The post-effect
chain is chosen by what you are spectating, not by an option:
`GameRenderer.checkEntityPostEffect` switches on the camera entity's type and
sets `GameRenderer.postEffectId` from it, and F4
(`GameRenderer.togglePostEffect`) flips it off and on. What the chain then
does to the picture is [post-processing](post-processing.md).
**Does minimizing the window save the client any work?** A great deal, but
not where you would look for it. Every zone still runs: the acquire is not
attempted and the blit and the present are skipped, which is three calls.
The saving is the limiter. `FramerateLimitTracker.getThrottleReason` tests
iconification *first*, ahead of idleness and the menu, and answers with a
limit of ten — so *frameLimiter* parks the thread for most of every hundred
milliseconds and the client draws its unseen frames about ten times a second
instead of at the player's setting. On top of that, losing focus for half a
second pauses a singleplayer world outright through
`Minecraft.pauseIfInactive`, and then there is no world left to draw.
**Where does the main menu's panorama come from?** The game, on the same two
halves. `Minecraft.grabPanoramixScreenshot` runs `GameRenderer.update`,
`GameRenderer.extract` and `GameRenderer.renderLevel` six times at
4096×4096, with a fixed delta of one and the camera in panoramic mode —
which is what `CameraRenderState.isPanoramicMode` exists for. A second,
silent screenshot path inside the world half writes the world icon,
singleplayer only, and only once enough sections have actually been rendered.
**When does the client draw a frame that runs no ticks?** Whenever
`Minecraft.renderFrame` is passed false for the flag that says this frame
advances game time. Three call sites do: the two loops that wait for the
integrated server to start and to stop, and `Minecraft.setScreenAndShow`,
which forces a single frame so that a screen appears during blocking work.
Such a frame has no ticks, no client lighting and no world render at all —
it is GUI only, and [GUI and screens](../client/gui-and-screens.md) is the
half that survives.
> **For a 1.21-era reader.** The rendering model is now **extract then
> render**: `GameRenderer.extract` copies the live game into a
> `GameRenderState` and `LevelRenderer.render` is handed a
> `CameraRenderState` rather than a `Camera`, because by the time it runs the
> camera is allowed to have moved on. Names to stop hunting for:
> *Minecraft.getMainRenderTarget* (now `GameRenderer.mainRenderTarget`),
> *Camera.setup* (now `Camera.update` plus `Camera.extractRenderState`),
> *GameRenderer.getProjectionMatrix* and *resetProjectionMatrix*,
> *Window.updateDisplay*, *LightTexture* (now `Lightmap`), and
> *MultiBufferSource* with every buffer source that used to hang off
> `RenderBuffers` — none of them exist. Most `Camera` accessors lost their
> *get* prefix (`Camera.position`, `Camera.entity`, `Camera.rotation`,
> `Camera.forwardVector`), though `Camera.getCullFrustum`, `Camera.getFov`
> and `Camera.getCameraEntityPartialTicks` kept theirs.
## Where to look
`Minecraft.renderFrame` — the frame is one method, and its profiler zones are
its table of contents. Then `GameRenderer.extract` and `GameRenderer.render`
for the wall between live objects and drawing, `GameRenderer.tick` for the
per-tick half nobody expects a renderer to have, `Camera.update` for how the
view is decided, and `GpuSurface.present` — in the zone called *swapBuffers*
— for where a frame ends. The GPU abstraction underneath all of it is
[blaze3d](blaze3d.md).
---
*Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes `tools/verify_names.py`.*
---
# The window
> Verified against **Minecraft 26.2** · Part XI · before the first frame: the game asks the operating system for a window, and finds out which graphics backend it has by which window survives.
The process is a second old. There is no world, no renderer, no resource
pack and nothing to draw. `GLX._initGlfw` has just brought GLFW up,
`NativeLibrariesBootstrap` has probed for the GL and Vulkan loaders and
`MonitorManager` has enumerated the monitors and the video modes each of them
offers. Now `Minecraft` wants a window — and it cannot ask for one without
already having decided how the pixels will be drawn. **The window and the
graphics backend are created together and neither can go first**: an OpenGL
window and a Vulkan window are made from different GLFW hints, so a Vulkan
attempt that gets a window and then fails at the device does not get to keep
the window. It is thrown away with the attempt, and the next candidate starts
from a fresh one.
[The frame](the-frame.md) is the lecture you watch first, and it opens on a
surface that has already been acquired. This page is what acquired it.
[Input and keybinds](../client/input-and-keybinds.md) opens on a callback that
has already fired, and [blaze3d](blaze3d.md) on a `GpuDevice` that already
exists. All three of them start here.
## The cast
| class | what it decides | thread |
|---|---|---|
| `Minecraft` | which backends to try, in which order, and when to give up | Render thread |
| `Window` | the GLFW handle, the three sizes, and every fullscreen transition | Render thread |
| `GpuBackend` | the window hints, and whether a failed window is the backend's fault | Render thread |
| `MonitorManager` | which monitor the window is considered to be on | Render thread |
| `Monitor` | which `VideoMode` an exclusive fullscreen switch takes | Render thread |
| `WindowEventHandler` | which of the six operating-system callbacks reach the game | Render thread |
| `FramerateLimitTracker` | what an iconified, idle or menu-bound window is allowed to cost | Render thread |
| `NativeImage` | the CPU-side pixels between a file and a texture | native memory, closed by its owner |
All of it lives in *com/mojang/blaze3d/platform*, and none of it exists on the
server: `server-classes.txt` has no entry under *com/mojang/blaze3d* at all.
## Trying backends until one of them makes a window
The startup path is a retry loop, and it is drawn as a flowchart rather than a
conversation because the shape *is* the fact: the loop encloses the window and
the device together. A backend that cannot make a window and a backend that
cannot make a device fail identically, and both hand the next candidate a
clean slate.
```mermaid
flowchart TD
GLX["GLX._initGlfw brings GLFW up, NativeLibrariesBootstrap probes the loaders"]
MonM["MonitorManager enumerates the monitors and their VideoModes"]
MC["Minecraft takes the next candidate from PreferredGraphicsApi.getBackendsToTry"]
GB["GpuBackend.setWindowHints — OpenGL and Vulkan want different ones"]
Window["a new Window: create the GLFW window, then register the six callbacks"]
Q1{"did a window appear?"}
ERRS["GpuBackend.handleWindowCreationErrors reads what GLFW complained about"]
DEV["GpuBackend.createDevice against the window handle, with the shader source and the debug options"]
Q2{"did a device come back?"}
KILL["close the window — its hints are wrong for the next candidate"]
LEFT{"any candidate left?"}
BOX["MessageBox.error, and the game never starts"]
RS["RenderSystem.initRenderer with the device that survived"]
DONE["setIcon, setTitle, setDefaultErrorCallback"]
GLX --> MonM --> MC --> GB --> Window --> Q1
Q1 -- "no" --> ERRS --> LEFT
Q1 -- "yes" --> DEV --> Q2
Q2 -- "no" --> KILL --> LEFT
Q2 -- "yes" --> RS --> DONE
LEFT -- "yes" --> MC
LEFT -- "no" --> BOX
```
What the window is asked for is a `DisplayData`: a size, an optional
fullscreen size and a fullscreen flag, with `DisplayData.withSize` and
`DisplayData.withFullscreen` for the transitions that change them later. What
comes back, if anything comes back, is a `Window` holding a `Window.handle`
and a `Window.backend` — and never a `GpuDevice`. The window knows which
backend made it and nothing about what that backend went on to build.
Below GLFW and STB, reached through LWJGL, the window itself calls almost
nothing else in the game — the exceptions are the three classes that have to
report a failure upward, which reach for `Minecraft`, `CrashReport` and the
server's watchdog. Above it, `Minecraft` drives startup and the two per-frame calls
below, `KeyboardHandler` and `MouseHandler` take the input callbacks and the
clipboard, `VideoSettingsScreen` drives the fullscreen and video-mode
controls, and `Screenshot` and `TextureManager` want `NativeImage`. What the
player's saved choices reach is `Options`: an override width and height, the
fullscreen flag and video-mode string, exclusive fullscreen, the GUI scale,
and the graphics-API preference that ordered the loop above. The window's
*position* is not among them — it is a field the move callback keeps and
nobody saves.
## Six callbacks are the entire surface, and the game hears two
Once the window exists, `Window`'s constructor registers six GLFW callbacks,
and they are almost the whole of what the operating system can say to it —
a seventh, the close callback, is added later by `Minecraft` and is the
subject of the last section. `WindowEventHandler` — a three-method interface
that `Minecraft` implements — is the whole of what a window is allowed to say
back to the game, and the window only ever reaches for two of those three
methods.
```mermaid
flowchart LR
OS["the operating system, through GLFW"]
FB["framebuffer size changed"]
CE["cursor entered the window"]
SZ["window resized"]
PS["window moved"]
FC["focus gained or lost"]
IC["iconified or restored"]
WEH["WindowEventHandler, implemented by Minecraft"]
W["a field on the Window, for whoever asks later"]
OS --> FB --> WEH
OS --> CE --> WEH
OS --> SZ --> W
OS --> PS --> W
OS --> FC --> W
OS --> IC --> W
```
`WindowEventHandler.framebufferSizeChanged` and
`WindowEventHandler.cursorEntered` are the two. A window resize, a window
move, a focus change and an iconify all end in a field — `Window.getX`,
`Window.getY`, `Window.isFocused` and `Window.isIconified` are what anyone
asks instead, whenever they get round to it — so four of the six events the
operating system reports are things the game is never *told*, only things it
can look up. `Window.isMinimized` is the one that reads like a fifth and is
not: it is set by the framebuffer callback, which fires with a zero-by-zero
size when the window goes away, and cleared by the same callback when a real
size comes back.
The third method on the interface is the odd one.
`WindowEventHandler.resizeGui` is never called by `Window` at all: its callers
are `Minecraft` and `Options`, which is to say the game calling itself when
the GUI scale option changes. And `WindowEventHandler.framebufferSizeChanged` is not only a
callback — `Window.updateFullscreenIfChanged` and
`Window.changeFullscreenVideoMode` both raise it directly, which is how F11
and a video-mode switch reach the renderer by the same route a dragged window
corner does.
Notice what is *not* among the six: keys, characters, mouse buttons, cursor
motion and scrolling. Every input callback is registered somewhere else
entirely, by `KeyboardHandler` and `MouseHandler` — see [input and
keybinds](../client/input-and-keybinds.md).
## Three sizes, and every misplaced GUI element is a confusion between them
| the size | how it is asked for | what it is |
|---|---|---|
| framebuffer | `Window.getWidth`, `Window.getHeight` | the pixels the renderer actually targets |
| screen | `Window.getScreenWidth`, `Window.getScreenHeight` | the window as the operating system reports it, which under DPI scaling is not the framebuffer |
| GUI-scaled | `Window.getGuiScaledWidth`, `Window.getGuiScaledHeight` | the framebuffer divided by an integer scale |
The integer scale is the part with a policy in it, and the two methods run
the other way round from their names. `Window.calculateScale` is handed what
the option asked for as a *ceiling* and decides what is actually possible,
counting upward while the framebuffer still divides by the two constants
`Window.BASE_WIDTH` and `Window.BASE_HEIGHT`, then rounding *up* to an even
number when the font needs unicode. `Window.setGuiScale` takes that answer
and stores it, computing the two scaled sizes from it. A high-DPI display is what makes the first two rows
diverge, and a GUI element that lands in the wrong place is nearly always
code that read one of the three and meant another.
## What the window does per frame, which is almost nothing
Two calls, both inside `Minecraft.renderFrame`, both in the *update window*
profiler zone: `Window.updateFullscreenIfChanged` at the very top of it, and
the surface reconfigure-and-acquire immediately after. Everything else the
window does is a callback firing.
`Window.updateFullscreenIfChanged` is where F11 lands.
`Window.toggleFullScreen` and `Window.setWindowed` flip the state,
`Window.isFullscreen` reports it, and `Window.changeFullscreenVideoMode` with
`Window.getPreferredFullscreenVideoMode` and
`Window.setPreferredFullscreenVideoMode` negotiate what exclusive fullscreen
turns into. Dragging the window to the other monitor is the same machinery
approached from the other end: `MonitorManager.findBestMonitor` decides which
monitor a window is on *by overlap*, and `Monitor.getPreferredVidMode` looks
for the saved preference among the modes this monitor actually offers, taking
the monitor's current mode when there is no exact match — it never
approximates. A `Monitor` is a record —
a name, a handle, its list of `VideoMode`s, the current one and its position —
and `Window.getRefreshRate` is the number that comes out of the mode.
The one thing on this page that runs continuously is
`FramerateLimitTracker`, which watches iconification and idle time — not
focus — and overrides the frame limit: ten frames a second for an iconified
or long-idle window, thirty for a short idle, sixty in a menu with no level. [The frame](the-frame.md) is
where that limit gets spent.
## `NativeImage`, the seam between a file and a texture
`NativeImage` is a `NativeImage.Format`, a width, a height and a pointer into
native memory. It is where every image in the game briefly is, and it is not
only textures. `NativeImage.read` is an STB decode from a stream, a byte array
or an NIO buffer — that is the PNG path. `NativeImage.copyFromFont` receives a
rasterised FreeType glyph. An atlas is assembled into one with
`NativeImage.copyRect`, `NativeImage.resizeSubRectTo` and
`NativeImage.fillRect`, and `NativeImage.mappedCopy`,
`NativeImage.getPixel` and `NativeImage.setPixel` are the rest of the
vocabulary. A downloaded skin sits in one while it is being validated, and a
screenshot arrives in one read back off the GPU on its way to
`NativeImage.writeToFile`.
`NativeImage.computeTransparency` is the method that [models and
atlases](models-and-atlases.md) leans on to decide which chunk layer a quad
belongs to — a rendering decision made by looking at the pixels of a file.
Because the memory is native, ownership is explicit: `NativeImage.close`
frees it, and `NativeImage.untrack` exists for the cases where something else
has taken the pointer over.
## Questions players ask
**Why does the game keep drawing while it is minimised?** Because nothing
stops it. `Window.isMinimized` suppresses the surface acquisition and the
frame then runs to completion regardless — see [the frame](the-frame.md). The
work that is actually saved is saved by `FramerateLimitTracker` dropping the
limit to ten, not by the frame being skipped.
**Why does a graphics crash report name the thing the game was doing?**
Because the error callback is swapped three times over the game's life: a
boot-crash handler while starting, `Window.setDefaultErrorCallback` once
running, and a null on close. `Window.setErrorSection` tags whatever GLFW
complains about with what the game was busy with when it complained, so a
driver's error message arrives attached to a phase rather than floating free.
**Why does the mouse cursor stop changing shape sometimes?** Because you
turned it off, or never turned it on. `Window.setAllowCursorChanges` is
driven by a player option on the mouse-settings screen and nothing else, and
with it clear every request is answered with the default arrow. The other
half of the problem is the platform's: `CursorType.createStandardCursor`
takes a fallback for the shapes a given system does not provide.
**Why does the game sometimes leave a crash report behind after I close
it?** Because a shutdown that hangs is reported from outside.
`ClientShutdownWatchdog` starts a daemon thread that sleeps fifteen seconds
and, if the shutdown has not claimed the counter by then, builds the crash
report itself from the main thread's stack. It is armed twice with different
powers: the window-close callback arms it to *report only*, while the one
`Minecraft.run` has already returned from arms it to report and then take the
process down.
**What is the rest of the package?** The corners the story above does not pass
through: `ClipboardManager` and `TextInputManager` for copy, paste and IME
text (both reached from `KeyboardHandler`), `CursorType` and `CursorTypes` for
the cursor shapes, `IconSet` for what `Window.setIcon` picks between,
`MacosUtil`, `DebugMemoryUntracker`, the rest of `GLX` (`GLX._getCpuInfo`,
`GLX._getLWJGLVersion`, `GLX.getGlfwPlatform`) — and `InputConstants`, the key
and mouse-button vocabulary that every `KeyMapping` is written in.
> **For a 1.21-era reader.** The headline is that the window no longer
> presents anything: *Window.updateDisplay* and *Window.setVsync* are gone,
> presentation is [blaze3d](blaze3d.md)'s `GpuSurface` protocol and vsync is a
> `GpuSurface.PresentMode`. Also gone: *Window.setupGuiState*, and
> *ScreenManager*, which never existed here — monitor handling has always been
> `MonitorManager`. And the constructor now takes a `GpuBackend`, because the
> window cannot be made without knowing which API is going to draw into it.
## Where to look
`Minecraft`'s constructor for the candidate loop and what happens when it runs
out of candidates. `Window`'s constructor for the order in which a window and
a backend come into being, then `Window.updateFullscreenIfChanged` for the
only thing the window does per frame. `MonitorManager.findBestMonitor` and
`Monitor.getPreferredVidMode` for the fullscreen negotiation. `NativeImage.read`
and `NativeImage.computeTransparency` for the image type the rest of Part XI
is built on.
---
*Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes `tools/verify_names.py`.*
---
# Blaze3D
> Verified against **Minecraft 26.2** · Part XI · one draw call, from a declared pipeline to a triangle — and the two backends that can serve it.
Open Video Settings, change **Graphics API**, restart, and the game comes back
looking exactly as it did. Nothing in the *net.minecraft* packages noticed,
because nothing in them talks to a driver: they talk to `GpuDevice`,
`CommandEncoder` and `RenderPass`, and a backend under
`com/mojang/blaze3d/opengl` or `com/mojang/blaze3d/vulkan` turns that into
real calls. What made the swap possible is that the state machine left the
game. Blend mode, depth test, cull and polygon mode are *fields of a
`RenderPipeline`*, declared once and applied when the pipeline is bound, and
`RenderSystem` — the class that used to be the state machine — contains no GL
call at all. It did not vanish, though. It moved behind the backend boundary,
where `GlStateManager` still shadows every toggle and still elides the
redundant ones.
This page is the vocabulary of that boundary; the window the device is created
against is [the window](the-window.md), the frame [the frame](the-frame.md).
## The cast
| class | what it decides | thread |
|---|---|---|
| `RenderSystem` | the static holder: the device, the render thread, the frame's shared uniforms and buffers | Render thread |
| `GpuBackend` | which API is alive — window hints, creation errors, and the device itself | Render thread |
| `GpuDevice` | what exists, and what the hardware will allow | Render thread |
| `CommandEncoder` | whether a pass may open, and with which attachments | Render thread |
| `RenderPass` | that the pipeline matches the attachments before a draw is allowed | Render thread |
| `GpuSurface` | how a finished frame reaches the screen, and what vsync means | Render thread |
| `RenderPipeline` | how to rasterise: shaders, blend, depth, cull, topology — declared, never called | declaration only, read on the Render thread |
| `BufferBuilder` | vertex data — on the render thread, except the one instance chunk meshing runs | Render thread, and the meshing workers |
## Four objects the game only touches through a façade
Four concrete, validating classes sit in `blaze3d/systems`, each over one thin
per-backend interface: the game holds the left column below, never the right.
```mermaid
flowchart TB
GB["GpuBackend — GLFW window hints, window-creation errors, and it creates the device. The one the game names and never wraps"]
subgraph F["what the game holds: the facade in blaze3d/systems, concrete and validating"]
GD["GpuDevice"]
CE["CommandEncoder"]
RP["RenderPass"]
GS["GpuSurface"]
end
subgraph I["one thin interface behind each"]
GDB["GpuDeviceBackend"]
CEB["CommandEncoderBackend"]
RPB["RenderPassBackend"]
GSB["GpuSurfaceBackend"]
end
GB -- "creates the device, and the device creates the rest" --> GD
GD --> GDB
CE --> CEB
RP --> RPB
GS --> GSB
I --> OGL["com/mojang/blaze3d/opengl — GlStateManager shadows every toggle"]
I --> VK["com/mojang/blaze3d/vulkan — swapchain, SPIR-V, reflection"]
```
| the game holds | the backend implements |
|---|---|
| `GpuDevice` | `GpuDeviceBackend` |
| `CommandEncoder` | `CommandEncoderBackend` |
| `RenderPass` | `RenderPassBackend` |
| `GpuSurface` | `GpuSurfaceBackend` |
`GpuBackend` is the entry point and the exception: no façade, because it is
what exists before a device does. Everything else the device creates, and it
answers for the hardware too — as one record graph, not a pile of getters.
`GpuDevice.getDeviceInfo` returns a `DeviceInfo` of `DeviceInfo.name`,
`DeviceInfo.backendName`, `DeviceInfo.isZZeroToOne`, a `DeviceFeatures` of
seven booleans, a `HintsAndWorkarounds`, a `DeviceType` and a `DeviceLimits`
whose `DeviceLimits.maxMemoryAllocationSize` caps the window size.
### Who checks what
The façade owns the **API-contract** checks and the backend owns the
resource-state ones. `CommandEncoder.createRenderPass` validates the attachment
count against `DeviceLimits.maxColorAttachments`, each attachment's
`GpuTexture.USAGE_RENDER_ATTACHMENT` bit, that the attachments are all one size,
that a render area was supplied and fits, and that no pass is already open.
`RenderPass.setPipeline` checks the pipeline's `ColorTargetState` list against
the pass's attachments in both count and `GpuFormat`. The `GpuBufferSlice`
overload of `RenderPass.setUniform` checks the offset against
`DeviceLimits.minUniformOffsetAlignment` — the plain `GpuBuffer` overload checks
nothing.
Underneath, the backend throws on its own account: `GlBuffer` for a buffer
mapped without persistent-mapping support, unreadably, unwritably or over two
gigabytes, and `GlDevice` `GpuOutOfMemoryException` on a failed allocation.
And it is the backends, not the façade, that keep the development-only
checks: the only validation in this whole tree gated on running from an IDE
is in `GlRenderPass` and `VulkanRenderPass`. Everything the façade asserts,
it asserts in a shipped game.
The thread assertions are not where a reader expects them either.
`RenderSystem.assertOnRenderThread` is called from eleven classes, eight of
those sites inside `RenderSystem` itself: it guards `RenderSystem`'s own
mutable statics and the GL- and GLFW-facing classes, while `GpuDevice`,
`CommandEncoder` and `RenderPass` assert nothing at all. And
`GpuDevice.createCommandEncoder` does not create an encoder — it allocates a
fresh façade over the one long-lived encoder the backend owns, so the *is a
pass open* guard is per-façade and the game calls it fresh at every use site.
### How tight the boundary is
**OpenGL is imported by exactly fourteen files** — thirteen in
`com/mojang/blaze3d/opengl`, the fourteenth the native-library bootstrap — and
nothing else in the game references LWJGL's OpenGL bindings. Vulkan
leaks upward in two places, not one: `RenderPass` imports two Vulkan
indirect-command structs to use their size when validating an indirect buffer,
and the loader probe imports Vulkan too, while `BackendCreationException` in
`blaze3d/systems` carries seven Vulkan-named failure reasons. The two exemptions
are one, granted twice. Graphics is not all of it either, and neither is the
render thread: `com/mojang/blaze3d/audio` is the OpenAL wrapper and runs on
the sound engine's own thread — see [the sound
engine](../client/sound-engine.md).
## Vulkan is not a stub
**7,477 lines against 5,627** — the Vulkan backend against the OpenGL one,
forty classes against twenty-eight.
It is the larger of the two trees: a real swapchain, the same GLSL compiled to
SPIR-V and reflected to build bind-group layouts, five required device
extensions, nine required features, and vendor-specific GPU crash breadcrumbs in
*vulkan/checkpoints* that OpenGL has no answer to.
`VulkanBackend.checkBackendAvailable` says why it is unavailable, though only
the default preference consults it.
The backends differ in **all seven** `DeviceFeatures` flags, and only one
difference is symmetric: Vulkan hardcodes five flags true that OpenGL derives
from extensions, and the mirrored pair is the two direct multi-draw flavours,
where OpenGL has the separate one and never the interleaved one and Vulkan the
interleaved one only if the driver offers *VK_EXT_multi_draw* — so a Vulkan
device can support neither. Not every draw consults those flags: the six
multi-draw and indirect entry points gate unconditionally, `RenderPass.draw`
and `RenderPass.drawIndexed` only when a non-zero first instance is asked for,
and `RenderPass.drawMultipleIndexed` — the batched chunk path — not at all.
## A pipeline is a record, not a sequence of calls
`RenderPipeline` is declarative and effectively immutable: a
`RenderPipeline.getLocation` identity, two shader `Identifier`s, a
`ShaderDefines`, a list of `BindGroupLayout`, up to eight `ColorTargetState`,
an optional `DepthStencilState`, vertex bindings, a `PolygonMode`, a cull flag
and a `PrimitiveTopology`. `RenderPipeline.Builder` assembles one, and
composition is the static `RenderPipeline.builder` taking
`RenderPipeline.Snippet`s, which `RenderPipeline.Builder.buildSnippet`
produces rather than consumes.
Blending is a named `BlendFunction` (`BlendFunction.TRANSLUCENT`,
`BlendFunction.ADDITIVE`…) rather than a pair of loose factors, and
`RenderPipeline.Builder.build` refuses a pipeline whose colour targets do not
all share one blend function, or that binds more than sixteen vertex
attributes. Depth is reversed-Z throughout: `DepthStencilState.DEFAULT`
compares greater-or-equal and `RenderSystem.DEFAULT_DEPTH_CLEAR_VALUE` is zero.
The catalogue lives on the game side: `RenderPipelines` registers the static
pipelines — `RenderPipelines.GUI`, `RenderPipelines.LIGHTMAP`,
`RenderPipelines.SKY` and dozens more, eighty-seven in all — from a shallow
tree of snippets and the shared uniform-name sets in `BindGroupLayouts`, and
`RenderPipelines.getStaticPipelines` is the list `ShaderManager` walks to
precompile them.
## What a pipeline does not say
A `RenderPipeline` says how to rasterise. It does not say which textures to
bind or which target to draw into — and that is where a 1.21 reader's composed
stack of *RenderStateShard*s went. The answer is *client/renderer/rendertype*:
`RenderType` wraps a `RenderPipeline` with an `OutputTarget`, a
`TextureTransform`, a `LayeringTransform`, an outline variant and the batching
predicates `RenderType.canConsolidateConsecutiveGeometry` and
`RenderType.sortOnUpload`. `RenderTypes` is the static catalogue, `RenderSetup`
builds the entries, and `RenderType.prepare` resolves one into a
`PreparedRenderType` — pipeline, texture bindings, uniform slice — at draw time.
## Buffers, uniforms, and the ring that resets every frame
The resource vocabulary is small: `GpuBuffer` and `GpuBufferSlice` with usage
bits (`GpuBuffer.USAGE_VERTEX`, `GpuBuffer.USAGE_UNIFORM`,
`GpuBuffer.USAGE_MAP_WRITE`…), `GpuTexture` and `GpuTextureView` with theirs,
`GpuSampler`, `GpuFence`, `GpuFormat`, `IndexType`, `PrimitiveTopology`. Two of
those are where old habits break. Sampler state left the texture — `GpuTexture`
has no filter or wrap setters, filtering is an immutable `GpuSampler` bound per
draw, and `SamplerCache` eagerly creates all thirty-two combinations at startup
and throws if either enum ever gains a constant. And there are three shared
index buffers, not one: `RenderSystem.getSequentialBuffer` switches between a
quad buffer, a line buffer with different winding, and a one-to-one buffer.
Per-draw uniform data does not come from per-draw uniform calls; it is carved
out of ring buffers. `DynamicUniforms` and `DynamicUniformStorage` hand out
`GpuBufferSlice`s of a `MappableRingBuffer` reset once a frame, while
`GlobalSettingsUniform` and `ProjectionMatrixBuffer` hold one buffer apiece
and rewrite it in place, which is all a frame-wide value needs. Per-frame
scratch comes from `TransientMemory` — one
interface, two large implementations over the shared `TransientBlockAllocator`.
Blocks are packed by hand with `Std140Builder`, sized by `Std140SizeCalculator`.
Vertex data is described by `VertexFormat` and `VertexFormatElement` (a plain
record of name, offset and `GpuFormat`) with the standard layouts in
`DefaultVertexFormat`, and built with `ByteBufferBuilder` and `BufferBuilder`
into a `MeshData`. Most `BufferBuilder`s are on the render thread like
everything else here; the exception is the one that matters most for
throughput, because chunk meshing runs `BufferBuilder` on worker threads and
stages the result
through `StagedVertexBuffer` and `UberGpuBuffer` into a `StagingBuffer`, which
is why `SectionRenderDispatcher` has a spin-wait guarded by
`RenderSystem.isOnRenderThread`. Render targets are `RenderTarget`,
`TextureTarget` and `MainTarget`, the transient ones allocated through
`GraphicsResourceAllocator` — `CrossFrameResourcePool` implements it — and
declared in the `FrameGraphBuilder` of [visibility and the
frame graph](visibility-and-the-frame-graph.md).
## Shaders, and the reflection that checks them
`ShaderManager` loads shader sources and hands them to a backend through
`ShaderSource`. `ShaderManager` resolves the *moj_import* directives at load
time, before a backend sees anything; the `ShaderDefines` are injected later
and inside each backend, by the same shared `GlslPreprocessor` at the moment
a program is compiled. The Vulkan side goes further than compiling: `GlslCompiler`
runs the GLSL through shaderc to SPIR-V and `IntermediaryShaderModule`
*reflects* the result with spirv-cross, enumerating `SpvUniformBuffer`s and
`SpvSampler`s — which is what lets a declared `BindGroupLayout` be checked
against what the shader declares. It is all data on disk, alongside the chains
in [post-processing](post-processing.md).
## One draw
Every drawing class comes through this one shape: `LevelRenderer`,
`GuiRenderer`, `FeatureRenderDispatcher`, `Lightmap`, `TextureAtlas`.
```mermaid
sequenceDiagram
participant Game as the game's own code
participant GD as GpuDevice
participant CE as CommandEncoder
participant RP as RenderPass
participant GlCE as GlCommandEncoder
participant GpuS as GpuSurface
Game->>GD: createCommandEncoder — a fresh facade over the one real encoder
Game->>CE: createRenderPass with a RenderPassDescriptor
CE->>CE: validate attachments, sizes, usage bits, render area, no pass open
CE->>GlCE: bind an FBO from the cache, viewport, scissor, clear
CE-->>Game: RenderPass, an AutoCloseable
Game->>RP: setPipeline — formats must match the attachments
Game->>RP: bindDefaultUniforms — Projection, Fog, Globals, Lighting
Game->>RP: setVertexBuffer, setIndexBuffer, bindTexture
Game->>RP: drawIndexed
RP->>GlCE: look up or compile the program, apply pipeline state, bind VAO
GlCE->>GlCE: glDrawElementsInstancedBaseVertex
Game->>RP: close — debug groups must balance
RP->>CE: submitRenderPass
Note over GpuS: the surface, at the two ends of the frame
Game->>GpuS: acquireNextTexture at the top of renderFrame, then blitFromTexture of the main target and present at the bottom
```
Everything above the `GlCommandEncoder` lane is validation or declaration.
Below it, one call is not one call: pass setup alone binds a framebuffer, sets
viewport and scissor and clears, and a single `RenderPass.drawIndexed` applies
depth, cull, blend, polygon mode and colour mask, binds a program, walks the
uniform and sampler bindings, binds a vertex array and finally draws. The
point is not that a draw is cheap. It is that *the game* never sees any of it.
The pipeline compiles lazily on its first `RenderPass.setPipeline` and is
cached by identity on the device, but no frame in a running game pays for it:
`ShaderManager` precompiles the static catalogue into that cache on every
resource reload, leaving the lazy path for pipelines outside it. Run the trace
on Vulkan and the game code is unchanged — dynamic rendering replaces the
framebuffer bind, push descriptors the uniform binding, and the swapchain
lives in `VulkanGpuSurface`.
## How a frame reaches the screen
Presentation is a four-step protocol, not a swap: `GpuSurface.configure`, then
`GpuSurface.acquireNextTexture`, then `GpuSurface.blitFromTexture`, then
`GpuSurface.present`. Vsync is not a toggle in that sequence but a
`GpuSurface.PresentMode` in the configuration: OpenGL offers a fixed pair of
modes, Vulkan whatever the driver enumerates, mailbox and relaxed FIFO included.
## Questions players ask
**Why did that draw produce nothing, and say nothing?** Because the deep
validation is a development-environment feature: the *missing uniform*,
*invalid shader program* and buffer-usage checks are gated on the in-IDE flag,
and in a shipped game the same conditions make the draw return without a word.
**Why is the game on OpenGL when I asked for Vulkan?** Because the backend is
chosen in `Minecraft`, not in Blaze3D. `PreferredGraphicsApi.getBackendsToTry`
returns an ordered *pair*, each candidate tried in turn, so every setting has
the other API as its fallback and the default is OpenGL-first. A previous
unclean shutdown downgrades twice: a Vulkan preference to the default, the
default to OpenGL.
**Why does the game care which GPU I have, when it can ask the driver?**
Because the capability record is sniffed as well as queried. `GlHeuristics`
reads the renderer and vendor strings to guess the device type, flags
GL-over-D3D12 — assumed on Windows-on-ARM whatever the string says — and flags
AMD for anisotropy problems, both of which change how the game uploads and
filters. The backend also probes the reported maximum texture size rather than
trusting it, halving a proxy allocation until the driver accepts one.
**What stops the CPU running a hundred frames ahead of the GPU?** A two-deep
submit fence, not the present: `GlCommandEncoder` rotates its transient memory
and a small fence ring on submit, and that is the pacing. Results that must come
*back* use `GpuFence` — a callback registered with
`RenderSystem.queueFencedTask`, run by `RenderSystem.executePendingTasks` once a
frame in the *gpuAsync* zone, stopping at the first fence that has not
signalled. Its one registration site is `GlCommandEncoder`'s texture readback,
and Vulkan routes the same callbacks through its own destruction queue.
> **For a 1.21-era reader.** Nearly every name you would reach for in this
> corner of the codebase has gone. `PoseStack` did *not* move, and is still here.
| you are looking for | it is now |
|---|---|
| *RenderSystem.setShader* and every state toggle on it | fields of a `RenderPipeline` |
| *ShaderInstance* | the pipeline's two shader `Identifier`s, compiled by `ShaderManager` |
| *RenderStateShard* | `RenderType` over a `RenderPipeline` |
| *VertexBuffer*, *Tesselator*, *BufferUploader* | `BufferBuilder` into a `MeshData`, then a `GpuBuffer` |
| *VertexFormat.Mode*, *VertexFormat.IndexType*, *TextureFormat* | `PrimitiveTopology`, a top-level `IndexType`, `GpuFormat` |
| *GpuDevice.getDeviceName* and its siblings | the `DeviceInfo` record |
| *Window.updateDisplay*, *setVsync* | `GpuSurface.present` and a `GpuSurface.PresentMode` |
## Where to look
`RenderSystem` for what the game holds, then `GpuDevice`, `CommandEncoder` and
`RenderPass` for the façade and its checks. `RenderPipeline.Builder` and
`RenderPipelines` for how a draw is declared, `RenderTypes` for how one is
dressed. `GlCommandEncoder` for an OpenGL draw, `VulkanCommandEncoder` for the
other answer, `GpuSurface` for where a frame ends.
---
*Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes `tools/verify_names.py`.*
---
# Visibility and the frame graph
> Verified against **Minecraft 26.2** · Part XI · you fly forward in creative and the world opens out in front of you, one section at a time.
You fly forward in creative and the world arrives. It does not resolve as a
wall of fog lifting all at once — it grows *outward* from where you already
are, section by section, at exactly the rate the mesher can keep up with. That
order is not the download order and it is not the mesher's queue order. It
falls out of an asymmetry inside the walk that decides what is visible at all:
a section that has not been meshed yet is **opaque** to the walk and stops it
dead, a section known to be empty is **transparent** and lets it straight
through, and a section that meshed to *nothing* is neither — it keeps a real
compiled mesh that happens to have no draws in it. Terrain reveals itself
outward because the walk can only reach as far as the meshes that already
exist.
[The frame](the-frame.md) ends where this page begins — but not quite at one
method. The first stage runs on the *extract* side of the wall:
`LevelExtractor.applyFrustum` trims the reached sections to the visible list
before `GameRenderer.render` is called at all. The other four are
`LevelRenderer.render`, which gathers what was submitted, declares the passes
of a frame, draws the terrain, schedules translucency work for a later frame
and re-runs the walk on its way out — in that order, and the order is the
page.
## The cast
| class | what it decides | thread |
|---|---|---|
| `LevelRenderer` | which sections are visible, which passes the frame declares, and how terrain is finally drawn | Render thread |
| `SectionOcclusionGraph` | which sections the walk can reach from the camera at all | full walk on `Util.backgroundExecutor`, partial walk here |
| `SectionOcclusionGraph.GraphState` | the published result of a walk — swapped whole on a rebuild, extended in place by a partial walk | rebuilt by a worker, extended and read here |
| `LevelExtractor` | whether the frustum is re-applied this frame, and so whether the visible list is rebuilt or reused | Render thread |
| `Frustum` | which of the reached sections survive into `LevelRenderer.visibleSections` | Render thread |
| `FrameGraphBuilder` | which passes exist, what each reads and writes, and what order they execute in | Render thread |
| `LevelTargetBundle` | the named render targets the passes hand between them | Render thread |
| `ChunkSectionsToRender` | one bucket per buffer set, so sections that share buffers share a binding | Render thread |
Only one of those reads the world, and it is the one on the extract side:
`LevelExtractor` holds the `ClientLevel` and is the class that carries it
across the wall. `LevelRenderer` does not — its single `ClientLevel`
reference is a *parameter*, to `LevelRenderer.invalidateCompiledGeometry`,
which is the reload path and not the frame path. Everything the drawing half
knows about the world came across in the snapshot; what the renderer still
owns is geometry, targets and order.
## Five stages, and the first one decides the other four
```mermaid
flowchart TD
S1["1. what is visible, in extract — SectionOcclusionGraph reaches sections outward from the camera, then LevelExtractor.applyFrustum keeps the ones the Frustum admits and caches them as LevelRenderer.visibleSections"]
S2["2. what is submitted — LevelRenderer.submitFeatures gathers entities and block entities, and FeatureRenderDispatcher.prepareFrame groups them, all before a pass exists"]
S3["3. what passes exist — FrameGraphBuilder.addPass declares each pass with its reads and writes, then FrameGraphBuilder.execute orders and runs them"]
S4["4. how terrain is drawn — LevelRenderer.prepareChunkRenders buckets the visible sections and ChunkSectionsToRender multi-draws each bucket, inside the main pass"]
S5["5. what is re-sorted — a rolling budget of translucent sections is scheduled, for a mesh that arrives a frame or more later"]
S6["SectionOcclusionGraph.update — the walk is re-run at the end of render, so the next frame reads a newer graph"]
S1 --> S2 --> S3 --> S4 --> S5 --> S6
S6 -. "next frame" .-> S1
```
Read it as **reach, gather, declare, draw, defer**, and note that stage one
has already happened when `LevelRenderer.render` is entered — it is the last
thing *extract* does. Stages one and five are bookkeeping that decides what
the drawing stages will have to do, and both are budgeted rather than
complete.
## The walk that decides what exists, and the frustum that only trims it
Visibility here is *reachability*, not a frustum test. `SectionOcclusionGraph`
starts at the camera's own section and walks outward one neighbour at a time,
and — with smart cull on, which is the default — it may step from a section
into a neighbour only if the two faces involved can see each other through
that section's geometry. That per-section answer is
a `VisibilitySet`, computed by `VisGraph` when the section was meshed — so the
question *can you see through this section* is decided once at compile time
and then read for free thousands of times a frame. A wall of stone does not
hide the world behind it because a frustum test rejected it. It hides it
because the walk cannot get past.
The reached sections live in an `Octree` inside
`SectionOcclusionGraph.GraphState`, alongside the queue of sections whose
neighbours still need visiting. A *rebuilt* state is never edited into the
old one: the whole of it is published through an `AtomicReference`, because
`SectionOcclusionGraph.scheduleFullUpdate` runs the complete rebuild on
`Util.backgroundExecutor` and the client thread has to keep reading the old
graph until the new one is ready. Everything else happens on the client
thread, including `SectionOcclusionGraph.runPartialUpdate`, which does edit
the published state in place — it walks outward again from the sections that
`SectionOcclusionGraph.schedulePropagationFrom` flagged, typically because a
new mesh landed for them and their neighbours are worth trying again — which
is a real walk, not a drain, and it is where the outward reveal actually
advances.
`SectionOcclusionGraph.update`, at the very end of `LevelRenderer.render`, is
what runs both.
The frustum arrives after all of this, and only trims.
`SectionOcclusionGraph.addSectionsInFrustum` visits the octree, keeps what a
`Frustum` admits, and fills `LevelRenderer.visibleSections` plus the small
short-radius subset `LevelRenderer.nearbyVisibleSections`. Looking a section
up by position goes through `LevelRenderer.viewArea`.
**Beyond sixty blocks the walk gets harder** — and *sixty blocks* and *three
sections* are one number written twice.
`SectionOcclusionGraph.MINIMUM_ADVANCED_CULLING_DISTANCE` is sixty, and
`SectionOcclusionGraph.MINIMUM_ADVANCED_CULLING_SECTION_DISTANCE` is that
same distance converted to section coordinates, which comes out at three; the
test that uses it compares section coordinates on each axis separately. Out
past it, smart cull adds a ray march *back toward the camera* from the
neighbour being considered, and rejects that neighbour if any section along
the line has not itself been reached by this walk. Inside it, nothing marches
— nearby geometry is cheap enough not to argue about.
### Which is why the reveal is outward
The three states a section can be in are the whole trick. An **uncompiled**
section is opaque: the walk stops there and everything behind it stays
unreached. An **empty** section is transparent: the walk passes through
without needing a mesh at all. A section that **compiled to nothing** is
neither — it holds a real compiled mesh with no draws in it and answers from
its own `VisibilitySet` like any other section. So as meshes land, the
frontier of the walk moves outward one shell at a time, and each newly meshed
section re-arms its neighbours through
`SectionOcclusionGraph.schedulePropagationFrom`.
Streaming is a separate handshake laid over the same walk. A section whose
chunk has not arrived yet is treated as neither opaque nor transparent — it is
*parked*, filed against the chunk it is waiting for, and resumed when
`ClientChunkCache` reports that chunk loaded. The walk then continues from
where it stopped rather than starting over.
The gate this stage controls is not only what gets drawn. **Only visible
sections are re-meshed**, so a block you place behind you costs nothing until
the walk reaches that section again; how a section becomes triangles once it
has been chosen — the dirty flags, the snapshot a worker reads, the compiler,
the three chunk layers and the buffer arenas they upload into — is [section
meshing](section-meshing.md).
### The visible list is a cache, not a per-frame computation
`LevelExtractor.applyFrustum` does not run every frame, and **two different
clocks** decide when it does. They are easy to run together and they are not
the same thing.
The **walk** is thrown away and redone when the camera crosses an eight-block
cell on any axis, when the field of view changes, or when the smart-cull
toggle changes. That is `SectionOcclusionGraph.invalidateIfNeeded`, and what
it schedules is the full off-thread rebuild.
The **frustum step** asks a different question — the walk's result may still
be good while the set of it you can see is not — and it re-runs when either
of two things happens: the graph raises its own frustum-update flag, which a
completed full walk always does and a partial walk does whenever it added a
section inside the offset frustum; or the camera's pitch or yaw crosses a
two-degree step. Turning your head therefore re-applies the frustum without
disturbing the walk at all, and between these events
`LevelRenderer.visibleSections` is simply the list from last time. Stand
still and stare, and the frame's terrain cost does not move.
## Everything is submitted before there is anywhere to put it
Entities and block entities do not draw themselves inside a pass. They are
gathered *first*, before a single pass has been declared.
`LevelRenderer.submitFeatures` runs `LevelRenderer.submitEntities` and
`LevelRenderer.submitBlockEntities` into `LevelRenderer.submitNodeStorage`,
and `FeatureRenderDispatcher.prepareFrame` groups everything submitted into a
`FeatureRenderDispatcher.PreparedFrame`. The passes declared in the next stage
capture that prepared frame and call into it; they never walk an entity list
themselves. What a submission contains, and how an entity produces one, is
[entity rendering](entity-rendering.md).
The ordering matters for a reason that only shows up in the next stage: the
frame graph needs to know, *while it is being declared*, whether anything in
this frame wants an outline. It can know that because the submission has
already happened.
## Declaring the passes, and why none of them is ever culled
`LevelRenderer.render` builds a graph and then executes it. Building it means
declaring resources and passes. `FrameGraphBuilder.importExternal` brings in
the targets that already exist outside the frame — the main render target and
the entity-outline target — and `FrameGraphBuilder.createInternal` declares
five that exist only for the duration of this frame. Each pass comes from
`FrameGraphBuilder.addPass`, states its dependencies with `FramePass.reads`
and `FramePass.readsAndWrites`, and states its body with `FramePass.executes`.
`LevelTargetBundle` is where the handles live under names —
`LevelTargetBundle.main`, `.translucent`, `.itemEntity`, `.particles`,
`.weather`, `.clouds`, `.entityOutline` — and `LevelRenderer.targets` is the
bundle the frame threads through every declaration.
```mermaid
flowchart TD
CLEAR["clear — wipes colour and depth on the main target"]
SKY["sky — LevelRenderer.addSkyPass"]
MAIN["main — LevelRenderer.addMainPass"]
OUT["the entity outline post chain — added only when something submitted an outline"]
CLOUDS["clouds — LevelRenderer.addCloudsPass, added only in a frame that has clouds"]
WEATHER["weather — LevelRenderer.addWeatherPass, which also draws the world border"]
TRANS["the transparency post chain"]
TOP["always on top — LevelRenderer.addAlwaysOnTopPass, which clears depth first"]
CLEAR --> SKY --> MAIN --> OUT --> CLOUDS --> WEATHER --> TRANS --> TOP
subgraph INSIDE ["inside the main pass, in order"]
direction TB
T1["opaque terrain — the OPAQUE draw group"]
T2["FeatureRenderDispatcher.PreparedFrame.executeSolid"]
T3["depth copied out of the main target into the translucent, item entity and particle targets"]
T4["PreparedFrame.executeTranslucent, then PreparedFrame.executeOutline"]
T5["translucent terrain — the TRANSLUCENT draw group"]
T6["PreparedFrame.executeTranslucentAfterTerrain"]
T1 --> T2 --> T3 --> T4 --> T5 --> T6
end
MAIN -.-> INSIDE
```
The two post chains in that figure are declared here and explained in
[post-processing](post-processing.md). The outline chain is why the
entity-outline target is imported at all, and the transparency chain is the
only reason the five internal targets are ever created.
**The graph culls passes, and culls none of these.**
`FrameGraphBuilder.execute` keeps only the passes that transitively feed an
imported external resource and drops the rest before it orders anything. Note
the plural: it seeds from *every* imported resource, not from the main target
alone, which is what saves the entity-outline chain — none of its four passes
ever writes to main, and all four survive because the glow target is imported
too. With that seeding, no pass `LevelRenderer` declares is ever dropped in a
stock game. The clouds pass is absent from a clouds-off frame for a
different and much cheaper reason: `LevelRenderer.render` never *adds* it. The
declaration is the branch; the culling machinery is insurance against a
declaration that has become pointless, not the mechanism the game uses to turn
features off.
## One bucket per buffer set, and what bucketing actually buys
`LevelRenderer.prepareChunkRenders` runs before the main pass is declared, and
its output is what that pass will execute. It walks
`LevelRenderer.visibleSections` and, for each layer a section has geometry in,
computes a hash of the buffers that geometry lives in and files the draw under
that hash. Sections sharing buffers land in the same bucket, and
`ChunkSectionsToRender` then issues one `RenderPass.drawMultipleIndexed` per
bucket, with each section's transform arriving as a slice of a uniform buffer
rather than as a per-draw state change. **The saving is not the draw calls.**
Both backends still loop and issue one GPU draw per section — what the bucket
removes is the buffer rebinding and the per-draw state change between them,
which is the expensive part on this side of the driver. The two groups the
main pass renders are
`ChunkSectionLayerGroup.OPAQUE` and `ChunkSectionLayerGroup.TRANSLUCENT`; the
layers underneath them belong to [section meshing](section-meshing.md).
The translucent layer inverts the rule, and it inverts it in the direction
nobody guesses. Its grouping hash omits the buffer contribution entirely, so
the hash never changes — **every** translucent section files into one bucket
instead of being spread across several. That is exactly what preserves the
visit order inside it, and the visit order is what the draw list is then
reversed against, so the far sections blend before the near ones. Correct
blending is bought here by refusing to *distinguish* the buffers, not by
refusing to share them.
**Directional shading is per dimension, and it is not data.** How bright a
face is by direction comes from a `CardinalLighting` record, and there are
exactly two of them: `CardinalLighting.DEFAULT` and `CardinalLighting.NETHER`,
both hard-coded. `DimensionType` carries the choice between them and nothing
else — a datapack picks, it does not supply numbers.
One ordering here catches everyone out. **Terrain is drawn before the sections
queued this frame are compiled**: `LevelRenderer.compileSections` runs *after*
`FrameGraphBuilder.execute`, so even the option that forces a synchronous
rebuild only guarantees the mesh exists by the end of frame *N* — it appears
in frame *N+1*. Again, [section meshing](section-meshing.md).
## Translucency, re-sorted on a budget it never finishes
Translucent quads inside a section have to be sorted back to front from where
you are standing, and where you are standing changes constantly. Re-sorting
every visible section every frame is not affordable, so the client re-sorts a
slice of them each frame and lets the rest be slightly stale.
Two groups are considered. Everything in
`LevelRenderer.nearbyVisibleSections` — the short-radius set the frustum step
filled alongside the main list — and then a round-robin slice of
`LevelRenderer.visibleSections`, an eighth of it or fifteen sections,
whichever is larger, walked from
`LevelRenderer.translucencyResortIterationIndex` so that successive frames
continue where the last one stopped.
Being considered is not being re-sorted. A section is scheduled if its
`TranslucencyPointOfView` actually changed, **or** if the camera's block
position moved since `LevelRenderer.lastTranslucentSortBlockPos` and the
section is either axis-aligned from the camera or one of the nearby ones. It
is then skipped anyway if a re-sort is already scheduled for it, or if it has
no translucent geometry at all. So standing still costs nothing, walking costs
a bounded amount, and a fast enough sideways move can leave a distant pane of
glass sorted for a viewpoint you have already left.
> **For a 1.21-era reader.** *LevelRenderer.renderLevel* does not exist — the
> method is `LevelRenderer.render`, and it is handed render state rather than
> a level. *LevelRenderer.renderChunkLayer* is gone, because a layer is no
> longer drawn by a method looping over chunks: it is a bucketed multi-draw
> built by `LevelRenderer.prepareChunkRenders` and issued by
> `ChunkSectionsToRender`. *LevelRenderer.setupRender* is gone too, its work
> split between `SectionOcclusionGraph` and `LevelExtractor.applyFrustum`. And
> every dirty method that used to hang off `LevelRenderer` moved to
> `LevelExtractor`, the world-facing half of the old class. Four names survive
> unchanged and mean what they always did: `ViewArea`, `VisGraph`, `Octree`
> and `Frustum`.
## Where to look
`LevelRenderer.render` — the stages of this page are that one method, top to
bottom. `SectionOcclusionGraph.update` for the walk, and
`SectionOcclusionGraph.runPartialUpdate` for the only part of it on the client
thread. `LevelExtractor.applyFrustum` for why the visible list is usually a
cache. `FrameGraphBuilder.execute` for how declared passes are ordered and
which are dropped. `LevelRenderer.prepareChunkRenders` and
`ChunkSectionsToRender` for how terrain finally reaches the GPU, with
[blaze3d](blaze3d.md) underneath it. The models and the atlas the terrain is
textured from are [models and atlases](models-and-atlases.md); the block
changes that make sections dirty arrive as the packets in [what the client is
told](../networking/what-the-client-is-told.md) and become dirty sections in
[the client level](../client/the-client-level.md).
---
*Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes `tools/verify_names.py`.*
---
# Section meshing
> Verified against **Minecraft 26.2** · Part XI · a block is placed, and the section it lives in is re-meshed, uploaded and drawn.
You right-click a block into place and it is simply *there* — no shimmer, no
gap, no frame in which the wall you just built has a hole in it. Behind that
instant there is a worker thread rebuilding a cube of world sixteen blocks on
a side from scratch, a snapshot of twenty-seven sections taken so that it may,
a scratch buffer it had to queue for, and a swap that waits until every last
byte has landed on the GPU. All of which is why the *other* half of the
story is the surprising one: place a block behind you and none of it happens
at all. Only sections the frame is already drawing are swept for dirtiness,
so the flag on a section at your back is set and then simply waits — for a
second, for an hour, for as long as you keep your back turned.
## The cast
| class | what it decides | thread |
|---|---|---|
| `LevelExtractor` | which block changes dirty which sections, and which dirty sections are worth compiling this frame | Render thread |
| `SectionUpdateTracker` | where dirtiness lives, and whether a never-compiled section is allowed to compile yet | Render thread |
| `RotatingSectionStorage` | which slot in a fixed ring a section's flag belongs to, and when that slot is re-homed | Render thread |
| `RenderRegionCache` | the snapshot a mesher is allowed to read, and how much of it is shared | Render thread |
| `SectionRenderDispatcher` | queue order, scratch buffers, the GPU arenas, and the moment a new mesh becomes the drawn one | Render thread, workers |
| `SectionTaskDynamicQueue` | which section a free worker takes next | any |
| `SectionCompiler` | the block-by-block walk that turns a snapshot into vertices | `Util.backgroundExecutor` |
| `BlockModelLighter` | smooth lighting and ambient occlusion, per quad, as the walk goes | `Util.backgroundExecutor` |
## The whole trip, in one figure
```mermaid
sequenceDiagram
participant MPGM as MultiPlayerGameMode
participant CL as ClientLevel
participant LX as LevelExtractor
participant SUT as SectionUpdateTracker
participant LR as LevelRenderer
participant SRD as SectionRenderDispatcher
participant Worker as Worker
participant SectC as SectionCompiler
MPGM->>CL: useItemOn under a prediction, then BlockItem.placeBlock calls setBlock
CL->>LX: setBlockDirty, but only if ModelManager.requiresRender
CL->>LX: blockChanged, player-changed or not, read off the update flags
LX->>SUT: dirty over a 3x3x3 block halo, one section or up to eight on a boundary
Note over LX,SUT: the same frame's extract pass, which runs after the tick that handled the click
LX->>LR: walk the visible sections, and only those
LX->>LX: RenderRegionCache builds a 27-section snapshot
LX->>SUT: the flag is cleared as the work is taken
Note over LR,SRD: same frame, after this frame's terrain has already been drawn
LR->>SRD: compileAsync, or compileSync under PrioritizeChunkUpdates
SRD->>Worker: taken nearest-first, and only if a buffer pack is free
Worker->>SectC: compile every block in the section into at most three layers
SectC-->>Worker: layers, block entities, visibility, sort state
Worker->>SRD: append to the staging buffer, spin-waiting if it is full
Note over LR,SRD: the end of a later frame
LR->>SRD: uploadTerrainBuffersToGpu, whose callback swaps the mesh in
```
Read it in three beats: a change makes a flag, a frame turns some flags into
work, and a much later frame publishes the result. The middle beat is the one
that leaves the client thread, and it does not always — a synchronous rebuild
compiles inline where it stands, and an empty mesh is published by the worker
that found it empty.
## A click, and the flag it leaves behind
The first arrow is not what it looks like. `MultiPlayerGameMode` owns the
prediction sequence — the client places the block itself and remembers what
it assumed — but the `Level.setBlock` that actually changes the world happens
down inside `BlockItem.placeBlock`. From the renderer's side it makes no
difference whether the change came from your own hand or from the server;
[what the client is told](../networking/what-the-client-is-told.md) is the
other door into the same call, by way of `ClientLevel.sendBlockUpdated`.
Dirtiness is a small API on `LevelExtractor`, and the calls differ mostly in
how much of the world they condemn.
| call | what it marks |
|---|---|
| `LevelExtractor.blockChanged` | one changed block, with the player-changed bit read off the update flags |
| `LevelExtractor.setBlockDirty` | one changed block, and only if `ModelManager.requiresRender` says a model cares |
| `LevelExtractor.setBlocksDirty` | a box of block positions |
| `LevelExtractor.setSectionDirty` | one section, by section coordinate |
| `LevelExtractor.setSectionDirtyWithNeighbors` | that section and the ones touching it |
| `LevelExtractor.setSectionRangeDirty` | a range of sections |
| `LevelExtractor.allChanged` | everything at once |
**27** — the size of the halo a single block change marks, in *block
positions*, not sections. This is the number to keep straight. A 3×3×3
neighbourhood of blocks maps to exactly **one** section for any block that is
not on a section boundary, and to at most eight when it is — a corner block
touching seven neighbours plus its own. Only the mesher's *read* region,
much later on, is genuinely twenty-seven sections. There *is* a gate on one
of the two doors — `ModelManager.requiresRender` guards
`LevelExtractor.setBlockDirty`, so a state change no model reacts to marks
nothing through that route — but it is not the route a placed block takes.
`Level.setBlock` goes through both, and the second,
`LevelExtractor.blockChanged`, marks the halo whatever the models say.
### The flag belongs to a slot, not to a section
`SectionUpdateTracker` holds a `SectionUpdateTracker.SectionDirtyState` per
section — with `SectionUpdateTracker.SectionDirtyState.isDirty` and
`SectionUpdateTracker.SectionDirtyState.isDirtyFromPlayer`, the second being
what the *prioritise chunk updates* setting keys off further down this page —
and the tracker is built on `RotatingSectionStorage`, a fixed ring of slots that
`RotatingSectionStorage.repositionCenter` re-homes as the camera moves. A
dirty flag therefore belongs to a *slot*, not to a section: walk far enough
away and the slot is re-homed and the flag is gone. Nothing is lost by that,
because a newly homed slot starts dirty.
## The sweep that only looks at what you can see
Once a frame, the extract pass walks the sections the renderer considers
visible and collects the dirty ones. That is the whole mechanism behind the
hook: a section that is not in the visible set is never even asked whether it
is dirty, so its flag sits there, indefinitely, and the section rebuilds the
instant it comes back into view. Which sections count as visible, and the
reachability walk that decides it, belong to [visibility and the frame
graph](visibility-and-the-frame-graph.md).
One extra gate applies, and only to sections that have never been compiled
before. `SectionUpdateTracker.hasAllNeighbors` requires the eight surrounding
chunk columns — horizontally only, never the section's own column — to be
loaded and lit before a first compile is allowed. A mesher decides whether a
block's face is worth drawing by looking at the block on the other side of it,
so a section built without its neighbours would be a section built against
nothing. A *re*compile skips that check entirely: a section that has a mesh
already may always build a new one.
## What a mesher is allowed to read
A compile runs for an unbounded time on a worker while the client thread
keeps applying block updates, so it cannot be allowed near the live world.
`RenderRegionCache` builds it a `RenderSectionRegion` instead: a 3×3×3 grid
of `SectionCopy`, each holding a genuine *copy* of one section's
`PalettedContainer` together with an immutable map of that chunk's block
entities. Inside the compile, a block state read is a read of that copy, and
it will answer the same way from the first quad to the last however much the
world has moved on.
The cache is what stops this being ruinous. Twenty-seven copies per dirty
section would mean 27*n* copies for *n* sections, and neighbouring dirty
sections share almost all of their neighbourhood — so the regions built in
one extract share their `SectionCopy` instances, and the real cost is far
closer to *n*.
What is *not* copied is as informative as what is. Tints and light are read
live, through the region's references to `ClientLevel` and the light engine —
so for those two the mesher is looking at the world as it is when it asks, not
at the world as it was when the snapshot was taken.
## The queue, the packs, and why more cores do not always help
`SectionRenderDispatcher` takes the sections the extract collected and either
queues them (`SectionRenderDispatcher.RenderSection.compileAsync`) or, under
the *prioritise chunk updates* setting, compiles them on the spot
(`SectionRenderDispatcher.RenderSection.compileSync`). `SectionTaskDynamicQueue`
decides the order and it is nearest-first, with one guard: a recompile only
beats a first-time compile while a small quota lasts, and only if it is also
nearer. With no first-time compile queued at all, a recompile wins outright.
So terrain you have never seen is never starved by a stream of rebuilds to
terrain you have.
The throttle is not the thread count. Each task-runner must acquire a
`SectionBufferBuilderPack` — the scratch vertex buffers a compile writes into
— from `SectionBufferBuilderPool` before it can do anything, and the pool is
sized to the processor count *or* to a share of the heap, whichever is
smaller, degrading further if it hits an out-of-memory error while
allocating. A worker that cannot get a pack puts its task back on the queue
and gives up its turn.
That requeue is a null check, and it is worth knowing how wide it is: the
catch that implements it covers the whole compile, so *any* null-pointer
failure inside the mesher quietly requeues the section rather than reporting
it. A section that fails this way forever will re-mesh forever, silently.
The ceiling on how many meshes exist at once is therefore the pool **plus
one**: the synchronous path uses `RenderBuffers.fixedBufferPack`, which is not
in the pool at all. And because the pool is usually larger than the
background pool, the constraint you actually hit is normally the thread
count after all.
## What the compiler makes
`SectionCompiler.compile` walks every block in the section, asks the models
for its quads and sorts them into layers. Its product is
`SectionCompiler.Results`, four things at once:
`SectionCompiler.Results.renderedLayers` (the geometry, per layer), the
`SectionCompiler.Results.blockEntities` it found on the way, a
`SectionCompiler.Results.visibilitySet` for the reachability walk on the
other page, and `SectionCompiler.Results.transparencyState`, the sort state
for the translucent layer. Those become the section's `CompiledSectionMesh`.
Lighting is not a separate stage: `BlockModelLighter` computes smooth
lighting and ambient occlusion as the walk goes, with a thread-local cache
bracketed around each compile.
**Three** — the chunk section layers, and there are only three:
`ChunkSectionLayer.SOLID`, `ChunkSectionLayer.CUTOUT` and
`ChunkSectionLayer.TRANSLUCENT`. A quad's layer is normally decided at bake
time and simply carried into the compile — see [models and
atlases](models-and-atlases.md) for the `BlockStateModelSet` the compiler
reads and the reload that invalidates every mesh in the world. But the mesher
can overrule it in two places. Every leaf quad is redirected to
`ChunkSectionLayer.SOLID` when the *cutout leaves* option is off, so that
setting is baked into the mesh rather than applied at draw time. And fluids
never consult a baked quad at all: their layer comes from the `FluidModel`,
through `FluidRenderer`.
## Onto the GPU, and a swap that is late on purpose
The worker does not touch the GPU. It appends its vertices to a
`StagingBuffer` under a lock, spinning if the buffer is full — a real
back-pressure point, where a worker waits on the client thread rather than the
buffer growing to fit it. The client thread drains it in
`SectionRenderDispatcher.uploadTerrainBuffersToGpu`, and each completed
upload fires the callback that publishes the new mesh. The result of a
compile therefore arrives back on the client thread, always, with exactly one
exception: a section that compiled to nothing at all is published directly on
the worker, because there is nothing to upload.
The destination is not one buffer per layer but an *arena* per layer. Each
`ChunkSectionLayer` has an `UberGpuBuffer` pair owning a growing list of
fixed-size heaps — 128 MiB for vertices, 32 MiB for indices — and each heap
is a real GPU buffer sub-allocated by a `TlsfAllocator`, freed again when it
empties. Sections are tenants in a shared allocation, not owners of buffers;
[blaze3d](blaze3d.md) is where those buffers come from.
And now the second fact this page exists to place: **the swap is atomic and
late.** `SectionRenderDispatcher.RenderSection.sectionMesh` keeps pointing at
the *old* mesh until every layer's vertex and index buffer has reported
uploaded. There is no frame in which a rebuilt section is missing, no flicker
and no hole — the price being that the section you can see is, for a few
frames, deliberately out of date. `SectionRenderDispatcher.RenderSection.reset`
is the other end of that lifecycle, and
`SectionRenderDispatcher.RenderSection.getVisibility` is not part of it at
all despite the name — it is the fade the next section explains, an alpha
that climbs from nothing to one over the upload's fade duration.
`SectionRenderDispatcher.RenderSection.resortTransparency` is the cheap path
that reorders an existing translucent mesh without recompiling anything;
[visibility and the frame graph](visibility-and-the-frame-graph.md) owns the
budget that decides when it runs.
## Questions players ask
**Why does distant terrain fade in, but a block I place never does?**
Because the fade is deliberately restricted to the case it was written for.
`SectionRenderDispatcher.RenderSection.setFadeDuration` is non-zero only for
distant sections that were not previously empty, and the fade clock starts at
a section's *first* upload — so a recompile of terrain you have been staring
at, which is what placing a block is, has no fade left to spend.
**I turned on "prioritise chunk updates" and it still costs me a frame. Why?**
Because of where compiling sits in the frame. Terrain is drawn *before* the
sections queued this frame are compiled, so the strongest promise the setting
can make is that the mesh exists by the end of frame *N*. It appears in frame
*N+1*. The setting buys you the compile, not the draw — and it buys it by
doing the work on the client thread, which is why it can also cost you the
frame outright.
**What happens when the buffer pool runs out?** Nothing visible, which is the
design. The worker that cannot acquire a `SectionBufferBuilderPack` puts its
section back on `SectionTaskDynamicQueue` — the *task* is requeued, not the
flag, which was cleared when the work was taken — so the only symptom is
terrain arriving more slowly. The pool is also allowed to
shrink itself: if it hits an out-of-memory error while allocating, it comes
back smaller and the game keeps going with fewer concurrent meshes.
> **For a 1.21-era reader.** The whole dirty API moved. Every
> *setBlockDirty*-shaped method that used to live on `LevelRenderer` is now on
> `LevelExtractor`, in *client/renderer/extract*, and the flags themselves
> live in `SectionUpdateTracker`. Below that: *ChunkRenderDispatcher* and
> *RenderChunk* are now `SectionRenderDispatcher` and its nested
> `SectionRenderDispatcher.RenderSection`, *CompiledChunk* is
> `CompiledSectionMesh`, *RenderChunkRegion* is `RenderSectionRegion`, and
> *LiquidBlockRenderer* is `FluidRenderer`. *RenderType.chunkBufferLayers* and
> its five chunk render types are three `ChunkSectionLayer`s.
> *BlockRenderDispatcher* and *BlockAndTintGetter.getShade* are gone.
## Where to look
`LevelExtractor.blockChanged` and `LevelExtractor.setBlockDirty` for where a
change becomes a flag, and `SectionUpdateTracker` for where the flag lives.
`SectionUpdateTracker.hasAllNeighbors` for the gate on a first compile.
`RenderRegionCache` and `SectionCopy` for what a mesher may read.
`SectionRenderDispatcher.RenderSection.compileAsync` and
`SectionTaskDynamicQueue` for what gets built and in what order.
`SectionCompiler.compile` for the walk itself, and `BlockModelLighter` for
where ambient occlusion comes from.
`SectionRenderDispatcher.uploadTerrainBuffersToGpu` for the upload and the
swap. Then [visibility and the frame
graph](visibility-and-the-frame-graph.md) for who decided the section was
visible in the first place, and [the client
level](../client/the-client-level.md) for the world all of this is reading.
---
*Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes `tools/verify_names.py`.*
---
# Models and atlases
> Verified against **Minecraft 26.2** · Part XI · a resource pack changes one texture, and every stone block in the world redraws.
You drop a pack into the folder, move it above the default, and the screen
goes to the loading overlay for a second. The pack replaced exactly one
file, a single stone texture, and when the overlay clears every block you
can see has been thrown away and rebuilt — not just the stone. The models
for stone were byte-identical before and after, and were still re-listed,
re-parsed, re-resolved and re-baked, because the sprite they point at is
now a different object at different coordinates in a different image.
**Nothing here is incremental. The unit of change is the model layer.**
Every block face, every item sprite and every particle texture starts as
JSON and a PNG in a pack and ends as four vertices pointing at a rectangle
of a stitched atlas. The stages below are that journey, and they happen
inside a resource reload — [the resource
system](../foundations/resource-system.md) owns `ReloadableResourceManager`
and the barrier this rides on — and at no other time.
## The cast
| class | what it decides | thread |
|---|---|---|
| `ModelManager` | the reload's spine, and what the finished lookup sets contain | workers, then the client thread at apply |
| `AtlasManager` | which atlases exist, and it publishes their stitches for others to await | client thread for the handshake, workers for the work |
| `SpriteLoader` | how one atlas is decoded, packed and mipmapped | workers |
| `ModelDiscovery` | which file an `Identifier` means, and what its parent chain resolved to | one worker task |
| `ModelBakery` | which unbaked model each `BlockState` and each item file bakes to | workers, in batches |
| `FaceBakery` | a quad's UVs, its rotated cull face, and its chunk layer | workers |
| `TextureManager` | who owns each `AbstractTexture`, and when an animation advances | client thread |
| `ItemModelResolver` | which `ItemModel` a given `ItemStack` draws with | client thread, every frame |
## The shape of the work: eighteen fans, one barrier
This is the clearest fan-out-and-barrier in the client. Eighteen independent
pieces of work start on worker threads at once — thirteen atlas stitches and
the five roots `ModelManager.reload` opens, three of them directory listings
that are each themselves a fan of one task per file — and they converge
exactly once.
```mermaid
flowchart TD
RL["a reload starts: F3+T, a pack change, or the game booting"]
HS["AtlasManager.prepareSharedState on the client thread, before any task runs"]
S1["blocks and items atlases: one task per sprite to decode and read metadata, then one stitch each, then mipmaps"]
S2["the other eleven atlases, the same fan, nothing awaits them until upload"]
L1["listing of models/, one task per file"]
L2["listing of blockstates/, one task per file"]
L3["listing of items/, one task per file"]
L4["EntityModelSet.vanilla and BuiltInBlockModels.createBlockModels"]
RES["ModelDiscovery interns and resolves, ModelGroupCollector groups the states"]
BAKE["ModelBakery.bakeModels in batches: one bake per BlockState, one per item file"]
BAR["the barrier"]
UP["client thread: TextureAtlas.upload, then ModelManager.apply"]
INV["LevelExtractor.allChanged raises a flag the next frame reads"]
RL --> HS
HS --> S1 & S2 & L1 & L2 & L3 & L4
L1 & L2 & L3 --> RES
S1 & RES --> BAKE
BAKE & S2 & L4 --> BAR
BAR --> UP --> INV
```
Read it as **spread, converge, upload, invalidate**: above the barrier,
worker threads in any order; below it, the client thread in exactly one.
## Thirteen atlases and three listings, all at once
**In:** every enabled pack. **Out:** thirteen stitched images, three parsed maps.
`AtlasManager` owns `AtlasManager.KNOWN_ATLASES` — thirteen of them, named
in `AtlasIds` — and each is a definition file in *atlases/*, not a folder
scan. `SpriteSourceList` runs that file's five kinds of source in order —
`SingleFile`, `DirectoryLister`, `SourceFilter`, `Unstitcher` and
`PalettedPermutations`, registered in `SpriteSources` — and a later source
overwrites an earlier one by id, which is how a pack replaces a vanilla
texture blind. `SpriteLoader.loadAndStitch` decodes and reads metadata one
task per sprite, hands the results to `Stitcher` — which sorts by height
and grows by powers of two — and returns a `SpriteLoader.Preparations`,
after which `MipmapGenerator` builds the mip chain under whatever
`MipmapStrategy` the texture's own metadata asks for.
Two properties of that packing surprise people. The mip level is clamped to
the smallest sprite's power of two, with a warning, so **one undersized
texture degrades mipmapping for every sprite in the atlas** — and only the
block atlas asks for mipmaps, the other twelve stitch flat. And sprite
padding derives from the mip level *and* the anisotropic filtering setting,
with the UVs computed inside the padded box, so anisotropy changes the
layout and every UV in the game. The mipmap slider is blunter still: it
writes the new level onto `AtlasManager` and schedules a texture reload, so
every sprite in the game is decoded and stitched again.
Meanwhile `BlockStateModelLoader` parses *blockstates/* into a
`BlockStateModel.UnbakedRoot` per `BlockState`, through
`BlockStateModelDispatcher` and `VariantSelector`, and `ClientItemInfoLoader`
parses *items/* into `ClientItem`.
### The handshake that lets two listeners share a future
Reload listeners are not supposed to reach into each other, and this pair
has to: baking cannot resolve a texture slot without knowing where the
sprite landed. `PreparableReloadListener.prepareSharedState` runs for
*every* listener on the client thread before *any* reload task starts, and
`AtlasManager` uses its turn to publish thirteen pending stitches under
`AtlasManager.PENDING_STITCH`, of which `ModelManager` awaits the two it
bakes against — the blocks atlas and the items atlas.
The *ordering* was always guaranteed anyway — the reload chains each
listener's barrier onto the previous one, and `AtlasManager` is registered
before `ModelManager` — so what the handshake buys is not order but the
end of the reaching-in.
## Interning the models, and dropping the ones that loop
**In:** the raw *models/* map. **Out:** a `ResolvedModel` per `Identifier`.
`ModelDiscovery` is a single worker task and the narrow waist of the
pipeline. Every `Identifier` becomes one `ModelDiscovery.ModelWrapper`,
caching its resolved texture slots and its baked geometry per `ModelState`,
and `ModelDiscovery.resolve` returns the map of `ResolvedModel`, whose
helpers walk the parent chain for geometry, slots, ambient occlusion and
transforms. A model whose parents never reach a root is logged and excluded,
and a model nothing references is parsed and never baked. In parallel,
`ModelGroupCollector` gives each block state a visual-equality group — the
fact that lets `ModelManager.requiresRender` later say *that change is
invisible*.
The JSON model itself is `CuboidModel`, made of `CuboidModelElement`,
`CuboidFace` and `CuboidRotation`, with `UnbakedGeometry` and
`UnbakedCuboidGeometry` between a resolved model and its quads, and
`Material`, `SpriteId`, `TextureSlots` and `MaterialBaker` are the
indirection from a model's *slot* reference to a real sprite.
## One bake per block state, and why that is affordable
**In:** resolved models and the stitched sprites. **Out:** a `ModelBakery.BakingResult`.
`ModelBakery.bakeModels` bakes once per *block state* and once per item
model file, sharing one baker whose caches are concurrent maps, and the
bakes are batched rather than scheduled individually, so this is tens of
tasks and not tens of thousands. `ModelBakery.MissingModels` — a block
part, a block, an item and a fluid — is baked first of all, so there is
always something to substitute. Two further bakes follow: the `BlockModel`
display layer, including the hard-coded `BuiltInBlockModels`, and the
`FluidStateModelSet`.
Dedup is what makes per-state baking cheap. Every state sharing an unbaked
variant gets the *same* baked object, geometry is cached per `ModelState`,
and vertex positions and material infos are interned. Multipart is the
exception that proves it: each state gets its own thin `MultiPartModel`
over a shared `MultiPartModel.SharedBakedState`, so every fence, wall, pane
and redstone-wire state really is a distinct object.
The output is a `QuadCollection` of `BakedQuad` — a ten-component record
of four positions, four packed UVs, a `Direction` recomputed from the baked
vertices, and a `BakedQuad.MaterialInfo` holding the sprite, the
`ChunkSectionLayer`, the item `RenderType`, the tint index, shade and light
emission. Three decisions land here and not later. Cull faces are rotated
at bake time — a rotated variant's north-culled quad is filed under the
rotated direction, in `UnbakedCuboidGeometry`, with `FaceBakery` doing the
UV half of the rotation and the uvlock — so the mesher never thinks about
it. Tint is *not* resolved: the quad carries an index, and the colour
arrives later from `BlockColors` or an `ItemTintSource`. And
`QuadCollection` carries translucent and animated flags, OR-ed up through
every model wrapper, so the mesher and `ItemStackRenderState` can decide
whether they need sorting or re-uploading without reading a quad.
### A quad's chunk layer is read out of the sprite's pixels
The most surprising decision in the pipeline is the one nobody has to make.
`FaceBakery.bakeQuad` does not take the render layer from the block or from
any per-face declaration: it asks `SpriteContents` what transparency actually
exists inside *that quad's UV rectangle*, and picks solid, cutout or
translucent from the answer.
**One pixel** — enough alpha inside a quad's UV rectangle to move that face
out of the solid layer (`FaceBakery`, asking `SpriteContents`).
A pack author who softens one edge of a texture has changed which chunk
layer that face draws in, and so when it is sorted and how it blends with
everything behind it. There is no warning, and only one way out: a
`Material` may set `Material.forceTranslucent`, from a *force_translucent*
key beside the sprite name, which skips the scan and takes the translucent
layer whatever the pixels say. A hundred and ten of the shipped models use
it — the stained glass, mostly — and it is the exception that shows the
rule, because it exists for textures whose alpha the scan would read
correctly and whose author wants them sorted anyway.
## A dozen ways to fail soft, and the two that crash
Missing and malformed input is a warning and a substitution at **twelve**
separate layers. An unparseable model file, an unparseable blockstate file
and a single bad variant selector are each swallowed locally, and the
broadest of the twelve is the last: a block state with no entry at all
still gets the missing model rather than an exception. In between sit
missing parents, cycles, bakes that throw, sprite ids that belong to no
atlas, unbound and unresolvable slot chains, and a block model caught
reaching outside the block atlas. The startup sweep that would catch the
last case, `Minecraft.selfTest`, **only runs in a development
environment**, and there it throws rather than warns.
Two places have no soft path. If `Stitcher` cannot grow an atlas within the
device's maximum texture size it raises `StitcherException`, which becomes
a crash report listing every sprite, and mip generation is the second hard
crash site. A pack with too many textures does not degrade — it crashes.
## The barrier, and how a sprite reaches the GPU
**In:** everything above. **Out:** live lookup sets and live GPU textures.
Only now does the client thread do anything. `TextureAtlas.upload` builds
the new texture and closes the old sprites, and `ModelManager.apply`
assigns the new lookup sets in one go. Underneath sits `TextureManager`, a
`PreparableReloadListener` owning every `AbstractTexture` by `Identifier` —
each atlas included — plus the `TickableTexture`s it advances, and it
registers the checkerboard `MissingTextureAtlasSprite` at construction.
The GPU side is [blaze3d](blaze3d.md)'s.
How a sprite's pixels reach the atlas depends on whether it moves. A static
sprite goes into a throwaway scratch texture, is blitted into every mip
level by a render pass, and the scratch texture is closed. An animated
sprite instead keeps one *persistent* texture per unique frame for the life
of the atlas and is redrawn only when it has something new to show, through
a second pipeline entirely if the animation interpolates —
`SpriteContents.AnimationState` drives it and
`TextureAtlas.cycleAnimationFrames` steps it.
## The flag that rebuilds the world
**In:** a successful reload. **Out:** every visible section re-meshed, a frame later.
Nothing rebuilds during the reload. `LevelExtractor.allChanged` raises a
flag, and on the *next frame's* extract
`LevelRenderer.invalidateCompiledGeometry` builds a new `SectionCompiler`
from the new model sets and queues every section for re-meshing — see
[section meshing](section-meshing.md). `SectionCompiler` is the biggest
consumer of `BlockStateModelSet.get` but far from the only one: the
block-breaking overlay, moving blocks, the in-wall screen effect, the
nether-portal sprite on the loading screen, `TerrainParticle` and
`BlockMarker` all read it too, with the HUD, `LevelExtractor` and
`FeatureRenderDispatcher` covering the rest. The path from a set to triangles is
`BlockStateModel.collectParts` then `BlockStateModelPart.getQuads`, over
`SingleVariant`, `WeightedVariants` or `MultiPartModel` with
`SimpleModelWrapper` as the usual concrete part. None of it crosses the
network: the server has no idea what a model is.
## How an item picks its model
An item never asks for a model by name. Every `ItemStack` carries
`DataComponents.ITEM_MODEL` — an `Identifier`, nothing else — and that id
is the whole decision: a stack whose component says *diamond sword* renders
as a diamond sword whatever item it actually is.
`ItemModelResolver` — held by `Minecraft`, called by the entity, block
entity and GUI renderers as they extract a frame — reads the component,
asks `ModelManager.getItemModel` for the baked model and
`ModelManager.getItemProperties` for the flags that travel with it, then
calls `ItemModel.update`, which appends one or more layers to an
`ItemStackRenderState`. Both lookups fall back rather than fail: an unknown
id quietly yields the missing item model and the default
`ClientItem.Properties`, whose two visible flags are whether the item plays
the hand-swap animation and whether it may overflow its slot in the GUI.
What each id maps to came from *items/*, parsed into `ClientItem` by
`ClientItemInfoLoader` and baked alongside the block models. `ItemModels`
registers **eight** kinds of unbaked item model and only one of them draws
anything by itself — the rest select, compose, dispatch on a range or
delegate — while `SpecialModelRenderers` covers the thirteen shapes no
cuboid model can express, from chests and banners to shields, heads,
tridents and decorated pots ([block-entity
rendering](block-entity-rendering.md) is where those thirteen get their
geometry). `ItemModelGenerator` is the odd one out and by
far the most-used model in the game: it extrudes a flat sprite into
geometry by tracing its alpha channel, which is what *item/generated*
means.
One rule is enforced here and nowhere else. A block model may only use the
block atlas, and a cuboid *item* model must draw every quad from a single
atlas — items or blocks, not both. A model that mixes them throws, the
exception is caught upstream, and the item falls back to the missing model.
Where the stack itself comes from is [items and
stacks](../items/items-and-stacks.md).
## Questions players ask
**Why does lag slow the water down but the pause menu not stop it?** Because
animations do not advance once per tick. `TextureManager.tick` sits outside
the client's catch-up tick loop, so a laggy client advances every animation
by one frame however many ticks it just owed. Pausing does not touch it —
`DeltaTracker` keeps handing out ticks with the menu open — and the one thing
that does stop the water is `/tick freeze`, because the guard on that call is
`Minecraft.isLevelRunningNormally` and nothing else.
**Why does an item frame sometimes look different from the block in it?**
Because blocks have two model layers with different jobs. `BlockStateModel`
is the quad source for the mesher and for particles, while `BlockModel` — a
different interface in a different package, reached through
`ModelManager.getBlockModelSet` — is the *display* model used by item
frames, block entities and ten entity renderers. Tints and a transform
belong to its usual implementation, `BlockStateModelWrapper`, not to the
interface.
**Can I look at the atlas?** Yes. A debug keybind writes every atlas to
disk, each with a text listing of every sprite's position and size beside it;
a shared-constant flag makes the game dump one at upload time as well.
**Why do some block updates cost nothing to draw?** Because two states that
look identical share a group id from `ModelGroupCollector`, and
`ModelManager.requiresRender` returns false for a change between them,
unless the fluid state differs.
> **For a 1.21-era reader.** This subsystem was renamed and re-packaged
> wholesale:
>
> | you will hunt for | it is now |
> |---|---|
> | *BakedModel* | split into `BlockStateModel` and `ItemModel` |
> | *ModelResourceLocation* | gone — block models are keyed by `BlockState` directly |
> | *BlockModelShaper* | `BlockStateModelSet` |
> | *WeightedBakedModel* | `WeightedVariants` |
> | *BlockElement*, *BlockElementFace* | `CuboidModelElement`, `CuboidFace` |
> | *BlockModelDefinition* | `BlockStateModelDispatcher` |
> | *AtlasSet* | `AtlasManager` |
> | *ItemModelShaper*, *BlockRenderDispatcher*, *ItemRenderer*, *ItemColors*, *SpriteTicker* | gone with no successor of that name |
>
> Two traps. `BlockModel` still exists and now means something entirely
> different — the display model above, not the JSON one. And
> `TextureAtlas.LOCATION_BLOCKS` and its siblings are deprecated but far
> from dead: they are still the *texture* ids the atlas-membership checks
> compare against, while `AtlasIds` names the *definition* files.
## Where to look
`ModelManager.reload` for the shape of the pipeline, then
`AtlasManager.prepareSharedState` for the handshake that makes it parallel.
`ModelDiscovery` for resolution, `ModelBakery.bakeModels` for baking,
`SpriteLoader.loadAndStitch` and `Stitcher` for the atlas,
`FaceBakery.bakeQuad` for where a face's layer is decided, and
`ItemModelResolver` and `BlockStateModelSet.get` for the two ways out.
---
*Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes `tools/verify_names.py`.*
---
# Entity rendering
> Verified against **Minecraft 26.2** · Part XI · a zombie is drawn: extract, submit, prepare, execute.
A zombie shuffles out of the dark towards you, you hit it, and for a moment it
flashes red. Between that zombie and those pixels stand four stages, each
handing the next a value object rather than a shared one: the live mob is read
into a fresh render state, the state is described as things that *ought* to be
drawn, the descriptions are sorted and batched, and only then does anything
write a vertex. Which is why `EntityRenderer` has no *render* method — nothing
on this page draws anything — and why the zombie is posed **at least twice**
in the frame you are looking at, three times if it is glowing, an
arrangement that is only sound because `Model.setupAnim` resets every part to
its baked pose before it starts.
All four stages are one thread, inside one `Minecraft.renderFrame`. The split
is a discipline, not a threading boundary: the first stage touches live
entities, the other three only snapshots. Nothing here reads the network —
everything drawn is a redrawing of what the server already told the client
([what the client is told](../networking/what-the-client-is-told.md),
[synched entity data](../entities/synched-entity-data.md)).
## The cast
| class | what it decides | thread |
|---|---|---|
| `LevelExtractor` | which entities are visible at all, and when their states are built | Render thread |
| `EntityRenderDispatcher` | which `EntityRenderer` an entity gets, and the hand's own lighting | Render thread |
| `EntityRenderer` | what goes into the render state, and what gets submitted — it never draws | Render thread |
| `EntityRenderState` | one entity's whole frame, copied by value, holding no `Entity` and no `Level` | a value object |
| `RenderLayer` | the extras — armour, held items, eyes, capes — each submitting at its own order | Render thread |
| `SubmitNodeStorage` | the submit nodes, bucketed by a global draw order | Render thread |
| `FeatureRenderDispatcher` | the grouping, the batching, and where the vertices are finally written | Render thread |
| `ModelFeatureRenderer` | the one feature renderer that walks a `ModelPart` tree | Render thread |
## Four stages, and what each hands the next
```mermaid
flowchart TD
E["Extract: LevelExtractor walks the visible entities and fills one fresh render state per entity"]
S["Submit: LevelRenderer walks the states, and each renderer describes what should be drawn"]
P["Prepare: FeatureRenderDispatcher sorts every submit, groups it, and builds the vertices"]
X["Execute: the frame graph's passes issue the draws the prepare already built"]
E -- "value objects, no Entity, no Level" --> S
S -- "submit nodes in SubmitNodeStorage, bucketed by order" --> P
P -- "one PreparedFrame of batched draws" --> X
```
The worked instance is one zombie going through all four.
```mermaid
sequenceDiagram
participant LX as LevelExtractor
participant ERD as EntityRenderDispatcher
participant ZR as ZombieRenderer
participant ZS as ZombieRenderState
participant LR as LevelRenderer
participant SNS as SubmitNodeStorage
participant FRD as FeatureRenderDispatcher
participant ZM as ZombieModel
LX->>LX: isEntityVisible — frustum via ERD, then: is its section compiled and visible?
LX->>ERD: extractEntity(zombie, its own partial tick)
ERD->>ZR: createRenderState — a fresh object, every entity, every frame
ZR->>ZS: extractRenderState down the whole chain
Note over ZS: position lerp, walk animation, equipment,
hasRedOverlay from hurtTime or deathTime,
lightCoords from getPackedLightCoords
ZR->>ZS: finalizeRenderState — sample the blocks under it for shadow pieces
LR->>ERD: submit(state, camera, relative position, PoseStack, collector)
ERD->>ZR: LivingEntityRenderer.submit
ZR->>SNS: submitModel — the PoseStack pose is COPIED, not held
ZR->>ZM: setupAnim — so the layers can read posed parts
ZR->>SNS: each RenderLayer submits at its own order
ZR->>SNS: submitLeash and submitNameTag, from the renderer
ERD->>SNS: submitFlame if burning, submitShadow if it has pieces
LR->>FRD: prepareFrame — group by feature type, batch by RenderType
FRD->>ZM: setupAnim again, then walk ModelPart and write vertices
FRD->>FRD: executeSolid, then executeTranslucent, then executeOutline
```
## Extract: the live entity becomes a snapshot
**In:** `ClientLevel.entitiesForRendering`, and a frustum.
**Out:** one freshly allocated render state per surviving entity.
**Decided:** visibility, and everything the other three stages will ever know.
`LevelExtractor.extractVisibleEntities` walks the level's entity list, drops
what `LevelExtractor.isEntityVisible` rejects, and hands each survivor to
`EntityRenderDispatcher.extractEntity`. That call finds the renderer for the
entity's `EntityType` through `EntityRenderDispatcher.getRenderer`, allocates
a state with `EntityRenderer.createRenderState`, fills it by running
`EntityRenderer.extractRenderState` down the whole inheritance chain, and then
lets `EntityRenderer.finalizeRenderState` reach into the world one last time
to sample the shadow.
Visibility is **two tests in two places**. The frustum test is
`EntityRenderer.shouldRender`, on the renderer; the "is the section this entity
stands in actually compiled and visible" test belongs to `LevelExtractor`, and
block entities must additionally clear a separate section threshold to count at
all. Several things escape the frustum entirely: anything indirectly carrying
the local player, the three renderers that declare themselves unculled, a
`Display` that sets its own no-culling flag, and — the ones nobody expects —
an entity on the other end of a visible leash, an end crystal with a beam
target and a guardian firing one, each of which is drawn because something
*else* in view is attached to it.
Light is not read at draw time. It comes from
`EntityRenderer.getPackedLightCoords` during extract — the dispatcher has a
method of the same name, but its one caller is the first-person hand — and
that method returns full brightness for a burning entity. Shadows are sampled here too,
and never past sixteen blocks: the renderer walks the blocks under the entity,
computes an alpha for each, and stores the shapes and alphas in the state, so
that the feature renderer three stages later only has to turn them into quads.
The strength falls to nothing at sixteen blocks, so a distant mob has no
shadow at any settings, and an invisible entity skips the sampling entirely.
### The ladder of render states
*(figure: tree-EntityRenderState.svg — a generated SVG, not reproduced here)*
Every render state in the game, by depth. Click to enlarge.
The tree is a ladder, each rung adding what the rung below could not assume.
`EntityRenderState` is the floor: position, `EntityRenderState.ageInTicks`,
`EntityRenderState.lightCoords`, `EntityRenderState.outlineColor`,
`EntityRenderState.nameTag`, `EntityRenderState.leashStates` and
`EntityRenderState.shadowPieces` — as true of a dropped item as of a wither.
`LivingEntityRenderState` adds rotations,
`LivingEntityRenderState.walkAnimationPos`,
`LivingEntityRenderState.deathTime`, `LivingEntityRenderState.isBaby` and
`LivingEntityRenderState.hasRedOverlay`. `ArmedEntityRenderState` adds hands,
`HumanoidRenderState` a pose and equipment, `UndeadRenderState` what the undead
share, `ZombieRenderState` two flags of its own. The player sits off the ladder
in `AvatarRenderState`.
Nothing in any of them holds an `Entity` or a `Level` — verified across every
class in the tree. The one member that looks live, an `AnimationState` on the
eleven states that carry one, is a single-int tick counter copied by value,
not a handle back into the world.
### The red flash is not a colour
It starts as `LivingEntity.hurtTime` — or `LivingEntity.deathTime` — and
becomes the boolean `LivingEntityRenderState.hasRedOverlay` here, at extract.
At submit, `LivingEntityRenderer.getOverlayCoords` packs that boolean into an
`OverlayTexture` coordinate alongside a separate white-flash axis, the one
the creeper's fuse uses — and, besides the creeper, only a primed TNT minecart
and the sulfur cube's inner layer. The wither is not on that list: it flashes
by swapping to a second texture. That packed integer rides through the submit node
untouched and lands, at execute, as a **per-vertex attribute**. Nothing along
the way is ever tinted red.
## Submit: describing a draw without making one
**In:** the render states, the camera, and a shared `PoseStack`.
**Out:** submit nodes in `SubmitNodeStorage`, bucketed by order.
**Decided:** what should be drawn, and where in the world's draw order.
`LevelRenderer.submitEntities` walks the states and calls
`EntityRenderDispatcher.submit` for each, after
`EntityRenderDispatcher.prepare` has set the camera for the frame.
`LivingEntityRenderer.submit` then describes the body, poses the model, and
lets every `RenderLayer` describe its own extra through `RenderLayer.submit` —
in that order, because the pose is only needed by the layers.
The description API is `SubmitNodeCollector` and its ordered form:
`OrderedSubmitNodeCollector.submitModel`, `.submitItem`, `.submitText`,
`.submitNameTag`, `.submitShadow`, `.submitFlame`, `.submitLeash`,
`.submitBlockModel` and `.submitCustomGeometry`, plus
`SubmitNodeCollector.order` to choose a bucket. `SubmitNodeStorage` keeps one
`SubmitNodeCollection` per order, and each collection files what it is given
into one of fifteen named phases — `SubmitNodeCollection.solid`,
`.translucentModels`, `.breakingOverlay`, `.outline` and eleven more, all
catalogued in [submit phases and feature
renderers](../../reference/submit-phases.md).
**`SubmitNodeCollector.order` is a global key, not a per-entity one.** Order
one means *after every entity's order-zero body in the whole world*, not
"after this entity's body". That is how the eyes layer, the armour layers and
the enchantment glint stack correctly across a crowd instead of interleaving
one mob's helmet with another's head. Armour claims consecutive orders as it
goes, so a dyed, enchanted, trimmed helmet occupies four — leather is the
only dyeable helmet and its equipment definition has two layers of its own —
and exactly one
layer in the game asks for a **negative** order, to get underneath everything.
The pose stack is transient, and half of it is dropped. A submit *copies* the
current pose; nothing downstream ever sees the stack. Models, items and block
models copy the full pose, while shadows, name tags, text and leashes copy
only the 4×4, so no normal matrix crosses for those. A leaked push is fatal,
but the check happens at the end of the *submit phase*, on a local stack, not
at the end of the frame. Avatars differ in one small way: the crouch offset is
removed *before* the shadow is submitted for a player and *after* it for
everything else, which stops a sneaking player's shadow sinking into the
ground.
### The renderers and models being described
Renderers and models are **shared and mutable**; render states are not. One
`ZombieRenderer` serves every zombie in the world — though it holds an adult
model, a baby model and two baked armour sets — and safety comes entirely from
the fresh per-entity state and from replaying the animation at draw time. The
chain is `EntityRenderer` → `LivingEntityRenderer` → `MobRenderer` →
`AgeableMobRenderer` → `HumanoidMobRenderer` → `AbstractZombieRenderer` →
`ZombieRenderer`. `AgeableMobRenderer` is deprecated and is not the base of
every humanoid: the enderman and the giant extend `MobRenderer` directly, the
armour stand and the avatar extend `LivingEntityRenderer`, all four while
using humanoid models. Players are served by `AvatarRenderer`, keyed by **skin
model rather than entity type** — the type map has no entry for the player or
the mannequin, and the dispatcher keeps two avatar maps, wide and slim,
falling back to wide.
Geometry is `Model` → `EntityModel` → `HumanoidModel`, built out of
`ModelPart`s. A model is baked once from a `LayerDefinition` /
`MeshDefinition` / `PartDefinition` tree named by a `ModelLayerLocation` in
`ModelLayers` and held in an `EntityModelSet`; `LayerDefinitions` is the single
static table that builds every one of them, out of the `CubeListBuilder` /
`CubeDefinition` / `CubeDeformation` / `PartPose` vocabulary. Posing is
`Model.setupAnim`, hand-written or driven by an `AnimationDefinition` of
`Keyframe`s through `KeyframeAnimation`, whose channels interpolate linearly
or along a Catmull–Rom spline.
Extras hang off `RenderLayer` — `HumanoidArmorLayer`, `ItemInHandLayer`,
`CustomHeadLayer`, `WingsLayer`, `EyesLayer`, `CapeLayer` and forty-odd more.
Armour and trims funnel through `EquipmentLayerRenderer`, dressed by
`EquipmentAssetManager` and `EquipmentClientInfo` out of the resource packs,
and a renderer holds a whole `ArmorModelSet` per body size rather than one
armour model. Player skins come by another road — `SkinManager`,
`SkinTextureDownloader`, `PlayerSkinRenderCache`, with `DefaultPlayerSkin` as
the fallback — and held or worn items through `ItemModelResolver` and
`ItemStackRenderState`, [models and atlases](models-and-atlases.md)'s business.
## Prepare: sorting, batching, and the vertices
**In:** a whole frame of submit nodes.
**Out:** a `FeatureRenderDispatcher.PreparedFrame` whose vertices already exist.
**Decided:** how few draws this can be.
`FeatureRenderDispatcher.prepareFrame` drains every phase of every order
bucket, groups the nodes, and lets the thirteen feature renderers build
geometry — `ModelFeatureRenderer` being where a `ModelPart` tree finally
becomes vertices. **Only two of the thirteen kinds of submit can be batched at
all** — a model and a piece of custom geometry — and everything else keeps
the order it was submitted in and merges only with its immediate neighbour.
Where the zombies of the world do collapse into one draw, that is the group
finding they all want the same `RenderType`. A translucent phase marks its
group strictly ordered, and that switches off exactly one of the group's two
merges: consecutive submits of one render type still share a draw, and what
stops is the fold into a *non-adjacent* earlier draw — which is the merge that
would move geometry ahead of everything between, and so undo the depth sort
the phase exists for.
### Why the zombie is animated more than once
Once during **submit** — but only because it has layers, and
`ItemInHandLayer`, `CustomHeadLayer` and half a dozen others need posed
`ModelPart`s to hang things off. Once again, per model submission, at
**prepare** time, because the submit node carried the model and the state but
not a pose for every part. A glowing zombie is animated three times, since the
outline is a second submission of the same model into
`SubmitNodeCollection.outline`. A fourth pass exists in the machinery — the
crumbling overlay — but no entity ever reaches it: the only non-null
`ModelFeatureRenderer.CrumblingOverlay` in the game is built in
`LevelExtractor`'s *block-entity* loop, so it is a chest being mined that
gets a fourth pose, never a mob. That the repeats are sound at all is only
because `Model.setupAnim` **resets every part to its baked
pose first**, so each call is idempotent from a known base — not because the
model is stateless, which it emphatically is not.
## Execute: the frame graph pulls the trigger
**In:** the prepared frame.
**Out:** draws.
**Decided:** almost nothing — the ordering was fixed two stages ago.
`FeatureRenderDispatcher.PreparedFrame` exposes five drains. `.executeSolid`,
`.executeTranslucent`, `.executeOutline` and `.executeTranslucentAfterTerrain`
are called from the frame graph's main pass, while `.executeAlwaysOnTop` runs
in a later pass of its own that clears depth first. Where those passes sit
relative to terrain, sky and post-processing is [visibility and the frame
graph](visibility-and-the-frame-graph.md)'s subject; the draws go out through
[blaze3d](blaze3d.md).
## Three things shaped like this pipeline, that are not it
**Block entities** take the same four stages under a different visibility
policy, a different partial tick and an empty block model —
[block-entity rendering](block-entity-rendering.md) is the whole of the
difference.
**The first-person hand** is a second pipeline entirely: `ItemInHandRenderer`
submits into its own `SubmitNodeStorage` and is drawn by
`FeatureRenderDispatcher.renderAllFeatures`, outside the frame graph — see
[the frame](the-frame.md).
**Hitboxes** left the renderer. F3+B is a debug-entry toggle read by a
separate debug renderer that emits gizmo primitives, suppressed under reduced
debug info, and the old hitbox render state record is dead code with exactly
two references, both inside its own file. Name tags borrow in the same way:
the submit node carries only a `Component`, and every glyph in it is resolved
by [text and fonts](../client/text-and-fonts.md), not here.
> **For a 1.21-era reader.** `EntityRenderer` has no *render* method, and
> neither does anything else on this page: the pair is
> `EntityRenderer.extractRenderState`, which reads the live entity, and
> `EntityRenderer.submit`, which describes what should be drawn without
> touching a vertex. *MultiBufferSource* does not exist anywhere in the game,
> nor any other buffer source. The rest of the names to stop hunting for:
> *PlayerRenderer* (now `AvatarRenderer`, for players and mannequins alike),
> every *render* method on `EntityRenderer` and `RenderLayer` (now *submit*),
> *EntityRenderDispatcher.renderHitbox* and *renderLeash*,
> *LivingEntityRenderer.getBob*, *MobRenderer.prepareMobModel*,
> *RenderType.entityCutoutNoCull* (the polarity flipped — the culled variant
> is now the one that says so), *ItemBlockRenderTypes*, and *ElytraLayer*
> (now `WingsLayer`). `RenderLayerParent` survives in name only: it is now a
> single-method interface, and the texture lookup that used to live on it is
> gone. `PoseStack` and `ModelPart` are unchanged.
## Where to look
`EntityRenderDispatcher.extractEntity` and `EntityRenderer.extractRenderState`
for the first stage, `LivingEntityRenderer.submit` for the second — the
clearest single method in the part. Then `SubmitNodeCollection` for the phase
list and `ModelFeatureRenderer` for where vertices are written. `ModelLayers`
and `LayerDefinitions` for how a model is described, and [submit phases and
feature renderers](../../reference/submit-phases.md) for both catalogues.
---
*Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes `tools/verify_names.py`.*
---
# Block-entity rendering
> Verified against **Minecraft 26.2** · Part XI · a chest on the ground and a chest in your hand, drawn in the same frame by two renderers that share a model and nothing else.
You place a chest, step back, and hold a second one up in front of your face.
Both are chests, both are lit, both open the same lid on the same hinge — and
almost nothing about how they got onto the screen is shared. The one on the
ground is a *block entity*: the terrain mesh at its position contains no
geometry at all, and everything you can see of it was extracted from the live
world by `ChestRenderer` a few microseconds ago. The one in your hand is an
*item*: it has no block entity, no render state and no extract stage, and it
is drawn by a class in a different package that exists only because an item
cannot be a block entity. The seam shows if you type */tick freeze*. The chest
on the ground is nailed to its last tick; the chest in your hand keeps
swaying with your view bob, because the two are drawn at **different partial
ticks** and only one of them respects the freeze.
This page is the sibling of [entity rendering](entity-rendering.md), and does
not re-teach it. Extract, submit, prepare, execute; render states that hold no
live object; `SubmitNodeCollector` and the fifteen phases behind it — all of
that is that page's, and all of it is true here. What follows is only the
differences, and they are larger than the shared machinery suggests.
## The cast
| class | what it decides | thread |
|---|---|---|
| `LevelExtractor` | which block entities are candidates at all — the two lists it walks | Render thread |
| `BlockEntityRenderDispatcher` | the renderer for a type, and the two gates every extraction passes | Render thread |
| `BlockEntityRenderer` | the geometry, and its own answers to *how far* and *off screen* | Render thread |
| `BlockEntityRenderState` | one block entity's whole frame: position, block state, type, light, break progress | a value object |
| `ClientLevel` | membership of the globally-rendered set, decided once when the block entity is added | Render thread |
| `SpecialModelRenderer` | the thirteen shapes an item model cannot express, drawn without a block entity | Render thread |
| `SpecialModelWrapper` | how an item model reaches one — the item road into `renderer/special` | Render thread |
| `BuiltInBlockModels` | how a *block state* reaches one, in a model table terrain never reads | a worker thread, on every resource reload |
## Three roads to the same collector
```mermaid
flowchart TD
BE["A chest placed in the world"]
IT["A chest in your hand, on a shelf, or on the ground as an item"]
BD["A chest carried by something that is not a chest — a block display, a minecart, an enderman"]
SEC["LevelExtractor walks the visible sections, then the globally-rendered set"]
BERD["BlockEntityRenderDispatcher — one shared ChestRenderer, one fresh ChestRenderState"]
IMR["ItemModelResolver reads the item-model component and opens a layer"]
SMW["SpecialModelWrapper puts a ChestSpecialRenderer in that layer"]
BMR["BlockModelResolver reads the built-in block-model table"]
SBM["SpecialBlockModelWrapper puts a ChestSpecialRenderer in that state"]
COL["SubmitNodeCollector — the same phases, the same feature renderers, the same vertices"]
BE --> SEC
SEC --> BERD
BERD --> COL
IT --> IMR
IMR --> SMW
SMW --> COL
BD --> BMR
BMR --> SBM
SBM --> COL
```
Two of the three roads end in `renderer/special`, and that is the package's
whole reason to exist: a chest that is not a block entity still has to look
like a chest. Only the left-hand road has a visibility policy of its own, a
state class of its own, or an extract stage that reads the live world.
| | entity | block entity | special model |
|---|---|---|---|
| what is walked | the level's renderable entities | the visible sections' meshes, then a global set | nothing — it is reached from a model |
| the visibility test | a frustum, plus a size-scaled distance | the *section* is visible, then a per-renderer radius | whatever drew the thing holding it |
| the stages | extract, finalize, submit | extract, submit | resolve and submit, in one call |
| the state | an `EntityRenderState` subclass | a `BlockEntityRenderState` subclass | a layer of an `ItemStackRenderState` |
| the partial tick | one computed per entity | one for every block entity in the world | the camera entity's |
| where the pose comes from | the dispatcher, from the state's position | `LevelRenderer`, translated to the block | the item transform for the display context |
| how many | one renderer per entity type | 24 renderer classes, 26 of the 49 types | 13 renderers under 13 ids |
## The chest, both halves, one frame
```mermaid
sequenceDiagram
participant LX as LevelExtractor
participant BERD as BlockEntityRenderDispatcher
participant ChestR as ChestRenderer
participant LR as LevelRenderer
participant IIHR as ItemInHandRenderer
participant IMR as ItemModelResolver
participant CSR as ChestSpecialRenderer
Note over LX,CSR: the extract half — one partial tick for every block entity in the world
LX->>LX: walk visibleSections, skip a section under 0.3 of its fade
LX->>BERD: tryExtractRenderState with the not-global flag
BERD->>BERD: the flag must equal shouldRenderOffScreen, then shouldRender within 64 blocks
BERD->>ChestR: createRenderState, then extractRenderState
ChestR->>ChestR: combine with the neighbour half — lid openness and the brighter of two lights
Note over LX,CSR: the draw half — the world first, the hand afterwards, in two storages
LR->>BERD: submit, with the pose already translated to the block
BERD->>ChestR: submit — one model, one sprite, no world access
IIHR->>IMR: resolve the held stack at the camera entity partial tick
IMR->>CSR: the item model has no quads, only a special renderer
CSR->>CSR: submit the same ChestModel with its openness fixed
```
The two halves of the figure are not two stages of one pipeline. The world's
block entities go through `LevelRenderer.submitFeatures` into the frame
graph. The held chest is submitted, prepared and drawn in
`GameRenderer.renderItemInHand`, into `GameRenderer.handAndScreenSubmitNodeStorage` — a
second storage, drained by `FeatureRenderDispatcher.renderAllFeatures` after
the whole world is already on the screen. They never share a submit node.
## The chest's block model is empty, and there are two tables of them
Open *blockstates/chest.json* and it names one model for every state. Open
that model and it declares a particle texture and no elements. So when
`SectionCompiler` walks the section and tesselates every block whose render
shape is *MODEL*, the chest contributes exactly zero quads to the terrain
mesh — and, in the same pass, adds itself to the section's list of block
entities. Everything you see of a placed chest is drawn one stage later, by a
renderer, from a snapshot.
That is what a block entity renderer is *for*: the shapes that a cuboid model
cannot express, and the state that a block state cannot hold. Only 26 of the
49 entries in `BlockEntityTypes` have a renderer registered in
`BlockEntityRenderers`, and the other 23 — furnaces, hoppers, barrels,
beehives — are drawn entirely by their block models, like any other block.
Having a block entity does not make a block interesting to look at.
There are **two** baked block-model tables, and confusing them is easy.
`ModelManager.getBlockStateModelSet` is the one built from the resource
packs, and it is the one `SectionCompiler` reads: a chest is empty in it.
`ModelManager.getBlockModelSet` is built on top of that one and merged with
`BuiltInBlockModels.createBlockModels`, which attaches a
`SpecialBlockModelWrapper` to every state of every chest, banner, skull,
shulker box, conduit, decorated pot, bell, enchanting table and end portal in
the game. Nothing in terrain ever reads that table — that is the whole of the
separation, and it is membership rather than behaviour. `BlockModelResolver`
is its one reader, and its callers are all entity renderers: item frames,
block displays, minecart contents, the block an enderman is carrying. When one
of them draws a chest it gets the quads **and** the special renderer, both,
because that road draws whatever it finds; terrain simply never asks this
table, and reads `BlockStateModelSet` instead, where the chest's entry is
empty.
## Culling by section, not by frustum
A block entity is never frustum-tested. `LevelExtractor.extractVisibleBlockEntities`
starts from `LevelRenderer.visibleSections` — the reachability walk that
[visibility and the frame graph](visibility-and-the-frame-graph.md)
describes — and takes each section's compiled list of block entities whole.
Culling has already happened, one section at a time.
Two extra gates then apply, and both are stricter than they look. The first is
the section's own fade-in: a freshly uploaded section reports a visibility
ramping from zero to one over a duration the *chunk section fade-in time*
option sets, and `LevelExtractor` skips its block entities until that number
reaches **0.3**. Terrain fades in from the first frame, and the chests inside
it appear about a third of the way through — furniture arriving after the
room. `LevelRenderer.compileSections` zeroes the duration for a section within
about twenty-eight blocks of the camera or one that was empty before, so the
gate only bites on distant terrain, which is exactly where you would blame the
draw distance for it.
The second is a distance test with the same name as the entity one and only
half of its behaviour. `EntityRenderer.shouldRender` does a size-scaled
distance test *and* a frustum intersection. `BlockEntityRenderer.shouldRender`
keeps the distance half alone: a camera position, and whether the block's
centre is within `BlockEntityRenderer.getViewDistance`.
**Sixty-four blocks** — the default, taken by nineteen of the twenty-four
renderer classes, and it does not scale with your render distance the way
`Entity.shouldRender` does.
| renderer | how far | why |
|---|---|---|
| the other nineteen | 64 | the interface default |
| `PistonHeadRenderer` | 68 | a moving block starts outside the block it is drawn from |
| `BlockEntityWithBoundingBoxRenderer` | 96 | the structure block's outline is a build tool |
| `TheEndGatewayRenderer` | 256 | the beam is the thing you are looking for |
| `BeaconRenderer` | the render distance in blocks | and measured **horizontally only** |
| `TestInstanceRenderer` | the larger of its two delegates | it wraps a beacon and a bounding box |
The beacon is the interesting row twice over. Its `BlockEntityRenderer.shouldRender` flattens both
positions onto the horizontal plane before comparing, so altitude never costs
you the beam — you can be at build height above a beacon at bedrock and still
see it. And its extraction scales the beam's radius by the horizontal distance
divided by 96, floored at one, so **the beam gets visibly wider the further
away you stand**, which is why a distant beacon does not thin into nothing.
Raising a spyglass resets the scale to one, because `BeaconRenderer` checks
whether the local player is scoping. The topmost beam segment is drawn to
`BeaconRenderer.MAX_RENDER_Y`, 2048 blocks above the block.
### Off screen means off *this* list
`BlockEntityRenderer.shouldRenderOffScreen` is not an extra permission — it is
a switch between two mutually exclusive lists, and it is enforced twice.
`ClientLevel.onBlockEntityAdded` puts a block entity into
`ClientLevel.getGloballyRenderedBlockEntities` only if its renderer says yes,
and `BlockEntityRenderDispatcher.tryExtractRenderState` throws the extraction
away unless the flag it was called with **equals** the renderer's answer. The
second check is what stops double-drawing: a beacon inside a visible section
is in that section's list *and* in the global set, and the equality test is
the only thing that picks one. Exactly three renderers say yes —
`BeaconRenderer`, `BlockEntityWithBoundingBoxRenderer` and
`TestInstanceRenderer`, which says yes because either of its two delegates
does.
## What a block entity's snapshot carries
`blockentity/state` holds 26 classes: the base and twenty-five subclasses. The
base is five fields wide — the position, the block state, the type, packed
light sampled from the level, and the crumbling overlay — and it is filled by
one static method, `BlockEntityRenderState.extractBase`, that every renderer
calls before adding its own. There is no `EntityRenderer.finalizeRenderState`
counterpart here: `BlockEntityRenderer` declares one extraction method, not
two, so nothing reaches back into the world after the snapshot is taken.
Twenty-five subclasses, twenty-six classes: `BedRenderState` is reachable from
nothing in the game. There is no bed block entity in `BlockEntityTypes` and no
bed renderer, and a corpus-wide search for the name finds only its own file.
It is the only orphan in the package.
Five states carry another pipeline's snapshot inside them, which is where the
machines actually touch — and **two** of the five carry an *entity* state, not
an item one. `SpawnerRenderState.displayEntity` is a whole
`EntityRenderState`, extracted through `EntityRenderDispatcher` from a display
entity the spawner creates client-side — a mob that `LevelExtractor` never
sees, never frustum-tests, and whose light the spawner overwrites with the
block's. `VaultRenderState.displayItem` reads like the item cases and is not
one: it is an `ItemClusterRenderState`, which extends `EntityRenderState`, and
the vault submits it through `ItemEntityRenderer` — the same renderer that
draws a dropped item lying on the ground. The three genuine item carriers are
`ShelfRenderState.items`, an array of three `ItemStackRenderState` that reaches
`ChestSpecialRenderer` from inside a block-entity render state, and
`CampfireRenderState.items` and `BrushableBlockRenderState.itemState` for what
is cooking and what is buried.
One live handle survives into a snapshot, and it is not unique to this side:
`MovingBlockRenderState` is a one-block fake world that holds the level's
`MovingBlockRenderState.lightEngine` and `MovingBlockRenderState.cardinalLighting`
by reference, so a moving block is lit at *prepare* time rather than at
extract. `PistonHeadRenderState` carries up to two of them, and the entity
side's `FallingBlockRenderState` carries one.
Signs are the other partial exception. `SignRenderState` stores the two
`SignText` objects rather than laid-out glyphs, and `AbstractSignRenderer`
calls `Font.split` during **submit** — the line wrapping of a sign happens a
stage later than everything else in the frame.
## One partial tick for the whole world
This is the difference a player can see. `LevelExtractor` gives every entity
its own partial tick, asking `TickRateManager.isEntityFrozen` per entity, so a
mob exempt from a freeze keeps interpolating while its neighbours stop. Block
entities get no such question: they all receive the single
`DeltaTracker.getGameTimeDeltaPartialTick` value that `GameRenderer.extract`
computed for the world, with the frozen-game flag honoured — which returns
exactly 1.0 while the game is frozen. Every block entity in the world is
therefore pinned to its last completed tick, with no per-block exemption
anywhere in the path.
The held chest is a third answer again. `GameRenderer` draws it with
`Camera.getCameraEntityPartialTicks`, which asks the frozen check about the
*camera entity* and, when it is not frozen, ignores the frozen game entirely —
and `TickRateManager.isEntityFrozen` never freezes a `Player`. So under
*/tick freeze* the item in your hand is redrawn from a live partial tick while
every chest lid in the world is stopped dead.
The same split shows up in the Christmas textures, which the game implements
three times. `ChestRenderer` reads `SpecialDates.isExtendedChristmas` **once,
in its constructor**, and its constructor runs only when
`BlockEntityRenderDispatcher.onResourceManagerReload` rebuilds every renderer
— so a placed chest that was ordinary at 23:59 on the 23rd stays ordinary
until the next resource reload. The item model in *items/chest.json* selects on
the *minecraft:local_time* property, whose `LocalTime` implementation re-checks
the clock at most once a second. And the built-in block model wraps its two
chests in a `ConditionalBlockModel` whose `IsXmas` property calls the same
static method live, every time the model is resolved. Two of the three notice
midnight almost at once. The one you are standing in front of does not.
## Where `renderer/special` borrows its geometry
`SpecialModelRenderers.bootstrap` registers thirteen renderers under thirteen
ids, dispatched by a codec, and the package holds exactly thirteen renderer
classes to match. Nine of them implement `NoDataSpecialModelRenderer` and read
nothing at all from the stack; the other four — banner, decorated pot, player
head and shield — pull one component out of it through
`SpecialModelRenderer.extractArgument`, which is the closest thing this road
has to an extract stage.
Eleven of the thirteen reach into `client/renderer/blockentity` for their
geometry. Three hold an instance of the block-entity renderer outright
(`BannerSpecialRenderer`, `DecoratedPotSpecialRenderer`,
`ShulkerBoxSpecialRenderer`); the rest call a static submit or name a static
texture or model layer on one — `SkullBlockRenderer.submitSkull`,
`AbstractEndPortalRenderer.submitSpecial`, `BannerRenderer.submitPatterns`,
`ChestRenderer.LAYERS`. Only `TridentSpecialRenderer` and
`CopperGolemStatueSpecialRenderer` stand alone. The chest in your hand really
is the same `ChestModel`, baked from the same `ModelLayerLocation`, posed at a
fixed openness instead of an interpolated one.
### How an empty item model turns into a chest
Reaching a special renderer from an item is one indirection: `SpecialModelWrapper` is an
`ItemModel` like any other, and its baked form puts the renderer into a layer
of the `ItemStackRenderState` through
`ItemStackRenderState.LayerRenderState.setupSpecialModel`. A layer either has
quads or has a special renderer, never both, and the layer's submit picks
whichever it has. That is the entire mechanism by which an empty item model
turns into a chest.
> **For a 1.21-era reader.** `BlockEntityRenderer` has no *render* method
> either: the pair is `BlockEntityRenderer.extractRenderState` and
> `BlockEntityRenderer.submit`, and `blockentity/state` is a package that did
> not exist. Three names to stop hunting for: *getRenderBoundingBox*, absent
> from the corpus entirely, because visibility is the section's business now
> plus the radius in `BlockEntityRenderer.getViewDistance`, *BedRenderer*, and
> every *renderItem* on a block-entity class — an item held by a block entity
> now goes through `ItemModelResolver` into an `ItemStackRenderState` like any
> other. `BlockEntityRenderer.shouldRenderOffScreen` survives with its meaning
> narrowed to *which of two lists*, `BlockEntityRenderers` still registers by
> `BlockEntityType`, and the fifteen submit phases these renderers land in are
> shared with entities.
## Where to look
`BlockEntityRenderDispatcher.tryExtractRenderState` first — it is twenty-one
lines and it contains both visibility gates. Then
`LevelExtractor.extractVisibleBlockEntities` for the two lists it is called
from, and `ChestRenderer` as the clearest renderer in the package, since it is
the one with a counterpart in `renderer/special` to compare against. For the
other road, `SpecialModelWrapper` and `ItemStackRenderState.LayerRenderState`,
then `BuiltInBlockModels` for the block-state road nobody expects to exist.
[Submit phases and feature renderers](../../reference/submit-phases.md) is the
catalogue everything here submits into.
---
*Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes `tools/verify_names.py`.*
---
# Lightmap, fog and sky
> Verified against **Minecraft 26.2** · Part XI · the sun goes down: every colour on screen, traced back to one keyframe curve.
Stand on a hill and watch the light go. The sky over the taiga slides from
blue towards black, the murk closes in until the far trees dissolve, stars
come up, the moon takes whatever shape it is owed tonight, and if a storm
arrives the scene goes grey and streaked. Five renderers make those colours —
`Lightmap` decides how bright, `FogRenderer` how far, `SkyRenderer` and
`CloudRenderer` what is up there, `WeatherEffectRenderer` what is coming down
— and between them they ask one question and nothing else: *what is this
attribute worth, here, now?* The surprise is who they ask. **Most of them no
longer know what time it is.** They ask a probe for a named value at a
position and a partial tick, and the day/night curve behind it is keyframes in
a data pack. Two still read the raw world clock — the clouds, because they
drift, and the rain, whose texture scrolls — but the weather asks nobody for a
colour: it seeds each column of rain from that column's own *coordinates*, and
touches no attribute and no probe at all.
## The cast
| class | what it decides | thread |
|---|---|---|
| `EnvironmentAttributeProbe` | what any attribute is worth at the camera, this frame | Render thread |
| `LightmapRenderStateExtractor` | the lightmap's ten uniforms, and whether to redraw at all | Render thread |
| `Lightmap` | how bright, as the 16×16 texture every terrain vertex samples | Render thread |
| `FogRenderer` | how far you can see, in what colour, and in which medium | Render thread |
| `SkyRenderer` | what hangs above the horizon — and which of two skies it is | Render thread |
| `CloudRenderer` | the cloud cells, and the face list built from them | `CloudRenderer.prepare` bakes on a worker, the rest on Render |
| `WeatherEffectRenderer` | which columns get rain, which get snow, and how hard | Render thread |
| `LevelRenderer` | which of those become frame-graph passes at all | Render thread |
## What a renderer has to know about an attribute, and no more
An **environment attribute** is a named, typed quantity — a colour, a
distance, an angle, a moon phase — that the world answers for a position and
an instant. `EnvironmentAttributeSystem` assembles that answer by running a
short stack of layers over the attribute's own default: the dimension, the
biome, one layer per timeline the dimension runs, and weather where a
dimension can have it. That machinery belongs to [environment attributes and
timelines](../world/environment-attributes-and-timelines.md); this page
assumes it and names only what it consumes. Three consequences shape
everything below.
**The client resolves the same stack from the same data** — it is never sent
a resolved colour — and what it adds is `EnvironmentAttributeProbe`, on the
camera. `EnvironmentAttributeProbe.tick` re-samples the biome neighbourhood
once per client tick and rolls each probed value's new answer down into last
tick's; `EnvironmentAttributeProbe.getValue` fetches the fresh one lazily,
during the frame, and interpolates between the two by partial tick; and any
attribute nobody asked for during a tick is evicted. Every renderer here that
asks for an attribute at all goes through the probe and never through the
system — and one of the five asks for none.
**Whether a value smooths or steps is declared on the attribute**, not chosen
by the renderer — which is why the sky colour slides and
`EnvironmentAttributes.MOON_PHASE` snaps, and why a renderer that wants a
different curve must ask for a different attribute.
**`ClientLevel` adds two layers of its own** on top of the four, and both are
the **lightning** flash: one lerps `EnvironmentAttributes.SKY_COLOR` a fifth
of the way towards a pale blue-white, the
other pins `EnvironmentAttributes.SKY_LIGHT_FACTOR` to one while the flash
lasts. Neither has anything to do with the End's sky flash, which never
enters the stack at all.
### What the dimension type and the biome still carry
Some of the old per-dimension and per-biome data survived the migration
unchanged. `DimensionType.skybox` is a
three-valued `DimensionType.Skybox` — `DimensionType.Skybox.NONE`,
`DimensionType.Skybox.OVERWORLD`, `DimensionType.Skybox.END` — and it is a
*branch*, not a colour. `BiomeSpecialEffects` still exists, hollowed out to
`BiomeSpecialEffects.waterColor`, `BiomeSpecialEffects.grassColorOverride`,
`BiomeSpecialEffects.grassColorModifier` and the foliage colours: every fog
and sky colour left it for `Biome.getAttributes`. None of it crosses the
network as pixels — the inputs arrive as registry sync during configuration,
attribute maps filtered through `EnvironmentAttributeMap.NETWORK_CODEC`, then
world time and weather during play ([protocol
phases](../networking/protocol-phases.md)) — so a data pack retints a
dimension without touching the client.
## The five askers
| renderer | what it asks for | when it asks | what it produces |
|---|---|---|---|
| `Lightmap`, through `LightmapRenderStateExtractor` | how bright block light and sky light should read, and in what tint | once per tick, at a partial tick of exactly one | ten std140 uniforms and one 16×16 texture |
| `FogRenderer` | the colour of the murk and the six distances it lives between | once per frame, inside the camera extract | one `FogData`, uploaded as one UBO slice |
| `SkyRenderer` | where the sun, moon and stars are, and how bright | once per frame | a `SkyRenderState` for the sky pass |
| `CloudRenderer` | what colour the clouds are and how high they sit | once per frame, read for it by `LevelExtractor` | a compressed face list, rebaked only when it must be |
| `WeatherEffectRenderer` | nothing, until it is raining | once per frame, and only then | a list of `WeatherEffectRenderer.ColumnInstance` |
## The trace: the sun goes down
```mermaid
sequenceDiagram
participant Time as Timelines
participant EAS as EnvironmentAttributeSystem
participant EAP as EnvironmentAttributeProbe
participant LRSE as LightmapRenderStateExtractor
participant FR as FogRenderer
participant SR as SkyRenderer
participant LR as LevelRenderer
participant LM as Lightmap
Note over Time,EAS: per client tick
Time->>EAS: the keyframe tracks for this world time — SUN_ANGLE, SKY_COLOR, SKY_LIGHT_FACTOR
EAP->>EAP: tick — Gaussian biome blend, last becomes new, unread attributes evicted, driven from Camera.tick
LRSE->>LRSE: tick — flicker walk, then needsUpdate is raised
EAS->>EAS: invalidateTickCache, the last statement of ClientLevel.tick — marks the non-positional values stale, recomputing none of them
Note over EAP,SR: per frame, extract
LRSE->>EAP: getValue(SKY_LIGHT_FACTOR, BLOCK_LIGHT_TINT, AMBIENT_LIGHT_COLOR)
LRSE-->>LM: LightmapRenderState — ten std140 values, plus the flag
FR->>EAP: getValue(FOG_COLOR, SUNRISE_SUNSET_COLOR, SKY_FOG_END_DISTANCE)
FR-->>LR: FogData — one colour and six distances, in one UBO
SR->>EAP: getValue(SUN_ANGLE, MOON_ANGLE, STAR_BRIGHTNESS, MOON_PHASE)
SR-->>LR: SkyRenderState
Note over LM,LR: per frame, render
LM->>LM: render — one three-vertex draw into a 16×16 texture
LR->>LR: addSkyPass — disc, sunrise fan, sun, moon, stars, dark disc
LR->>LR: addMainPass — terrain samples the lightmap
LR->>LR: addCloudsPass, then addWeatherPass
```
The middle band's order is a dependency order. `GameRenderer.extract` runs
`LightmapRenderStateExtractor.extract`, then `GameRenderer.extractCamera` —
where `FogRenderer.setupFog` stashes its `FogData` on
`CameraRenderState.fogData` — then `LevelExtractor.extract`, which drives
`WeatherEffectRenderer.extractRenderState` and
`SkyRenderer.extractRenderState`. Then `GameRenderer.renderLevel` uploads the
fog with `FogRenderer.updateBuffer`, takes one slice with
`FogRenderer.getBuffer`, and `LevelRenderer.render` declares the passes.
`SkyRenderer.renderSunriseAndSunset` is the clearest instance of the pattern.
The sunrise fan's colour *is* `EnvironmentAttributes.SUNRISE_SUNSET_COLOR`, an
ARGB keyframe track, and its visibility is that colour's own alpha channel —
which the renderer also scales the fan's depth by. The fade is a property of
the data, not of the geometry, so a data pack restyles the sunset without a
line of client code changing.
## How bright: one draw per tick, and no partial ticks at all
`Lightmap` is a 16×16 `GpuTexture` plus a `MappableRingBuffer` of uniforms.
`Lightmap.render` writes those uniforms and issues **one three-vertex draw**
with `RenderPipelines.LIGHTMAP`: the brightness curve lives in the shader and
the whole texture is a by-product of it. In 1.21 this was a `NativeImage`
filled pixel by pixel in Java and re-uploaded every frame.
What it draws from is `LightmapRenderState`: ten values in std140 order — six
floats from `LightmapRenderState.skyFactor` and
`LightmapRenderState.blockFactor` to `LightmapRenderState.brightness`, then
four colours, `LightmapRenderState.blockLightTint` and
`LightmapRenderState.skyLightColor` from
`EnvironmentAttributes.BLOCK_LIGHT_TINT` and
`EnvironmentAttributes.SKY_LIGHT_COLOR`, the other two from
`EnvironmentAttributes.AMBIENT_LIGHT_COLOR` and
`EnvironmentAttributes.NIGHT_VISION_COLOR` — and an eleventh field,
`LightmapRenderState.needsUpdate`, which is not a uniform at all but the flag
that decides whether the draw happens. `LightmapRenderStateExtractor.tick`
runs the torch-flicker random walk in
`LightmapRenderStateExtractor.blockLightFlicker` and raises its own copy of
the flag; `LightmapRenderStateExtractor.extract` copies it across, clears it,
and reads the probe alongside `Options.gamma`, `Options.darknessEffectScale`,
the conduit-power water vision and
`LightmapRenderStateExtractor.calculateDarknessScale`. **So the lightmap is
recomputed once per tick and not once per frame** — and, deliberately,
`GameRenderer.extract` hands the extractor a partial tick of exactly one
while `FogRenderer.setupFog` and `SkyRenderer.extractRenderState` get the
real one. Sky and fog interpolate mid-tick. World lighting steps.
Three leftovers. `Lightmap.getBrightness` survives but no longer feeds the
lightmap: it is a CPU-side duplicate of the shader's curve, kept for `Hud`,
`EntityRenderer`'s shadow sampling and `ScreenEffectRenderer` alone. The
packing statics moved out of the texture into `LightCoordsUtil`, from where a
packed value reaches a vertex through `VertexConsumer.setLight`. And
`UiLightmap` is the 1×1 white `DynamicTexture` handed out while
`GameRenderer.useUiLightmap` is set.
### Two curves that look like one
`EnvironmentAttributes.SKY_LIGHT_FACTOR` is a *visual* attribute, spatially
interpolated, and the lightmap reads it;
`EnvironmentAttributes.SKY_LIGHT_LEVEL` is a *gameplay* attribute, not
positional, and `Level.updateSkyBrightness` turns it into `Level.skyDarken`
for mob spawning. `Timelines.OVERWORLD_DAY` keyframes both, at slightly
different times and to different night values — so they look like one number,
and a data pack can pull them apart.
## How far: one block for the whole frame, filled by a priority walk
`FogRenderer`'s output is a mutable `FogData`: `FogData.color` plus six
distances — a start and an end each for the medium and the horizon, then
`FogData.skyEnd` and `FogData.cloudEnd`. In open air those are
`EnvironmentAttributes.FOG_COLOR`, `EnvironmentAttributes.FOG_START_DISTANCE`,
`EnvironmentAttributes.FOG_END_DISTANCE`,
`EnvironmentAttributes.SKY_FOG_END_DISTANCE` and
`EnvironmentAttributes.CLOUD_FOG_END_DISTANCE`, and underwater they are
`EnvironmentAttributes.WATER_FOG_COLOR`,
`EnvironmentAttributes.WATER_FOG_START_DISTANCE` and
`EnvironmentAttributes.WATER_FOG_END_DISTANCE` instead. It owns one ring
buffer, `FogRenderer.regularBuffer`, and beside it a second buffer of its
own, `FogRenderer.emptyBuffer`, filled with *infinitely far* for when fog is
off.
**There is one fog UBO for the whole frame, not one per pass.**
`LevelRenderer.render` takes a single slice and hands the same one to the
sky, main, weather and always-on-top passes, and does not hand it to the
clouds pass — which reads a cloud fog end out of the same buffer anyway,
because the binding is sticky and the shader simply keeps reading what was
last bound. The sky and cloud fog ends are separate fields *inside that one
block*, which the shaders choose between, so what a player sees as
per-element fog is a shader decision and not a binding.
### The list that decides the colour
The colour and the darkening come from different places.
`FogRenderer.FOG_ENVIRONMENTS` is an ordered list and the order *is* the
priority: `LavaFogEnvironment`, `PowderedSnowFogEnvironment`,
`BlindnessFogEnvironment`, `DarknessFogEnvironment`, `WaterFogEnvironment`,
and `AtmosphericFogEnvironment` **last**, which is what makes it the
guaranteed fallback. `FogRenderer.computeFogColor` makes **one** pass down
that list carrying two independent latches — it takes the colour from the
first environment whose `FogEnvironment.providesColor` is true and the
darkness from the first whose `FogEnvironment.modifiesDarkness` is, which need
not be the same one — whereas
`FogEnvironment.setupFog` stops at the first applicable one, and it and
`FogEnvironment.isApplicable` are the class's only abstract methods.
`MobEffectFogEnvironment` declares `FogEnvironment.providesColor` false on
purpose: blindness and darkness may darken somebody else's colour, never
supply one, and the atmospheric environment sits last precisely so somebody
always does. Which medium the camera is in is a `FogType` (`FogType.WATER`,
`FogType.LAVA`, `FogType.POWDER_SNOW`, `FogType.ATMOSPHERIC`,
`FogType.NONE`), and *NONE* maps to the atmospheric environment. Rain fog is
the only stateful one: `AtmosphericFogEnvironment.rainFogMultiplier` is an
exponential follower, so the murk lags a storm starting rather than
snapping to it, and `AtmosphericFogEnvironment.updateRainFogState` thickens it
even in a biome with no precipitation, at half strength.
## What is up there: two skies, and a texture that is never bound
`SkyRenderer` builds every buffer it will ever need in its constructor, from
`SkyRenderer.buildStars` to `SkyRenderer.buildMoonPhases` against the
`AtlasIds.CELESTIALS` atlas, and per frame fills a `SkyRenderState` running
from `SkyRenderState.skybox` through `EnvironmentAttributes.SUN_ANGLE`,
`EnvironmentAttributes.MOON_ANGLE`, `EnvironmentAttributes.STAR_ANGLE` and
`EnvironmentAttributes.STAR_BRIGHTNESS` to `SkyRenderState.endFlashIntensity`.
**The stars are the same in every world.** `SkyRenderer.buildStars` seeds a
fixed constant and rejects samples outside a shell, so `SkyRenderer.STAR_COUNT`
is an attempt count rather than a star count, and they are rebuilt only when a
resource reload takes the whole renderer down:
`LevelExtractor.onResourceManagerReload` sets
`LevelExtractor.shouldResetSkyRenderer` and `LevelRenderer.addSkyPass` closes
and reconstructs the entire `SkyRenderer`, stars, moon phases and all. The moon
phase, likewise, is no longer arithmetic on the day count — it is
`EnvironmentAttributes.MOON_PHASE` driven by `Timelines.MOON`, whose period is
`MoonPhase.COUNT` days, and the renderer picks a sub-quad of an eight-quad
buffer by `MoonPhase.index`.
**The End takes a different branch entirely.** With
`DimensionType.Skybox.END`, `SkyRenderer.extractRenderState` fills only the
End-flash fields: the sun angle, the moon phase, the sky colour and the dark
disc are never sampled. And `EndFlashState` is not the dragon fight — it is a
free-running flash on a six-hundred-tick cycle, seeded per interval for its
offset, duration and angles, advanced by `EndFlashState.tick` in any dimension
whose skybox is the End's. The sky is also skipped five ways, four of them in one method:
`LevelRenderer.addSkyPass` bails in lava, in powder snow, when
`CameraRenderState` reports that a mob effect blocks the sky — which is
blindness and darkness folded into one boolean before the method is entered —
and when `DimensionType.Skybox` is *NONE*, which is the Nether. The fifth is
outside it: `GameRenderer.renderLevel` suppresses the sky when a boss bar
wants world fog, with `AtmosphericFogEnvironment.setupFog` clamping the fog
hard in that case.
### The clouds, which get no fog and no texture
The clouds are the first of the two exceptions: their colour and height are
`EnvironmentAttributes.CLOUD_COLOR` and `EnvironmentAttributes.CLOUD_HEIGHT`,
but their *drift* is raw world time. **And the cloud texture is never bound as
a texture.** `CloudRenderer.prepare` does the whole job on a worker — reading
the image and baking it into `CloudRenderer.TextureData` through
`CloudRenderer.packCellData`, one 64-bit word per pixel with the colour in the
high bits and four neighbour-emptiness flags in the low four — and
`CloudRenderer.apply` is two statements on the client thread that install the
result and raise the rebuild flag.
`CloudRenderer.buildMesh` walks cells of `CloudRenderer.CELL_SIZE_IN_BLOCKS`,
writing three bytes per face through `CloudRenderer.encodeFace` — a compressed
*face list*, expanded to quads in the shader, with
`CloudRenderer.RelativeCameraPos` and `CloudStatus` deciding which faces
exist. It is rebuilt on a reload, when the camera crosses a cell boundary or
changes side, or when the `CloudStatus` changes — and a data pack setting the
cloud colour to zero alpha removes the pass entirely.
## What is coming down: rebuilt every frame, and seeded from the clock
`WeatherEffectRenderer` is the second exception: its columns are placed by
world position, but the streaks are seeded from raw world time. It holds one
`WeatherEffectRenderer.vertexBuffer` and the precomputed tangent tables
`WeatherEffectRenderer.columnSizeX` and `WeatherEffectRenderer.columnSizeZ`,
and its per-frame product is a list of `WeatherEffectRenderer.ColumnInstance`
records inside a `WeatherRenderState`.
`WeatherEffectRenderer.extractRenderState` returns immediately when the rain
level is zero, so a clear sky costs nothing. Otherwise it loops every column
in a square of radius `Options.weatherRadius`, querying the heightmap and the
precipitation at each — every frame, on the CPU. The vertex buffer is rebuilt in
`WeatherEffectRenderer.render` rather than in extract, rain and snow are two
indexed draws sharing it, and the world border rides in the same pass. Particles and sound are somebody else's job:
`ClientLevel.tickWeatherEffects` spawns those per tick within the same radius,
next to `ClientLevel.animateTick`, which scatters
`EnvironmentAttributes.AMBIENT_PARTICLES`.
## What is not an attribute
The migration was not total, which is why *everything is an attribute now*
needs a qualifier. `DimensionType.ambientLight` and
`DimensionType.cardinalLightType` are plain record fields, read directly —
and the first of the two no longer reaches the lightmap at all: its two readers are
`Lightmap.getBrightness`, the CPU-side duplicate this page has already said
the shader does not use, and one deprecated method on `LevelReader`. Block tint never moved at all: grass, foliage and water
are still `BiomeColors` reading `BiomeSpecialEffects` through the four
`ColorResolver`s, with no probe and no layer stack in it. And the clouds still
read the world clock, because a value sampled at the camera and lerped by
partial tick is the wrong shape for a drift — while the weather reads neither
clock nor attribute, seeding each column from its own coordinates.
> **For a 1.21-era reader.** Nearly every per-dimension, per-biome,
> per-time-of-day visual constant is an environment attribute now, so the
> names to stop hunting for are: *LightTexture* (now `Lightmap` plus
> `LightCoordsUtil`), *DimensionSpecialEffects* and all three subclasses (now
> `DimensionType.skybox` plus attributes), *FogParameters* (now `FogData`),
> *RenderSystem.setShaderFogColor* and its siblings (now one
> `RenderSystem.setShaderFog` taking a uniform slice),
> *LevelRenderer.renderSky* / *renderClouds* / *renderSnowAndRain* (now the
> `LevelRenderer.addSkyPass` family of frame-graph passes, declared as
> [visibility and the frame graph](visibility-and-the-frame-graph.md)
> describes), and *Level.getSkyColor*, *ClientLevel.getStarBrightness* and
> *ClientLevel.effects*, all attributes now. The draws went the way of
> everything in [blaze3d](blaze3d.md), from `RenderPipelines.LIGHTMAP` and
> `RenderPipelines.SKY` to `RenderPipelines.WEATHER_DEPTH_WRITE`.
## Where to look
`LightmapRenderStateExtractor.extract` first, then `Lightmap.render` for what
it feeds. `EnvironmentAttributeProbe.getValue` for the question every renderer
here asks, and [environment attributes and
timelines](../world/environment-attributes-and-timelines.md) for how it is
answered. `FogRenderer.computeFogColor` for the priority walk.
`SkyRenderer.extractRenderState` and `LevelRenderer.addSkyPass` for the sky
and its two branches, `CloudRenderer.buildMesh` and
`WeatherEffectRenderer.extractRenderState` for the meshes rebuilt inside the
frame, and `BiomeColors` for the colour system that did not move.
---
*Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes `tools/verify_names.py`.*
---
# Particles
> Verified against **Minecraft 26.2** · Part XI · A player breaks a block, and the puff of block texture appears on every screen within sixty-four blocks.
The block goes. On the breaker's own machine the puff is already there,
predicted, before the server has heard about the swing; on every other
machine within sixty-four blocks it arrives a moment later as a level event
and lands as the same sixty-four textured quads, built by the same method
from the same shape. Two entirely different routes, one visual result — and
the interesting thing is what neither route does. Neither asks how far away
you are. Neither asks whether the packet was worth sending. And neither asks
your particle setting, which three pieces of the client read three different
ways, while the server is told what you chose and never once acts on it. **A
particle is not something the game decides to show you. It is something that
survives a series of gates that disagree about what they are gating.**
## The cast
| class | what it decides | thread |
|---|---|---|
| `ServerLevel` | which players are told about a particle at all — on dimension and distance, nothing else | Server |
| `ParticleType` | the type's identity in the registry, and `ParticleType.getOverrideLimiter`, the "ignore the limits" flag baked into it | either |
| `ClientLevel` | the gated entry point, `ClientLevel.doAddParticle` — and the ungated ones beside it | Client |
| `ParticleResources` | which provider a type gets, registered once at construction, and which `SpriteSet` — rebound on every reload | load off-thread, bind on Client |
| `ParticleEngine` | the groups, the one-tick admission queue, the emitters, the per-type counts | Client |
| `ParticleGroup` | whether there is room: the per-render-type cap and the probabilistic reservoir | Client |
| `ClientExplosionTracker` | how many explosion particles happen this tick, and where — the client's own budgeted generator | Client |
| `SingleQuadParticle.Layer` | which of three atlases a quad reads, and which of two pipelines draws it | Client |
Everything below the second row runs on the client thread, and the only
off-thread work in the system is the load half of `ParticleResources.reload`
— the bind that rebuilds each `SpriteSet` comes back to the client thread.
Several things cross the network and none of them is a particle. Three carry
the bulk of it: a `ClientboundLevelParticlesPacket` is an explicit request
carrying count, spread, speed and two override flags, a
`ClientboundLevelEventPacket` is an event the client interprets, and a
`ClientboundExplodePacket` is a
description the client expands itself.
## Does the particle happen at all?
Both routes start in the same place. `Block.spawnDestroyParticles` raises
level event `LevelEvent.PARTICLES_DESTROY_BLOCK` with the breaker as the
source, and the two branches diverge only because of *who* that source is
relative to whoever is watching.
```mermaid
sequenceDiagram
participant MPGM as MultiPlayerGameMode
participant Block as Block
participant SL as ServerLevel
participant PL as PlayerList
participant CPL as ClientPacketListener
participant CL as ClientLevel
participant PE as ParticleEngine
Note over MPGM,PE: the breaker's own client, predicting
MPGM->>Block: playerWillDestroy, then spawnDestroyParticles
Block->>CL: levelEvent, PARTICLES_DESTROY_BLOCK, the breaker as source
CL->>CL: LevelEventHandler, the sound, then addDestroyBlockEffect
CL->>PE: add, one TerrainParticle per quarter-block cell of the shape
Note over SL,PE: everybody else, within 64 blocks
Block->>SL: the same levelEvent, on the server's copy of the block
SL->>PL: broadcast within 64 blocks, skipping the source
PL->>CPL: ClientboundLevelEventPacket
CPL->>CL: levelEvent, then the same addDestroyBlockEffect
CL->>PE: add, the identical particles
```
**Neither route is gated.** Both end in `ClientLevel.addDestroyBlockEffect`,
which calls `ParticleEngine.add` directly and never passes through
`ClientLevel.addParticle` — so neither the distance check nor the particle
setting applies to either of them. The branches differ in who dispatches the
event, not in what the client then does with it.
And the breaker is not always a player.
`LevelEvent.PARTICLES_DESTROY_BLOCK` has fifteen call sites and only three
pass a source at all — a bed, a tall plant, and `Block.playerWillDestroy`
itself. A fox faceplanting into snow, a rabbit eating a carrot down one age,
a sheep eating grass, a zombie breaking a door, a suspicious block that fell
and shattered, and `Level.destroyBlock` itself all raise it with a null source
— which means the server broadcasts it to *everybody*, including whoever
caused it.
**Sixty-four** — quads in a full cube's puff, because
`ClientLevel.addDestroyBlockEffect` walks every box of
`BlockBehaviour.BlockStateBase.getShape` on a fixed quarter-block grid with
a minimum of two cells per axis.
That is the *outline* shape, not the collision shape, and the difference is
visible: a torch has no collision shape at all and still gives twelve
particles, where reading the collision shape would give none. Each particle
shows a different randomly-offset quarter-crop of the block's sprite, which
is why the puff does not look tiled. A block may opt out of the whole thing
— `BlockBehaviour.BlockStateBase.shouldSpawnTerrainParticles` gates both the
destroy and the crack effects — and `TerrainParticle` additionally refuses
air and `Blocks.MOVING_PISTON`.
### Three neighbours that look like the same thing
The *crack* particles that fly off while you are still mining come from
`ClientLevel.addBreakingBlockEffect`, called once per client **tick** from
`Minecraft.continueAttack` by way of `Minecraft.handleKeybinds`, and never
networked at all — your neighbour's screen shows their own crack particles,
computed locally, not yours. The `/particle` command arrives as
`ClientboundLevelParticlesPacket` and goes through `ClientLevel.addParticle`
once per requested count with Gaussian spread, unless the requested count is
zero, which is a second mode entirely: one particle whose velocity is the
offset vector scaled by the speed, which is how a *directed* particle is
spawned. And the ambient scatter is a fixed cost paid every tick regardless
of what is there — `ClientLevel.animateTick` samples 1,334 random block
positions, 667 within sixteen blocks and 667 within thirty-two, most of
which do nothing. Riding on that one loop are the drip particles
(`ClientLevel.trySpawnDripParticles`), the biome's `AmbientParticle` list
named by `EnvironmentAttributes.AMBIENT_PARTICLES`
([environment attributes and timelines](../world/environment-attributes-and-timelines.md)),
and the barrier and light **block markers** — in creative, holding one of
those two items makes every matching block in range emit a marker particle,
which is the only reason you can see them.
## Who is allowed to see it?
There are three distance rules, they are enforced by three different pieces
of code, and two of them happen to be the same number.
| the gate | measured from | the distance | what an override does to it |
|---|---|---|---|
| the server choosing whom to send a `ClientboundLevelParticlesPacket` to | the receiving player | 32 blocks | *widens* it, to 512 — but from the caller's own boolean, in practice `/particle … force`, and never from the particle type |
| the client deciding whether to build the particle at all, in `ClientLevel.doAddParticle` | the **camera** | 32 blocks | skips the check entirely |
| the server broadcasting a level event — the break puff's second route | the receiving player | 64 blocks | not consulted: a level event carries no particle type |
| `ClientLevel.addDestroyBlockEffect`, and everything else that hands `ParticleEngine.add` a finished particle | — | none | nothing to override |
The two thirty-twos are independent, not one check written twice. A particle
that clears the server's test can still be dropped by the client's, because
the client measures from where you are *looking* rather than from where your
feet are, and a packet that took a tick to arrive is measured against a
camera that has since moved. And the two overrides are not one flag either:
the client's comes off the particle type, through
`ParticleType.getOverrideLimiter`, whose three readers are all client-side,
while the server's is a boolean the caller passes in. One deletes a check
outright; the other multiplies a different check by sixteen.
The 64-block radius is the third rule, and nothing overrides it in either
direction. Once a level event lands the client asks no further questions,
which is why a break puff at the edge of view is unconditional where the
same particle requested by `/particle` would never have been sent.
## Does the setting apply?
Three pieces of the client read the same particle setting, and none of them
agrees with the others about what its values mean.
| who reads it | what it does |
|---|---|
| `ClientLevel.doAddParticle` | *decreased* is rewritten to *minimal* about a third of the time, and *minimal* drops everything — except that the always-show flag rescues a *minimal* setting one time in ten, and the rescue lands on *decreased*, which is then re-rolled |
| `ClientExplosionTracker` | anything below *All* is off. The pending explosions are cleared unused, and there is no decreased tier for explosion block particles at all |
| `ClientLevel.tickWeatherEffects` | on *decreased*, halves its column count — rain thins rather than stopping; on *minimal* it breaks out before adding anything, and there it does stop |
Only the first of those is the gate everything is nominally supposed to go
through, and **four call sites outside the particle package bypass it
entirely** by handing a constructed particle straight to `ParticleEngine.add`:
the break puff, the crack effect, the firework starter and the item-pickup
streak. A fifth is inside the system, a firework spark spawning more sparks. The same puff arriving as a particle
*packet* would be distance-culled and might be diced away by the setting.
Arriving as a level event, it is unconditional.
The last piece reads like a bug and is not. **The server knows your particle
setting and never uses it.** It arrives in the client information and is
stored on the player, and the broadcast filters on dimension and distance
and nothing else — so turning particles down saves your GPU and costs the
server exactly nothing.
## Is there room for it?
Explosions are the one source that budgets itself before it asks anyone
else, and they are not a particle packet at all. `ServerLevel.explode` sends
a `ClientboundExplodePacket` carrying a radius, a block count and a
`WeightedList` of `ExplosionParticleInfo`, and
`ClientPacketListener.handleExplosion` hands that to
`ClientLevel.trackExplosionEffects` and the `ClientExplosionTracker`. Each
tick the tracker totals the block counts of every explosion it is holding,
caps the result at `ClientExplosionTracker.MAX_PARTICLES_PER_TICK`, and draws
that many weighted samples: a random direction, a cube-root-distributed
radius so the samples fill the volume evenly, rejected outright if the block
there is not air. Each survivor picks an `ExplosionParticleInfo` from the
weighted list for its type, its positional scaling and its speed multiplier.
Then the whole list is cleared, spent or not.
Everything else meets the engine's own two limits.
```mermaid
flowchart TD
A["a constructed particle reaches ParticleEngine.add"] --> B{"does Particle.getParticleLimit name a ParticleLimit"}
B -- "no limit, the overwhelming majority" --> D
B -- "SPORE_BLOSSOM, already at its count" --> X["dropped"]
B -- "SPORE_BLOSSOM, under its count" --> Q["queued in particlesToAdd until the next ParticleEngine.tick"]
Q --> D{"then ParticleGroup.add, for the particle's ParticleRenderType"}
D -- "at ParticleGroup.MAX_PARTICLES" --> X
D -- "past ParticleGroup.RESERVOIR_START" --> E["kept with probability equal to the square of the fraction of RESERVOIR_SIZE still free"]
D -- "below RESERVOIR_START" --> K["kept"]
E --> K
E --> X
K --> T["ticked from the next tick onward"]
```
The cap is per render type, not global, and the last quarter of it is
probabilistic: past `ParticleGroup.RESERVOIR_START` the acceptance
probability falls as the square of the free fraction, so the last few
hundred slots are very hard to fill and a particle storm degrades gradually
rather than hitting a wall. Since almost everything is a
`ParticleRenderType.SINGLE_QUADS` particle, that one group's budget is
effectively the whole budget; the other three groups have their own.
The per-type machinery beside it is the strangest thing in the system.
`ParticleLimit` is a full accounting apparatus — a key carried by the
particle, a count map in `ParticleEngine.trackedParticleCounts`, a decrement
when a group refuses a particle the limit had already accepted — and it has
**exactly one instance**, `ParticleLimit.SPORE_BLOSSOM`. The whole mechanism
exists to hold down one kind of falling petal, and
`ParticleEngine.hasSpaceInParticleLimit` is the only thing that ever reads the
map. The number on the debug screen is a different count:
`ParticleEngine.countParticles` walks the live render-type groups, and
`DebugEntryParticleRenderStats` is its only consumer.
## When does it move, and when is it drawn?
Admission is deferred by up to a tick. `ParticleEngine.add` puts the
particle in `ParticleEngine.particlesToAdd`, and `ParticleEngine.tick` —
which runs from `Minecraft.tick`, right after the ambient scatter, and only
while the level is running normally — does three things in a fixed order:
tick every existing group, then tick the emitters, then drain the queue into
groups. Because the drain is *last*, a particle never moves on the tick that
admits it, and a particle created during rendering is invisible until a tick
has run.
The emitters are the exception to almost everything. A `TrackingEmitter` is
a `NoRenderParticle` bolted to a moving entity, spending its short life
calling `ClientLevel.addParticle` on that entity's behalf — crits, the
enchanted-hit sparkles and the totem burst are all emitters. It lives in
`ParticleEngine.trackingEmitters` rather than in a `ParticleGroup`, so it is
never counted, never culled and never extracted. It is only ticked. So is a
`NoRenderParticleGroup`: `ParticleEngine.tick` iterates the whole group map,
but `ParticleEngine.extract` iterates `ParticleEngine.RENDER_ORDER`, which
lists three of the four render types, so a no-render group ticks its
contents forever and is never asked for a render state. Which is exactly
what a no-render particle is for.
Everything visible happens at extract time, once per frame, from
`LevelExtractor`. That is where the particle's previous and current
positions are lerped by the partial tick and made camera-relative before
being packed — interpolation is not a property of the particle, it is a
property of the extract. It is also where the culling happens, and the cull
is a point test: the particle's centre, not its quad, against a `Frustum`
whose origin has been slid a few blocks *behind* the camera so that
particles just past the near plane survive. Three of the four groups take a
`Frustum` and ignore it. Only `QuadParticleGroup` culls.
What survives is packed into `ParticlesRenderState`, one
`ParticleGroupRenderState` per group, with `QuadParticleRenderState` writing
twelve floats and two integers per particle into a per-layer
`QuadParticleRenderState.Storage` — a growable struct-of-arrays, reset and
reused each frame rather than reallocated — and
`QuadParticleFeatureRenderer` turning that into draws through
[Blaze3D](blaze3d.md). The layer decides which atlas is bound, and the
particle system draws from three of them, not one:
| the sprite lives on | opaque | translucent |
|---|---|---|
| the particle atlas | `SingleQuadParticle.Layer.OPAQUE` | `SingleQuadParticle.Layer.TRANSLUCENT` |
| the block atlas | `SingleQuadParticle.Layer.OPAQUE_TERRAIN` | `SingleQuadParticle.Layer.TRANSLUCENT_TERRAIN` |
| the item atlas | `SingleQuadParticle.Layer.OPAQUE_ITEMS` | `SingleQuadParticle.Layer.TRANSLUCENT_ITEMS` |
The six resolve to two pipelines, `RenderPipelines.OPAQUE_PARTICLE` and
`RenderPipelines.TRANSLUCENT_PARTICLE`. `SingleQuadParticle.Layer.bySprite`
picks a row and a column by reading whether the stitched sprite actually
contains translucent texels and which atlas the sprite lives on — and only
three particle classes ever ask it: `TerrainParticle`, `BlockMarker` and
`BreakingItemParticle`. Every other quad particle hard-codes opaque or
translucent on the particle atlas, so the four terrain and item layers exist
solely for block- and item-textured particles
([models and atlases](models-and-atlases.md)).
The last surprise is in the submission. **A quad particle group is submitted
twice per frame** — once into the solid bucket and once into the
after-terrain bucket, the same render state object entered twice, with the
feature renderer filtering each entry by whether the layer is translucent.
Opaque particles therefore draw before terrain-translucent geometry and
translucent ones after, and the per-particle packing still happens only
once. The dedicated particle render target exists only under the
transparency post chain, and even then only translucent particles use it
([visibility and the frame graph](visibility-and-the-frame-graph.md)). One
particle escapes this system entirely: `ItemPickupParticle` carries an
`EntityRenderState` and is submitted through `EntityRenderDispatcher`, so
the item flying into your inventory is
[a rendered entity](entity-rendering.md) wearing a particle's lifetime.
Two events empty the engine wholesale, and both have to.
`ParticleEngine.clearParticles` runs on a resource reload, because every
live particle holds a sprite reference into an atlas that no longer exists,
and `ParticleEngine.setLevel` clears the particles and the emitters both.
Particles are crash-report sites by design, though none of the reports is
raised on `ParticleEngine`: they come from `ParticleGroup.tickParticle`,
from `QuadParticleGroup.extractRenderState`, and from
`ClientLevel.doAddParticle` for a provider that throws while constructing.
The one malformed particle that does *not* crash is the one arriving over
the network — `ClientPacketListener.handleParticleEvent` logs it and drops
it. And `Particle.move` skips the collision sweep above a fixed speed, so a
particle thrown hard enough stops colliding with the world altogether, while
one that has been stopped by a collision once stays flagged as stopped.
> **For a 1.21-era reader.** `ParticleEngine` no longer owns providers,
> sprites, reloading or rendering — those became `ParticleResources` and the
> extract-plus-feature-renderer pipeline, and every provider and sprite-set
> member moved off the engine. *TextureSheetParticle* merged into
> `SingleQuadParticle`; the sheet-based `ParticleRenderType` constants became
> `SingleQuadParticle.Layer`, leaving `ParticleRenderType` a record with four
> values; *Particle.getRenderType* is `Particle.getGroup`;
> *Particle.getLightColor* is `Particle.getLightCoords`; *Particle.render*
> and *ParticleEngine.render* are an extract method plus
> `QuadParticleFeatureRenderer`; *ParticleEngine.destroy* and *crack* are on
> `ClientLevel`. And the name `ParticleGroup` was reused for something
> completely different: it is the per-render-type bucket now, and the limiter
> record it used to be is `ParticleLimit`.
## Where to look
`Block.spawnDestroyParticles` · `ClientLevel.addDestroyBlockEffect` ·
`ClientLevel.addBreakingBlockEffect` · `ClientLevel.doAddParticle`, the gate
everything else bypasses · `ClientLevel.animateTick` ·
`ClientExplosionTracker.tick` · `ParticleEngine.add` · `ParticleEngine.tick` ·
`ParticleEngine.extract` · `ParticleGroup.MAX_PARTICLES` · `ParticleLimit` ·
`ParticleResources.registerProviders` for the catalogue ·
`SingleQuadParticle.Layer.bySprite` · `QuadParticleFeatureRenderer`
---
*Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes `tools/verify_names.py`.*
---
# Post-processing
> Verified against **Minecraft 26.2** · Part XI · you press Escape and the world goes soft behind the menu — and the machine that softened it is the one that turns everything green when you spectate a creeper.
The blur behind the pause menu is not a GUI effect. It is a *post-processing
chain*: a file called *blur.json*, sitting in the jar beside *creeper.json*,
in the same format, loaded by the same loader, compiled into the same kind of
object and run by the same three classes. Six such files ship, covering the
pause menu, the three things it is unpleasant to spectate, the glow around a
spectral-arrowed mob and the option that sorts water against particles. A
resource pack can rewrite every one of them. What it cannot do is add a
seventh, because every chain this game will ever load is named by a constant
in Java, and there are only six of those.
## The cast
| class | what it decides | thread |
|---|---|---|
| `PostChainConfig` | what a chain is as data: its own targets, and its passes in order | parsed on a worker |
| `ShaderManager` | which chains exist, when they are compiled, and when they are thrown away | prepare on a worker, everything else on the render thread |
| `PostChain` | which targets a pass may name, and where a pass's output lives | Render thread |
| `PostPass` | one draw: a pipeline, its inputs as samplers, its uniforms as buffers | Render thread |
| `UniformValue` | the seven types a JSON-declared uniform may have, and how each is packed | Render thread |
| `LevelTargetBundle` | the seven names the level's targets answer to, and which set a chain may ask for | Render thread |
| `LevelRenderer` | the two chains that become passes in the world's own frame graph | Render thread |
| `GameRenderer` | the four chains that get a frame graph of their own, built and thrown away on the spot | Render thread |
## From a file on disk to a pass in a graph
```mermaid
flowchart TD
DISK["a JSON file under post_effect, plus the GLSL programs it names"]
PREP["ShaderManager.prepare — every chain parsed into a PostChainConfig, off the render thread"]
LOAD["first request: PostChain.load builds one PostPass per declared pass and precompiles each pipeline"]
CACHE["cached by id, and only by id, until the next resource reload"]
ADD["PostChain.addToFrame — external targets fetched from a bundle, internal ones declared in the graph"]
PASS["PostPass.addToFrame — one FramePass per pass, reading its inputs and read-writing its output"]
EXEC["FrameGraphBuilder.execute — each body binds a pipeline, binds the samplers, draws three vertices"]
OUT["the last pass lands on an imported target, which is what the rest of the frame goes on to use"]
DISK --> PREP --> LOAD --> CACHE --> ADD --> PASS --> EXEC --> OUT
```
Read it as **parse, compile, declare, draw**, and note that the halves live
in different phases of the client's life: the parse belongs to a resource
reload, and everything from *addToFrame* onward happens inside a frame, every
frame, for as long as the effect is on.
## What a chain declares, and the two kinds of name in it
A `PostChainConfig` is two things: a map of *targets* it wants for itself,
and a list of *passes* in the order they run. That is the entire schema.
Each pass names a vertex program and a fragment program by `Identifier`, an
output target, a list of inputs and a map of uniform blocks. An input is one
of exactly two shapes: `PostChainConfig.TargetInput` names another target and
may ask for its depth attachment rather than its colour, and
`PostChainConfig.TextureInput` names a PNG under *textures/effect* with its
dimensions. Both carry a *sampler name*, and two inputs on one pass sharing
one is rejected by the codec while the file is being parsed — so the chain
never reaches the config map at all, rather than failing later when something
asks for it — and that name is the contract with the GLSL —
`PostChain` appends *Sampler* to it when it builds the pass's
`BindGroupLayout`, so an input called *In* is the shader's *InSampler*.
The distinction that matters is between the two kinds of target name. A name
in the chain's own *targets* map is **internal**: it belongs to the chain, is
created fresh inside the frame graph at screen size unless the chain
overrides that, and is gone when the graph finishes. Any other name is
**external** and must be supplied by whoever runs the chain.
`PostChainConfig.Pass.referencedTargets` collects both kinds,
`PostChain.load` subtracts the internal ones, and what remains must be a
subset of the allowed set the caller passed in —
`LevelTargetBundle.MAIN_TARGETS`, `LevelTargetBundle.OUTLINE_TARGETS` or
`LevelTargetBundle.SORTING_TARGETS`, one, two or six names. A chain naming a
target its caller did not offer does not load at all. An internal target may
also be declared *persistent*, in which case `PostChain` allocates it once,
keeps it in `PostChain.persistentTargets` and imports it rather than creating
it, so a pass can read what it wrote last frame. None of the six asks for
one.
## Loaded off-thread, compiled inside a frame
`ShaderManager` is a `SimplePreparableReloadListener`, so its two halves run
in two places. `ShaderManager.prepare` runs on the reload's worker executor:
it reads every GLSL source under *shaders*, resolves each source's
*moj_import* directives through `GlslPreprocessor`, and parses every JSON
under *post_effect* with `PostChainConfig.CODEC` into an immutable map — a
malformed chain is logged and simply absent from it. `ShaderManager.apply`
then runs on the render thread, clears the device's pipeline cache,
precompiles every statically registered pipeline and, only if all of them
succeeded, swaps in a new `ShaderManager.CompilationCache` and closes the
old.
Post chains are not in that precompiled set. They are built lazily, the first
time somebody asks: `ShaderManager.getPostChain` consults the cache, and on a
miss `PostChain.load` walks the config, builds a `RenderPipeline` per pass
from `RenderPipelines.POST_PROCESSING_SNIPPET`, names it *chain id* slash
*pass index*, and precompiles it there and then. **The first frame you
spectate a creeper compiles two shader programs in the middle of itself.** A
failure throws `ShaderManager.CompilationException`, which
`ShaderManager.getPostChain` logs, caches as a permanent absence so the next
frame does not try again, and reports to
`Minecraft.triggerResourcePackRecovery` — the path that disables a resource
pack that broke the game. Closing the old cache, meanwhile, closes every
`PostChain` in it, destroying its persistent targets and freeing each
`PostPass`'s uniform buffers: a reload does not rebuild the chains, it
forgets them.
## A pass is three vertices, and its uniforms are written once, at load
`PostPass.addToFrame` adds one `FrameGraphBuilder.addPass` named after its
pipeline's location. Every target input becomes a `FramePass.reads`, the
output a `FramePass.readsAndWrites`, and the body goes in through
`FramePass.executes` — nothing is drawn while the graph is built.
When the body eventually runs, it sets an orthographic projection —
`ShaderManager` keeps one `Projection` and one `ProjectionMatrixBuffer` and
lends them to every chain — writes the output size and each input's size into
a `MappableRingBuffer` as the *SamplerInfo* block, then opens a `RenderPass`,
binds pipeline, default uniforms, custom uniform blocks and inputs, and draws.
**Three** — vertices in every post-processing draw, in all twenty-six passes
the six chains declare (`PostPass.addToFrame`). There is no quad and no
vertex buffer: the shared vertex program builds one oversized triangle out of
the vertex index alone, and the fragment shader sees the whole screen.
The uniforms are stranger than they look. A `UniformValue` has seven types —
int, ivec3, float, vec2, vec3, vec4 and a 4×4 matrix — and a block is a list
of them under a name. `PostPass`'s constructor sizes the block with
`UniformValue.addSize`, packs it with `UniformValue.writeTo` and uploads it
to a `GpuBuffer` **once**, at load, never to be written again. The per-entry
*name* in the JSON is read by no codec — only the type and the value are, and
members match the GLSL block positionally. Only the block's own name, the key
in the uniforms map, has to match anything.
That is why the blur's radius is not one of them. *blur.json* declares a
radius of zero, and *box_blur* treats zero as "ask elsewhere": it falls back
to a member of the *Globals* block, which `GlobalSettingsUniform.update`
rewrites every frame from `OptionsRenderState.menuBackgroundBlurriness` and
`RenderSystem.bindDefaultUniforms` binds to every post pass. **Anything a
chain needs to vary per frame cannot be a chain uniform.** It has to come in
through the global block, whose seven members are fixed in Java.
## The six chains
| chain | who declares it | what it reads | what a player sees |
|---|---|---|---|
| *blur* | `GameRenderer.processBlurEffect`, called from inside `GuiRenderer.draw` | the main target and one internal target, six passes alternating between them | the world going soft behind a pause or options screen |
| *creeper* | `GameRenderer.render`, when the camera entity is a `Creeper` | the main target, and one internal target it bounces through | luminance collapsed into the green channel, then posterised and mosaicked |
| *spider* | `GameRenderer.render`, when it is a `Spider` | the main target and four internal targets | the view repeated through several skewed, blurred, red-tinted lobes |
| *invert* | `GameRenderer.render`, when it is an `EnderMan` | the main target, and one internal target it bounces through | colours inverted, four fifths of the way |
| *entity_outline* | `LevelRenderer.render`, when anything submitted an outline this frame | the entity-outline target — and never the main one | the coloured halo around a glowing mob |
| *transparency* | `LevelRenderer.render`, when improved transparency is on | **six** of the caller's targets, colour **and** depth, and just one internal target of its own | water, particles, clouds and rain layered in the right order |
Only one of those is a screen effect. *blur* runs over whatever is currently
on the main target, world and GUI alike, because `GuiRenderer.draw` splits
the GUI in two and runs it in the gap: everything up to the blur boundary is
drawn, the depth buffer is cleared, the chain runs, and the rest of the GUI
is drawn crisp on top. `Screen.extractBlurredBackground` sets that boundary
through `GuiGraphicsExtractor.blurBeforeThisStratum` when
`Options.getMenuBackgroundBlurriness` is at least one, which is why the
slider at zero costs nothing and why the menu's darkening tint is not blurred.
Three are world effects: *creeper*, *spider* and *invert* run at the end of
`GameRenderer.render`'s world block, after the level and before any GUI, so
they warp the world and leave the HUD alone. The last two are neither.
*entity_outline* never touches the main target — it reads and writes an
offscreen glow buffer that something else composites — and *transparency* is
not a filter over a picture at all but the step that *makes* the picture,
merging six separately rendered layers by depth.
`GameRenderState.useShaderTransparency` gates it on
`OptionsRenderState.improvedTransparency` and on not being in panoramic mode,
and when it is off `LevelRenderer` never creates those five targets, so
everything draws into the main one and sorts by luck.
## The outline chain, end to end
Take the one whose whole life is visible. A mob is glowing, so something
submits it to `SubmitNodeCollection.outline`, and
`FeatureRenderDispatcher.PreparedFrame.executeOutline` draws it — flat, in
the glow colour — into the entity-outline target during the main pass. That
target is a `TextureTarget` `LevelRenderer` owns across frames.
```mermaid
sequenceDiagram
participant LR as LevelRenderer
participant ShadM as ShaderManager
participant PChain as PostChain
participant PPass as PostPass
participant FGB as FrameGraphBuilder
participant GR as GameRenderer
LR->>FGB: importExternal — the entity outline target, which the main pass has just drawn into
LR->>ShadM: getPostChain for entity_outline, allowing main and entity_outline
ShadM-->>LR: the cached chain, or four freshly compiled pipelines
Note over LR,ShadM: the lookup runs every level frame — only addToFrame is skipped when hasAnyOutline is false
LR->>PChain: addToFrame with the screen size and the level's target bundle
PChain->>FGB: createInternal — the chain's own swap target, at screen size
PChain->>PPass: addToFrame, four times, in declared order
PPass->>FGB: addPass, reads the input, reads and writes the output
Note over PPass,FGB: sobel to swap, blur across, blur down, blit back
PChain-->>LR: the bundle's outline handle replaced with the last one written
FGB->>FGB: execute — bodies run in dependency order, three vertices each
GR->>LR: doEntityOutline, after the whole graph has finished
Note over GR,LR: blitAndBlendToTexture composites the glow onto the main target
```
The first pass is an edge detector and what it detects edges in is **alpha**,
not colour: the target is transparent everywhere nothing was submitted, so
the boundary of the silhouette is the boundary of the halo. The next two blur
that outline across and then down, and the last blits it back where it
started — the chain ends on the same external target its first pass read, and
the internal *swap* target is what makes that legal, since no pass ever reads
and writes one buffer at once. Then it stops, and the compositing is somebody
else's job. `LevelRenderer.doEntityOutline` runs after
`GameRenderer.renderLevel` returns, outside the graph entirely, and blends
the glow onto the main target with a pipeline of its own. This is the one
chain whose result is invisible until a separate blit puts it on screen.
## Two doors into the GPU, and one of them is deprecated
`PostChain` has two entry points, and which one a chain goes through is the
whole difference between the two halves of this page.
`PostChain.addToFrame` takes a `FrameGraphBuilder` somebody else already
started and appends to it. That is what `LevelRenderer` does with the outline
and transparency chains: they are not a second rendering path but more passes
in [the graph it was building anyway](visibility-and-the-frame-graph.md),
ordered by their declared reads and writes alongside the sky, the terrain,
the clouds and the weather. They survive that graph's culling for the
ordinary reason — their last pass writes a target imported from outside — and
they appear in the profiler under their pipeline names, because the level's
graph is executed with an inspector that pushes a zone per pass.
`PostChain.process` is the other door, and it is marked deprecated. It builds
a `FrameGraphBuilder` of its own, imports one target as *main*, adds the
chain, executes it and throws it away. Both its callers are in
`GameRenderer`: the camera-entity effect at the end of the world block, and
`GameRenderer.processBlurEffect` in the middle of the GUI.
Neither passes an inspector, so **the blur and the spectator shaders never
get a slice of the F3 pie chart to themselves**: their cost is folded into
whichever enclosing zone they ran under, *render → world* for the spectator
effects and *render → gui → draw* for the blur, and no name in the chart
tells you a post chain is what you are looking at. The graphs are throwaway but the
memory is not: both doors take internal targets from the one
`CrossFrameResourcePool`, which holds a released target for three frames in
case something asks again for that size and format.
## Questions players ask
**Can a resource pack add a post effect?** It can add the *file*, the game
will parse it, and nothing will ever run it. `ShaderManager.getPostChain` is
called with six ids: three constants in `LevelRenderer` and `GameRenderer`,
three built inside `GameRenderer.checkEntityPostEffect` from the camera
entity's class. No registry, no data-driven selection, no command. What a
pack *can* do is replace any of the six, with as many passes as it likes,
running fragment programs it also ships under *shaders/post* — a real and
underused amount of rope.
**Why does the creeper effect vanish when I press F5?** Because third person
clears it, and the perspective key does it directly.
`GameRenderer.checkEntityPostEffect` switches on the camera entity's class,
sets `GameRenderer.postEffectId` for a creeper, a spider or an enderman, and
clears it for anything else — including for no entity at all. The perspective
key calls it straight out of `Minecraft.handleKeybinds`;
`Minecraft.setCameraEntity` is the other door into the same method, for when
what you are spectating changes rather than how. F4 (`Options.keyToggleSpectatorShaderEffects`) is a separate
switch, flipping `GameRenderer.effectActive` without forgetting which chain
was chosen, and the F3 screen names the survivor through
`DebugEntryPostEffect`.
**What happens to a chain when the window is resized?** Nothing. A
`PostChain` has no size of its own: the dimensions arrive as arguments to
`PostChain.addToFrame` every frame, and internal targets are described fresh
from them each time. `GameRenderer.resize` clears the resource pool so the
old targets are not handed back, and `LevelRenderer.resize` resizes the
entity-outline target it owns. The compiled pipelines never mention a
resolution, so they are untouched.
> **For a 1.21-era reader.** *ShaderInstance*, *EffectInstance*, *Effect* and
> *AbstractUniform* are gone, and `Uniform` survives only as an OpenGL-backend
> detail no post chain ever names: a post pass's uniforms are
> `UniformValue` records packed into a `GpuBuffer` with `Std140Builder`, and
> everything else is bound by `RenderSystem.bindDefaultUniforms`.
> *PostChain.process* still exists, but it is deprecated and both its callers
> build a throwaway frame graph — `PostChain.addToFrame` is the real one.
> *PostChain.resize*, *PostChain.getTempTarget* and `PostChain`'s whole
> bookkeeping of named render targets are gone, because the frame graph
> allocates them now. And *Fabulous* is no longer a mode anything reads: it
> survives as one of four `GraphicsPreset` values, but a preset only *writes*
> the individual options and is then forgotten, so what actually gates the
> transparency chain is `Options.improvedTransparency` — which
> `GraphicsPreset.FABULOUS` sets true everywhere except macOS.
## Where to look
`PostChainConfig` first — the record *is* the file format. Then
`ShaderManager.prepare` and `ShaderManager.getPostChain` for where a chain
comes from and how long it lives, `PostChain.load` for the validation that
decides whether it loads at all, and `PostChain.addToFrame` for the only
thing a chain does. `PostPass` is one pass and one draw. For the callers,
`LevelRenderer.render` declares two chains into the world's frame graph,
`GameRenderer.render` and `GameRenderer.processBlurEffect` run the other four
through the deprecated door, and `LevelTargetBundle` names what any may ask.
---
*Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes `tools/verify_names.py`.*
---
# XII · World generation
> Verified against **Minecraft 26.2** · Part XII · a world made out of one number per point, decided before anything about it exists, and reproducible from a seed and a data pack alone.
Everything in this part is determined by two things: the world seed, and the
data packs enabled when the world is opened — `WorldLoader.load` re-reads the
worldgen registries out of the current packs every time, so only the seed and
the dimension list are actually saved. No entity, no player and no tick has
any say in it. Give the same seed and the same packs to two copies of the game
and they will agree, block for block, forever — which is the property
speedrunners, seed-hunting sites and structure finders all depend on. It holds
not because nothing here reads the world — the decoration step reads block
states, heights and the carving mask through `PlacementContext`, and the
surface pass reads its neighbours' biomes — but because **everything it reads
is itself a function of that seed and those packs**. There is one deliberate
exception, and it is the only place generation reads something the current
seed did not produce:
[the boundary with chunks an older version generated](blending.md).
What a player recognises the part by is the seam: the flat shelf of ground
under a village that was not there before, the cave that is flooded the
moment you break into it, the desert that becomes a jungle along a ragged
line, the tree that grows up through another tree.
Counting `world/level/levelgen` and `world/level/biome` together — one class
per file, one line per line of decompiled source, the way
[the atlas](../../maps/README.md) counts everything else — that is **451
classes and 45,700 lines**. This part does not cover all of it;
[what this book skips](../anatomy/what-this-book-skips.md) says which parts
are declined and why.
## The shape of the part
Part XII is **a substrate, a pipeline, and a wing** — and the wing runs
first while being taught last. Part IV owns the conveyor that runs the
statuses ([the chunk generation pipeline](../world/chunk-generation-pipeline.md));
this part is the cargo of seven of them.
```mermaid
flowchart TB
CW["10 · Creating a world — where the seed and the packs were chosen, before any of this"]
CW -.-> DF
DF["1 · Density functions — the substrate, and the only page that is not a chunk step"]
DF --> SS
subgraph SS["STRUCTURE_STARTS, STRUCTURE_REFERENCES — first in the game, last in the lectures"]
direction LR
S6["7 · Structure placement"] --> S7["8 · Jigsaw and templates"]
S6 --> S8["9 · Hand-built structures"]
end
SS --> BI
subgraph BI["BIOMES"]
L2["2 · Biomes"]
end
BI --> NO
subgraph NO["NOISE, SURFACE, CARVERS"]
L3["3 · Terrain"] --- L3b["4 · Blending — the one place generation reads an older version's work"]
end
NO --> FE
subgraph FE["FEATURES"]
direction LR
L4["5 · Features and placement"] --> L5["6 · Trees"]
end
```
Read the solid chain as the order the game runs, and the numbers as the order
to watch. They disagree on purpose — and the dashed arrow disagrees most, since
the world was created before a single chunk was, and its page is told tenth. A structure is *decided* two statuses
before the biomes and the terrain it will stand in exist — it asks the
generator directly for the numbers it needs rather than reading a chunk — and
it writes its blocks three statuses after the terrain is cut, inside the
decoration step. Putting
the three structure pages last keeps that whole arc in one place, at the cost
of one forward topic: three of the six pages before them reach for the
beardifier — two by link, one by name — before the page that owns it.
The substrate arrow means *is made of*, not *happens before*. Four consumers,
spread over three of the nine pages below it, take the density graph — the
biome sampler is six of its functions, the aquifer is four more, ore veins are
three, and the beardifier is a node the chunk splices in. The decoration and structure
packages never mention `DensityFunction` at all; they reach the substrate only
through the beardifier and the heights the generator hands them.
The tenth page is the part's origin, told last. The object every other page
reads — the seed and the map of dimensions, each a generator built out of
noise settings, biome sources and placed features — is a tree of everything
the first nine explain, so the page that says where it came from is a
closer with nine satisfied references rather than an opener with nine
forward ones.
## Before you start
[The chunk generation pipeline](../world/chunk-generation-pipeline.md) from
Part IV, and not optionally. It is the only page that says *when* any of this
runs, what the twelve chunk statuses are, how the dependency pyramid keeps
neighbours out of each other's way, and which thread each step is on. Eight
of the ten pages here name a status, and two of them open on one.
[Chunk anatomy](../world/chunk-anatomy.md), for what is being written into —
sections, the two paletted containers, and the heightmaps the terrain steps
maintain by hand.
[Environment attributes and timelines](../world/environment-attributes-and-timelines.md),
also from Part IV, for lecture two: `Biome` has been hollowed out, and the sky,
the fog, the music and a dozen gameplay switches now reach the player through a
stack of modifier layers in which the biome is one layer rather than the owner.
[Codecs, NBT and JSON](../foundations/codecs-nbt-json.md) and
[identifiers and registries](../foundations/identifiers-and-registries.md)
from Part II, because worldgen is the most thoroughly data-driven system in
the game and this part assumes the dynamic-registry model rather than
re-teaching it. [The data-driven type pattern](../foundations/data-driven-types.md)
lists fifty-six instances of the pattern, and twenty-six of them are owned by
a page in this part.
## Watch in this order
1. [Density functions](density-functions.md) — the substrate, and the
abstract one. Three forms of one graph, two rewrites, and six caches that
cache nothing until something else installs them.
2. [Biomes](biomes.md) — a nearest-neighbour search in seven dimensions, one
of which is not sampled from the world at all, and the two biome borders
the game keeps a couple of blocks apart.
3. [Terrain](terrain.md) — noise, surface and carvers. Seven hundred and
sixty-eight cells filled from their corners, and a cave whose water was
decided before the cave was.
4. [Blending at the old-chunk border](blending.md) — the one place world
generation reads the world. Sixteen columns an old chunk re-measures out
of its own blocks, and a seam where the terrain splines are switched
off entirely.
5. [Features and placement](features-and-placement.md) — decoration as a
stream of positions folded through filters, in an order the whole
dimension agreed on before any chunk existed.
6. [Trees](trees.md) — one algorithm with five slots in it, and the
clearance scan that runs after the crown has been sized.
7. [Structure placement](structure-placement.md) — the part's policy page.
A lottery that never looks at the world, an absence stored as a hole, and
a command that generates chunks to answer a question.
8. [Jigsaw and templates](jigsaw-and-templates.md) — how a village assembles
itself, and how any piece becomes blocks. A growth limit that works by
taking the right pool away.
9. [Hand-built structures](hand-built-structures.md) — the older assembler,
which is still most of the code. Four families of piece grammar, and the
one structure that throws itself away and starts again.
10. [Creating a world](creating-a-world.md) — where the seed and the data
packs came from. A screen that is a running data-pack load with widgets
on it, settings carried across a reload by being serialised to JSON, and
a Cancel button that does not undo.
Two comes before three: `ChunkStatus.BIOMES` is the parent of
`ChunkStatus.NOISE`, it is where the terrain's own workspace is built, and the
surface pass reads the biome. Four needs both, and reaches one status forward:
the last of its five consumers is a border tick run at `ChunkStatus.FEATURES`.
Seven
comes before eight and nine, which are alternatives to each other rather
than a sequence. Ten can be watched first by a viewer who wants the origin
before the machinery, at the cost of nine forward references.
## Reference this part uses
[Density-function nodes](../../reference/density-function-nodes.md) is the
catalogue behind lecture one: all thirty-four node types in registration
order, what each takes, and what the per-chunk rewrite turns it into.
[Registries](../../reference/registries.md) for the fourteen *worldgen/*
registries a data pack writes into, and
[the data-driven type pattern](../foundations/data-driven-types.md) for why
some of them are frozen at startup and some reload with the world.
[Diagram lanes](../../reference/lanes.md) for the abbreviations these figures
use. [Naming drift](../../reference/naming-drift.md) has twelve rows for
this part, all of them re-derived and none of them changed since pass 2.
[Level data and rules](../../reference/level-data-and-rules.md) for which
file the seed, the dimensions and the rules each end up in, which lecture
nine links to rather than restates. And
[the glossary](../../reference/glossary.md) for *density function*,
*aquifer*, *beardifier*, *NoiseChunk*, *blending data*, *old chunk*,
*PlacedFeature*, *jigsaw*, *world preset* and *world gen settings*.
Where the part stops: what happens to a chunk *after* `ChunkStatus.FEATURES`
— lighting, spawning, promotion to a live chunk, and being saved or sent — is
Part IV. What the client is told about any of it is Part IX, and the answer
is the finished chunk with none of the machinery: block states and the biome
palette in one buffer, the heightmaps, and the block entities.
---
*Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes `tools/verify_names.py`.*
---
# Density functions
> Verified against **Minecraft 26.2** · Part XII · One number out of one point: how a JSON file becomes "stone or air", and why the graph you can read in the registry is never the graph that runs.
Open the overworld's *depth* function in a data pack and you can read the
shape of the world out of it in eleven lines: a gradient down the Y axis,
added to *overworld/offset* — which is fifteen hundred lines of continents and
erosion wrapped in things called *flat_cache* and *cache_2d*. Both files are
honest and readable. And the
object it parses into is never sampled by anything: it is rewritten once per
dimension, and again per chunk, and only the third form ever computes a
number for a block. **The caches named in that file cache nothing.** They are
requests, and something else grants them.
Terrain in 26.2 is a scalar field: a function from a block position to a
*double*, where the convention is that zero is the surface and **positive
means solid**. `DensityFunction` is the interface, `DensityFunctions` is the
library of thirty-four node types you build one out of, and a data pack
assembles them as JSON. This page is the three forms that one graph takes and
the machinery that moves between them; the node types themselves are
[the node catalogue](../../reference/density-function-nodes.md), and the terrain
steps that sample the result are [terrain](terrain.md).
Nothing here touches a block, and nothing here differs between two worlds
built from the same seed and the same packs. This is the layer that *the
seed* actually means.
## The cast
| class | what it owns | its clock |
|---|---|---|
| `DensityFunction` | one method that matters — `DensityFunction.compute`, taking a position and returning a double — plus `DensityFunction.fillArray` for the batch form and the two static bounds | — |
| `DensityFunctions` | the node library, and the codecs that dispatch a JSON *type* to one of them | data-pack load |
| `DensityFunction.NoiseHolder` | the seeding seam: a noise-parameters holder plus a `NormalNoise` that is **null as parsed** | filled once per dimension |
| `DensityFunctions.Marker` | a cache *request*, wrapping one function and computing nothing itself | replaced once per chunk |
| `NoiseRouter` | the fifteen functions a generator asks for, as one record; `NoiseRouter.mapAll` rebuilds all fifteen at once | — |
| `NoiseRouterData` | vanilla's graph, written in Java and *emitted* as the JSON that ships | build time |
| `RandomState` | the per-dimension instantiation: the seeded router, the climate sampler, the noise memo, the `SurfaceSystem` | once per level |
| `NoiseChunk` | the per-chunk instantiation, and simultaneously the sample position *and* the loop driver — it implements `DensityFunction.FunctionContext` and `DensityFunction.ContextProvider` both | once per chunk |
Underneath all of it is the *synth* package: `NormalNoise` (two `PerlinNoise`
stacks summed and normalised), `PerlinNoise` (octaves of `ImprovedNoise`),
`ImprovedNoise` (one octave of 3-D Perlin over a permutation table),
`BlendedNoise` (the pre-1.18 terrain noise, itself a
`DensityFunction.SimpleFunction`), plus `SimplexNoise` and
`PerlinSimplexNoise`. `Noises` holds the sixty-three keys for the parameter
sets and `Noises.instantiate` builds one from a positional factory.
## Three forms of one graph
```mermaid
flowchart TB
subgraph AA["as parsed — shared by every world, unseeded and cacheless"]
direction TB
A1["Ap2, add"] --> A2["YClampedGradient"]
A1 --> A3["HolderHolder — a pointer at another registry entry"]
A3 --> A4["Marker, flat cache — delegates, caches nothing"]
A4 --> A5["NoiseHolder — noise is null, answers 0.0"]
end
subgraph BB["as seeded — RandomState.router, one per dimension"]
direction TB
B1["Ap2, add"] --> B2["YClampedGradient"]
B1 --> B3["HolderHolder — still a pointer"]
B3 --> B4["Marker — still delegating"]
B4 --> B5["NoiseHolder — a real NormalNoise"]
end
subgraph CC["as wrapped — one per chunk, and the only form that runs"]
direction TB
C1["Ap2, add"] --> C2["YClampedGradient"]
C1 --> C3["the pointed-at graph itself"]
C3 --> C4["NoiseChunk.FlatCache — a real array, filled"]
C4 --> C5["NoiseHolder — the same NormalNoise"]
end
AA -- "RandomState.create — one visitor over the whole router" --> BB
BB -- "NoiseChunk.forChunk — a second visitor" --> CC
```
Both arrows are `DensityFunction.mapAll`, which is the only interesting
operation in this system: it applies a `DensityFunction.Visitor` bottom-up
over a whole graph, rebuilding each node's children through
`DensityFunction.mapChildren`. A visitor has two channels —
`DensityFunction.Visitor.apply` for nodes and
`DensityFunction.Visitor.visitNoise` for noise leaves — and everything below
is one or other channel doing its job.
## Parse: one file, one graph
Every file under a pack's *worldgen/density_function* directory becomes one
registry entry through `DensityFunctions.DIRECT_CODEC`, which is an *either*:
a bare number in the JSON is silently a `DensityFunctions.Constant`, and
anything else dispatches on its type id through
`BuiltInRegistries.DENSITY_FUNCTION_TYPE`. Every *child* slot instead uses
`DensityFunction.CODEC`, a `RegistryFileCodec`, so a string id, an inline
object and a bare number are interchangeable everywhere a function is
expected. A string becomes a `DensityFunctions.HolderHolder` — a live pointer
at another entry, which is how a graph references a graph.
Two things happen during construction that a reader of the JSON cannot see.
Constructors **fold**: `DensityFunctions.TwoArgumentSimpleFunction.create`
collapses an *add* or a *mul* with one constant argument into a
`DensityFunctions.MulOrAdd`, so a node type in the file is not necessarily
the class in memory. And the **bounds propagate**:
`DensityFunction.minValue` and `DensityFunction.maxValue` are computed as
each node is built and pushed upward, which makes them a static analysis of
the data pack — one that talks back, because building a *min* or a *max* over
two ranges that cannot possibly overlap logs a warning naming both arguments.
`DensityFunctions.HolderHolder` is the one node that cannot be written back
out: it is not registered, and asking it for its codec throws. It exists only
in memory, and re-serialising a graph goes through `DensityFunction.CODEC`,
which recognises it and emits the id string it came from.
## Seed: once per dimension
`RandomState.create` forks the seed into named positional factories —
`RandomState.aquiferRandom`, `RandomState.oreRandom`, and whatever else asks
through `RandomState.getOrCreateRandomFactory` — and then runs
`NoiseRouter.mapAll` with a wiring visitor over all fifteen router fields.
The visitor fills each `DensityFunction.NoiseHolder` with a real
`NormalNoise` from `RandomState.getOrCreateNoise`, rebuilds `BlendedNoise`
with a new random source, and replaces the end-islands node with a reseeded
one. Everything else it passes through untouched: the markers and the
pointers survive this rewrite intact.
Two details in there matter later. The **two nether climate noises are
special-cased** into a legacy construction over a `LegacyRandomSource` and
therefore skip the memo entirely. And the visitor keeps its own memo of what
it has already rewritten, so a subgraph referenced from five router fields is
rewritten **once and stays one object** — which is precisely what makes the
per-chunk caching in the next step pay, because five router fields that share
a subgraph will share its cache.
Then a *second*, different visitor runs, and it strips machinery rather than
installing it: it unwraps every `DensityFunctions.HolderHolder` to its value
and every `DensityFunctions.Marker` to its wrapped function, over the six
climate functions only, to build `RandomState.sampler`. That is the
`Climate.Sampler` [biomes](biomes.md) reads — a copy of the climate half of
the graph with no caches and no indirection in it at all.
> **For a 1.21-era reader.** Three of the six climate functions have two
> names. `NoiseRouter` calls them *vegetation*, *ridges* and *continents*;
> `Climate.Sampler` calls the same three *humidity*, *weirdness* and
> *continentalness*. Neither vocabulary is wrong and both ship.
## Wrap: once per chunk
`NoiseChunk.forChunk` builds the workspace and runs `NoiseRouter.mapAll` a
second time, and `NoiseChunk.wrapNew` is the switch that matters. A
`DensityFunctions.Marker` becomes the real cache its type names. A
`DensityFunctions.HolderHolder` is resolved to its value once instead of on
every sample. And three singletons are swapped **by object identity**:
`DensityFunctions.BlendAlpha` and `DensityFunctions.BlendOffset` become two
flat caches the `NoiseChunk` constructor has *already filled*, before any
router mapping ran, and `DensityFunctions.BeardifierMarker` becomes this
chunk's `Beardifier`. If the level's [`Blender`](blending.md) is empty, the
blend nodes
survive as the constants they are and a *blend_density* marker is replaced by
its own child, erasing the node.
Afterwards `NoiseChunk` adds the beardifier marker to the router's final
density itself, wraps the sum in one more cache-all-in-cell, and maps
*that* — which is why `NoiseChunk.fullNoiseDensity` is not any node the data
pack wrote ([terrain](terrain.md) walks the cells that sample it).
The rewrite is reversible, for the caches: all six implement
`DensityFunctions.MarkerOrMarked`, so they still report their original marker
type and would serialise back to the id they came from. The blend nodes are
not reversible — `DensityFunctions.BlendAlpha` comes back wrapped in a flat
cache it did not start inside.
## The six caches, and the three a single point may use
This is the payoff of the whole arrangement, and the split inside it is not
the one the names suggest.
`NoiseChunk.NoiseInterpolator`, `NoiseChunk.CacheAllInCell` and
`NoiseChunk.CacheOnce` each begin by checking that the sampling context *is*
the `NoiseChunk` itself, and delegate to the wrapped function when it is not.
They are meaningful only inside the cell loop — the interpolator throws
outright if sampled while the chunk is not interpolating, and the other two
key on a cell index or on an interpolation counter, neither of which means
anything outside it.
`NoiseChunk.FlatCache` and `NoiseChunk.Cache2D` do the opposite: they key on
**position alone** and will happily answer a
`DensityFunction.SinglePointContext`. `NoiseChunk.Cache2D` could not do
otherwise — it is the one nested class here that is *static*, so it holds no
reference to the chunk to compare against. And that is exactly what makes
`NoiseChunk.cachedClimateSampler` and `NoiseChunk.preliminarySurfaceLevel`
cheap: both sample the wrapped graph with single-point contexts, and both hit
the two-dimensional caches every time. **A single-point sample is not a cache
bypass — it is a bypass of the three-dimensional caches only.**
The resolutions are worth saying once. The *interpolated* marker sits on the
expensive three-dimensional terms and is evaluated at cell corners.
*flat_cache* is **not** exact per column: it fills its array by sampling at
the quart corner with y = 0, so one value serves a four-by-four block group.
Only *cache_2d* is genuinely per column, and vanilla is not consistent about
where it puts one: of the twenty-four in the shipped files, eleven sit inside
a flat cache and thirteen do not — and no *noise_settings* file contains a
*flat_cache* at all.
## Questions players ask
**Does editing a *cache_once* in a data pack do anything?** Yes, but not what
it says. It is a request that `NoiseChunk.wrapNew` install a cache in that
slot; the node itself computes nothing and delegates. Worldgen performance
lives in a switch statement, not in the data.
**Is the readable graph ever actually sampled?** Once, and you can watch it
happen. `NoiseBasedChunkGenerator.addDebugScreenInfo` samples
`RandomState.router` with single-point contexts to fill the F3 noise
readout — the one production path that runs the graph with every marker a
no-op, and the reason the once-rewritten form has to stay safe to sample from
anywhere.
**Why do two identical-looking subgraphs end up sharing one cache?** Because
both visitors key their memo on the node *itself*, and the nodes are records,
so two separately-parsed but structurally identical subgraphs are merged into
one object. `DensityFunctions.Spline` makes this explicit: it has a
hand-written equality that compares only the spline and ignores the derived
sampler beside it, so two identical splines from two different files become
one node with one cache.
**Are the bounds trustworthy?** Mostly, with exceptions worth knowing, and
they run in both directions. `DensityFunctions.Marker` passes its child's
bounds through *except* when its type is *blend_density*, where it reports
infinities — the one place a marker is not transparent. The two blend leaves
under-report before the wrap and not after: `DensityFunctions.BlendAlpha` and
`DensityFunctions.BlendOffset` parse as the constants 1 and 0, and become
`NoiseChunk.BlendAlpha` and `NoiseChunk.BlendOffset` with a range of zero to
one and an infinite one — so a fold decided above a blend node was decided on
the wrong range. `DensityFunctions.HolderHolder` reports infinities while
its holder is unbound, which is what lets forward references parse at all.
And `DensityFunction.NoiseHolder` answers a maximum of 2.0 while its noise is
still null, where every one of the sixty-three shipped noise definitions comes
out between 2.57 and 7.32 once seeded — so the freshly parsed graph reports
noise bounds that are too **narrow**, and seeding widens them.
**Is there anything in here that does not work?** Three things.
`DensityFunctions.TransformerWithContext` is the shape a position-dependent
transform would take and has no implementation in 26.2. `Density` writes down
the three conventions this whole system rests on — surface at zero, and the
two values a node reaches for when it wants to end an argument — as constants
that **nothing anywhere reads**; the routers spell the same numbers as
literals. And `DensityFunctions.shift`, the three-dimensional domain warp,
has no callers and appears in no shipped file: vanilla uses only the two
two-dimensional warps, and those two read the *same* noise parameters with
their axes swapped, behind a registry id that is called *offset* rather than
*shift*.
**Which of these nodes reads the world?** Two, and both do it the same way —
by harvesting what they need from neighbouring chunks at construction and
never touching a chunk afterwards. The three blend nodes reach `BlendingData`
([blending at the old-chunk border](blending.md)); the *beardifier* marker
becomes a `Beardifier`, whose `Beardifier.forStructuresInChunk` reads the
structure references out of chunks at `ChunkStatus.STRUCTURE_REFERENCES`
([structure placement](structure-placement.md)). Everything else — every
noise, spline, selector and cache in the catalogue — reads no blocks, no
chunks and no level, which is why the whole system can run on a worldgen
worker with nothing loaded.
## Where to look
`DensityFunction.compute` · `DensityFunction.mapAll` ·
`DensityFunction.Visitor` · `DensityFunction.NoiseHolder` ·
`DensityFunctions.DIRECT_CODEC` · `DensityFunctions.Marker` ·
`DensityFunctions.MarkerOrMarked` · `DensityFunctions.HolderHolder` ·
`DensityFunctions.TwoArgumentSimpleFunction` · `NoiseRouter` ·
`NoiseRouterData.overworld` · `RandomState.create` ·
`RandomState.getOrCreateNoise` · `NoiseChunk.forChunk` ·
`NoiseChunk.wrapNew` · `NoiseChunk.NoiseInterpolator` ·
`NoiseChunk.Cache2D` · `NoiseChunk.cachedClimateSampler` ·
`Climate.Sampler` · `NormalNoise.create` · `ImprovedNoise.noise` ·
`Noises.instantiate` · `Density`
---
*Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes `tools/verify_names.py`.*
---
# Biomes
> Verified against **Minecraft 26.2** · Part XII · A point in the world gets a biome: six numbers quantised to integers, a nearest-neighbour search in seven dimensions, and the two different answers the game keeps for the same block.
Walk out of a desert into a jungle and watch the ground. The grass changes
colour at one line. The fog and the sky change at a *different* line, a
couple of blocks away. Neither is a bug and neither is a rendering artefact:
the game genuinely stores one biome per four-by-four-by-four volume and then
answers "which biome is this block in?" **two different ways**, one jittered
and one not, and different systems ask different questions. The surprise is
which side each thing is on — grass colour, mob spawning and whether water
freezes are all on the *jittered* side, and the sky is not.
A biome is a label in the chunk at quarter resolution plus a bundle of
consequences hanging off that label. In 26.2 the bundle has been hollowed
out: `Biome` itself holds five things, and most of what a player would call
"the biome" now lives in the environment-attribute stack, where the biome is
one layer among several rather than the owner
([environment attributes and timelines](../world/environment-attributes-and-timelines.md)).
## The cast
| class | what it decides | when |
|---|---|---|
| `BiomeSource` | which biome a quart cell gets. Four implementations, and `BiomeSource.possibleBiomes` is the memoised pre-filter everything else leans on | `ChunkStatus.BIOMES`, on a worldgen worker |
| `Climate.Sampler` | the six climate numbers at a point. Filling a chunk uses `NoiseChunk.cachedClimateSampler`, the chunk-wrapped copy; `RandomState.sampler` is the flattened, cacheless one everything outside a chunk asks ([density functions](density-functions.md)) | per quart cell |
| `Climate.ParameterList` | the search space: one `Climate.ParameterPoint` per biome, indexed by a `Climate.RTree` | built once per world |
| `OverworldBiomeBuilder` | the overworld's parameter table, in Java — temperature, humidity, erosion and continentalness bands over six tables of biome keys | build time |
| `LevelChunkSection` | where the answer lives: a second `PalettedContainer` keyed by biome holder, two bits per axis | written once, saved, shipped |
| `BiomeManager` | the jitter — which biome this *block* gets, as opposed to which cell it is in | every gameplay read |
| `Biome` | five things: climate settings, an `EnvironmentAttributeMap`, `BiomeSpecialEffects`, generation settings and mob settings | — |
| `EnvironmentAttributeMap` | the biome's contribution to sky, fog, music and the gameplay switches — as *modifiers*, not values. Twenty gameplay attributes exist; the sixty-six vanilla biome files touch three of them between them, and fifty-one touch none | per attribute, per read |
## The trace: a chunk's biomes
```mermaid
sequenceDiagram
participant CST as ChunkStatusTasks
participant NBC as NoiseBasedChunkGenerator
participant CA as ChunkAccess
participant LCS as LevelChunkSection
participant MNBS as MultiNoiseBiomeSource
participant ClimS as Climate.Sampler
participant CPList as Climate.ParameterList
participant CRT as Climate.RTree
CST->>NBC: createBiomes — ChunkStatus.BIOMES, which NOISE and SURFACE both require
NBC->>NBC: fork to init_biomes, wrap the resolver in Blender and BelowZeroRetrogen
NBC->>CA: fillBiomesFromNoise, with the chunk's cached climate sampler
CA->>LCS: fillBiomesFromNoise — rebuild the container, 64 cells per section
loop per quart cell
LCS->>MNBS: getNoiseBiome — quart coordinates and the sampler
MNBS->>ClimS: sample — six functions, each multiplied by 10,000 and truncated
ClimS-->>MNBS: a Climate.TargetPoint of six longs
MNBS->>CPList: findValue — the parameter list owns the search
CPList->>CRT: findValueIndex — nearest neighbour over seven dimensions
CRT-->>LCS: a biome holder, into the palette
end
Note over LCS: saved under "biomes", shipped inside the chunk payload
```
The two wrappers in the second arrow only do anything beside chunks an
older version generated — [blending at the old-chunk border](blending.md)
is where they are explained.
**Biomes are decided before terrain, and not for it.** `ChunkStatus.BIOMES`
precedes `ChunkStatus.NOISE`, and the two do not depend on each other at all:
the noise fill never reads a biome. What makes a jungle and its terrain agree
is that both were computed from the *same* noise router — `RandomState` builds
the climate sampler out of the depth, continents, erosion and ridges
functions, the very ones that shape the land. Neither was consulted about the
other. The biome does not touch a block until `ChunkStatus.SURFACE`
([terrain](terrain.md)).
Only the noise generator does the above. `FlatLevelSource` and
`DebugLevelSource` inherit the base implementation and use the level's
uncached sampler directly. The End is a `NoiseBasedChunkGenerator` like the
overworld and the nether — just with `TheEndBiomeSource` in front of it,
which does not do a climate search at all: it thresholds a single erosion
sample outside a fixed central radius.
## The search, and the axis that is not sampled
`Climate.quantizeCoord` multiplies each of the six climate values by ten
thousand and truncates, so the entire search is integer arithmetic. The
target is a `Climate.TargetPoint` of six longs; each biome declares a
`Climate.ParameterPoint` of six `Climate.Parameter` intervals; and
`Climate.ParameterList.findValue` walks a `Climate.RTree` — six children per
node — minimising the sum of squared distances from the target to each
interval.
Except the count is seven, not six. `Climate.PARAMETER_COUNT` is **7** and
`Climate.RTree.create` refuses a point that does not supply seven, because
each `Climate.ParameterPoint` carries a scalar *offset* alongside its six
intervals, and `Climate.TargetPoint.toParameterArray` appends a literal zero
as the seventh coordinate of every query. So the seventh term of the metric
is always that biome's offset squared: a fixed penalty added to its score. It
is a "make this biome harder to win" dial, not anything sampled from the
world.
The tree also remembers. `Climate.RTree` keeps the winning leaf in a
`ThreadLocal` and seeds the next search with it as the initial candidate.
Adjacent quart cells almost always resolve to the same biome, so the walk
usually prunes immediately — which is what makes filling sixty-four cells a
section cheap. The tree is therefore stateful per thread, though never
incorrect: the remembered leaf is only a starting bound.
One thing the parameter table does *not* have is a separate underground
system. `OverworldBiomeBuilder.addUndergroundBiomes` and
`OverworldBiomeBuilder.addBottomBiome` place dripstone caves, lush caves,
sulfur caves and the deep dark into the same seven-dimensional table by their
*depth* band. A
cave biome is an ordinary entry that happens to win only below the surface.
## The two borders
The label goes into the section's biome palette — two bits per axis, so
**sixty-four biome cells per section** — is written to NBT under *biomes*
([chunk storage](../world/chunk-storage.md)) and is shipped to the client
inside the chunk payload. From then on it is *stored*, not computed, which is
why `/fillbiome` can exist at all and why `ClientboundChunksBiomesPacket`
exists to tell the client about it.
And then two different readers ask for it two different ways.
| | the jittered read | the exact read |
|---|---|---|
| entry point | `LevelReader.getBiome` → `BiomeManager.getBiome` | `BiomeManager.getNoiseBiomeAtPosition`, and on the client `BiomeManager.getNoiseBiomeAtQuart` |
| what it does | offsets by two, takes the eight surrounding quart corners, and picks the one minimising `BiomeManager.getFiddledDistance` — a seeded hash worth up to ±0.45 of a cell per axis | floors to the quart cell and reads the palette |
| who uses it | freezing and precipitation, mob spawning, commands — **and block tint**: grass, foliage and water colour, through `ClientLevel.calculateBlockTint` | the environment-attribute stack, and nothing else |
| what it looks like | the ragged border | the straight one |
**Block tint is on the jittered side**, which is the half of this that
surprises people: grass colour follows exactly the same ragged line as
whether snow falls. What softens the colour boundary in game is not the biome
lookup but a box blur on top of it — `ClientLevel.calculateBlockTint`
averages the result over the columns named by the *biome blend radius* option
and caches that in a `BlockTintCache`. Fog and sky are the ones on the other
border.
The client's exact read is the more expensive of the two, and
unconditionally so: `EnvironmentAttributeProbe.tick` runs a
`GaussianSampler` over the neighbourhood **every tick**, accumulating whole
`EnvironmentAttributeMap`s into a `SpatialAttributeInterpolator`. Whether an
attribute is actually interpolated is tested later, when the layer is
applied, and one that fails the test falls back to a single unfuzzed lookup.
The server never interpolates at all — it passes no interpolator.
## What a biome still owns
Five things, and none of them stops being read once the chunk is generated —
`NaturalSpawner` asks the mob settings every tick and a bone-mealed grass block
asks the generation settings.
`Biome.climateSettings` is precipitation, a base temperature, a
`Biome.TemperatureModifier` and downfall. Temperature is the interesting one:
`Biome.getHeightAdjustedTemperature` samples noise per block high above sea
level — which is why snow lines are ragged rather than flat — and `Biome`
keeps a fixed-size per-thread cache in front of it that evicts rather than
grows. Most of the public surface is the questions rather than the number:
`Biome.warmEnoughToRain`, `Biome.coldEnoughToSnow`, `Biome.shouldFreeze`,
`Biome.shouldSnow`. `Biome.getBaseTemperature` is the raw, uncached,
unadjusted escape hatch.
`BiomeSpecialEffects` is, in 26.2, **only block tint** — five fields, all of
them colours or a grass-colour modifier, and only the water colour is
mandatory. Fog, sky, clouds, ambient sound, music and particles have all left
it for the attribute stack
([lightmap, fog and sky](../rendering/lightmap-fog-and-sky.md)). And when the
four optional ones are silent, the tint does not come from the biome at all:
grass
and foliage colour are a lookup into the colormap images by temperature and
downfall, through `GrassColor`, `FoliageColor` and `DryFoliageColor`. "The
biome's grass colour" is usually just the two climate numbers that index a
texture.
`Biome.getAttributes` returns the `EnvironmentAttributeMap`, whose entries
are `AttributeModifier`s rather than values — so a biome may *override* the
layer below it or merely *modify* it, which is how a swamp thickens water fog
without naming a distance. One restriction lands here specifically:
`EnvironmentAttributeMap.CODEC_ONLY_POSITIONAL` means a biome may not set a
non-positional attribute at all.
`BiomeGenerationSettings` is a set of carvers and one set of placed features
*per decoration step*, read by [features and placement](features-and-placement.md);
`BiomeGenerationSettings.getBoneMealFeatures` is its only reader outside
worldgen, and its one caller is `GrassBlock`. `MobSpawnSettings` is the
weighted spawn lists `NaturalSpawner` reads
([entity lifecycle](../entities/entity-lifecycle.md)) — and its entry
constructor silently rewrites any miscellaneous-category entity type to pig.
## Questions players ask
**Why do I always spawn near the origin?** Because the *chunk* is chosen by a
climate search. `Climate.SpawnFinder` and `Climate.findSpawnPosition` look for
the point whose climate best matches the noise settings' spawn target, in two
spiral passes out to a maximum radius of 2,048 blocks, with depth pinned to
zero and the fitness deliberately biased toward the origin so that a tie lands
near 0,0. `MinecraftServer` then keeps only that answer's chunk and does a
terrain search inside it: an eleven-by-eleven chunk spiral of
`PlayerSpawnFinder.getSpawnPosInChunk`, over a first guess taken from
`ChunkGenerator.getSpawnHeight` or, failing that, the *WORLD_SURFACE*
heightmap. A dimension whose settings name no spawn target skips the climate
half and starts that spiral at the origin chunk.
**Why does `/locate biome` find biomes in chunks I have never visited?**
Because it asks the generator, not the world. `BiomeSource.findClosestBiome3d`
spirals through `BiomeSource.getNoiseBiome` with the live sampler and never
reads a palette — so it finds biomes in ungenerated chunks and will **never**
find one placed by `/fillbiome`. It pre-filters against
`BiomeSource.possibleBiomes`, so asking for an impossible biome fails
instantly rather than after a spiral out to six thousand four hundred blocks.
**Can a data pack change the overworld's biome layout?** Not the vanilla
preset. `OverworldBiomeBuilder` is hardcoded, and the data-pack element that
would carry a parameter list serialises to nothing but a preset name — there
are two presets. A pack can supply its own parameter list for a multi-noise
source; it cannot edit the one that ships.
**What does the client actually have?** A hollow `Biome`.
`Biome.NETWORK_CODEC` sends the climate settings, the *syncable* attributes
and the effects, and substitutes empty generation and mob settings — features,
carvers and spawn lists never cross the wire. And when a client asks for a
biome in a chunk it does not have, it gets plains.
**Does the biome decide which mobs spawn?** Not on its own, and not first.
`ChunkGenerator.getMobsAt` consults `Structure.spawnOverrides` *before*
`Biome.getMobSettings` ([structure placement](structure-placement.md)), and
nether fortresses are special-cased earlier still, inside `NaturalSpawner`.
**Why does adding one biome change the whole dimension?** Because
`EnvironmentAttributeSystem` builds one positional layer per attribute that
*any* biome in the registry mentions, at level construction. One new biome
naming one new attribute adds a layer every position in the dimension then
carries.
## Where to look
`Biome` · `Biome.getAttributes` · `BiomeSpecialEffects` ·
`BiomeSource.getNoiseBiome` · `BiomeSource.possibleBiomes` ·
`MultiNoiseBiomeSource` · `TheEndBiomeSource` · `Climate.Sampler.sample` ·
`Climate.quantizeCoord` · `Climate.ParameterList.findValue` ·
`Climate.RTree.search` · `Climate.PARAMETER_COUNT` ·
`Climate.findSpawnPosition` · `OverworldBiomeBuilder.addBiomes` ·
`OverworldBiomeBuilder.addUndergroundBiomes` ·
`LevelChunkSection.fillBiomesFromNoise` · `BiomeManager.getBiome` ·
`BiomeManager.getFiddledDistance` · `BiomeManager.getNoiseBiomeAtPosition` ·
`BiomeGenerationSettings` · `MobSpawnSettings` · `BiomeColors` ·
`EnvironmentAttributeProbe`
---
*Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes `tools/verify_names.py`.*
---
# Terrain
> Verified against **Minecraft 26.2** · Part XII · One chunk's rock: seven hundred and sixty-eight cells filled from their corners, a water table decided before the caves are cut, and the cave that fills with water because of it.
You dig into a cave and it is flooded. The water is not a fluid that flowed
in and settled; nothing flowed anywhere. Before the cave existed, while the
chunk was still solid stone, something decided that a point at that depth in
that column belongs to water rather than to air — and when the carver came
through and asked what to put in the hole it was digging, that answer was
still on file. **A carver does not choose the block it carves.** It chooses
the *shape*; the `Aquifer` chooses the material, and it chose it for the
stone as well.
This page is the three chunk statuses that turn a scalar field into blocks —
`ChunkStatus.NOISE`, `ChunkStatus.SURFACE` and `ChunkStatus.CARVERS`, plus
the workspace that `ChunkStatus.BIOMES` quietly builds before any of them.
The conveyor that runs the statuses, the dependency pyramid and the
threading are [the chunk generation
pipeline](../world/chunk-generation-pipeline.md) in Part IV; this is the
cargo. The scalar field itself is [density
functions](density-functions.md), and the labels that steer the surface pass
are [biomes](biomes.md).
## The cast
| class | what it decides | when |
|---|---|---|
| `ChunkGenerator` | the API the statuses call — `ChunkGenerator.fillFromNoise`, `ChunkGenerator.buildSurface`, `ChunkGenerator.applyCarvers`. `ChunkGenerators.bootstrap` registers exactly three implementations | worldgen executor |
| `NoiseGeneratorSettings` | the whole per-dimension recipe: the `NoiseSettings` cell dimensions, the default block and fluid, the `NoiseRouter`, the surface rules, the sea level, the aquifer and ore-vein switches | data, loaded with the world |
| `NoiseChunk` | the per-chunk workspace — the wrapped router, the cell interpolators, the `Aquifer`, the filler chain | built at `ChunkStatus.BIOMES`, dies with the chunk |
| `MaterialRuleList` | which filler answers first: the aquifer, then the ore veins | inside the cell loop |
| `Aquifer` | what liquid, if any, belongs at a point — and therefore what a carver may leave behind | noise *and* carvers |
| `OreVeinifier` | copper or iron, from the sign of one router function | inside the cell loop |
| `SurfaceSystem` | the column re-skin: grass over dirt over stone, sand, the badlands bands | `ChunkStatus.SURFACE`, one instance per level |
| `WorldCarver` | the shape of caves and canyons, and nothing about their contents | `ChunkStatus.CARVERS` |
Everything here runs on the worldgen executor, one task at a time per
dimension. Two of the steps fan out further. `ChunkGenerator.createBiomes`
forks to the background pool as *init_biomes* for **every** generator — the
base implementation does it, so `FlatLevelSource` and `DebugLevelSource` fork
too — and `NoiseBasedChunkGenerator` overrides it only to use the chunk's
cached sampler. The second fork, `NoiseBasedChunkGenerator.fillFromNoise` as
*wgen_fill_noise*, really is the noise generator's alone.
`RandomState` — the per-level seed root — is built once in `ChunkMap` and
shared by every generating chunk, and it owns the `SurfaceSystem`, which is
therefore per **level**, not per chunk.
## Four statuses, and what each hands on
```mermaid
flowchart LR
BIO["BIOMES"] -- "the NoiseChunk, with its caches and its Aquifer" --> NOI["NOISE"]
NOI -- "solid rock, two worldgen heightmaps" --> SUR["SURFACE"]
SUR -- "a skin, and a preliminary surface level" --> CAR["CARVERS"]
CAR -- "holes, and a CarvingMask" --> FEA["FEATURES"]
```
The odd arrow is the first one. **The workspace is born one status before
the terrain needs it**, because the biome sampler wants the chunk's caches
too ([biomes](biomes.md) reads the climate functions through
`NoiseChunk.cachedClimateSampler`). So `NoiseChunk.forChunk` runs at
`ChunkStatus.BIOMES`, wrapping the seeded router into chunk-local caches and
constructing the `Aquifer` and the `NoiseChunk.BlockStateFiller` chain,
before a single block exists.
That one instance then serves all three terrain steps, which is exactly what
makes the aquifer's answers agree between filling and carving. It is heavily
mutated on the way — and `NoiseChunk.stopInterpolation`, at the end of the
noise fill, *disarms* it: from the surface step onward, sampling an
interpolator with the `NoiseChunk` itself as the context throws. A caller that
passes any other context is quietly served by the wrapped function instead,
which is what every post-fill caller does. What survives for reuse is the aquifer's grid cache and
the preliminary surface level. It is never cleared and it is not pinned to a
thread; the three steps run as separate tasks on whichever worker takes
them, and what serialises them is the chunk-status future chain rather than
thread affinity.
## Filling the noise: six loops, one number at the bottom
The overworld's `NoiseSettings` asks for a horizontal noise size of one and
a vertical size of two, and `NoiseSettings.getCellWidth` and
`NoiseSettings.getCellHeight` turn those into blocks by multiplying by four.
So the unit of overworld terrain is a cell **four blocks wide, four deep and
eight tall**, and a chunk is four by four by forty-eight of them.
**768** — cells in one overworld chunk, each holding 128 blocks
(`NoiseBasedChunkGenerator.fillFromNoise`).
`NoiseChunk` evaluates the *interpolated* density terms at cell **corners**
only. Everything inside a cell is three linear interpolations away from the
eight corners around it, and the walk that does this is six loops deep:
```mermaid
flowchart TB
subgraph CX["for each of the 4 cell columns in X: advanceCellX fills the next corner slice, swapSlices drops the old one"]
subgraph CZ["for each of the 4 cell rows in Z"]
subgraph CY["for each of the 48 cells in Y, downward: selectCellYZ loads its eight corner values"]
subgraph BY["for each of the 8 block layers in the cell, downward: updateForY"]
subgraph BX["for each of the 4 blocks across in X: updateForX"]
BZ["for each of the 4 blocks across in Z: updateForZ, then getInterpolatedState — one block decided"]
end
end
end
end
end
```
Read the nesting as the cost model. The two outer levels are where the
sampling happens — a slice of corner values is filled per cell column and
dropped one column later — and everything below `NoiseChunk.selectCellYZ`
is arithmetic on eight numbers. Counting the slices, one *interpolated* term
is sampled five times five times forty-nine per chunk:
**1,225** — corner samples per interpolated density term, per chunk
(`NoiseChunk.fillSlice`, five slices of five by forty-nine).
The Y direction runs **downward** at both nesting levels, which matters
because the two worldgen heightmaps are updated as blocks are written and
the first non-air block seen from the top is the answer.
Two things about that lattice are worth stating plainly, because "Minecraft
terrain is a lattice" is true twice over at two different resolutions. Only
the terms explicitly marked *interpolated* come from the eight-corner lerp,
and resolving every reference in the overworld router finds **eight** of
them: one round the whole final-density subtree, four inside the noodle-cave
graph, and three across the two vein terms — *vein_gap* is not one of them,
and neither is the aquifer's barrier, which the `Aquifer` samples per block.
The final density is then wrapped in a *cache_all_in_cell*, filled for every
block in the cell. Meanwhile the 2-D shaping terms, continentalness and erosion and
ridges and the splines, sit behind *flat_cache*, which samples once per
**four-by-four block column group** at y = 0 and reuses that for all sixteen
columns. Only *cache_2d* is exact per column
([density functions](density-functions.md) owns the caches).
The write at the bottom of the loop does not go through
`ChunkAccess.setBlockState`. The fill calls the section setter directly with
the threading check disabled and updates `Heightmap.Types.OCEAN_FLOOR_WG`
and `Heightmap.Types.WORLD_SURFACE_WG` by hand, and it skips air entirely —
a chunk starts empty, so only non-air is ever written. The acquire/release
pair around the whole fill is a concurrent-access assertion, not a lock.
## The two fillers: what the number becomes
`NoiseChunk.getInterpolatedState` does not read the density and compare it
to zero. It runs the `MaterialRuleList` — a chain of
`NoiseChunk.BlockStateFiller`s — and takes the first non-null answer. There
are two of them, in this order.
**The aquifer.** `Aquifer.computeSubstance` receives the final density and
decides, from its own barrier and fluid-level noises sampled on a coarse
grid, whether this point is stone, air, or a fluid. `Aquifer.FluidPicker`
and `Aquifer.FluidStatus` are the global fallback underneath its local water
tables — the sea, and the lava. `Aquifer.NoiseBasedAquifer` is the real
implementation; a dimension with aquifers switched off gets a trivial one.
**The ore veins.** `OreVeinifier` is the second filler, active only when the
settings enable it, and it is why copper and iron veins are *terrain rather
than decoration*: they exist before the surface pass and before the carvers,
and no feature places them. Which of the two `OreVeinifier.VeinType`s you
get is the **sign** of one router function, `NoiseRouter.veinToggle` — there
is no separate "which ore" noise.
If both fillers return nothing, the block becomes the settings' default
block. And `Aquifer` marks the positions where it placed fluid for
post-processing, so the settled water table you can see in a cross-section
becomes real fluid ticks the moment the chunk is promoted — which is why
"nothing flowed in" is a true statement about worldgen and not about the
chunk's first live tick ([scheduled ticks](../world/scheduled-ticks.md)).
## The surface pass, and the two places it breaks its own rule
`SurfaceSystem.buildSurface` compiles the **dimension's**
`NoiseGeneratorSettings.surfaceRule` tree once for the whole chunk — one rule
tree per dimension, which then branches on biome inside itself, then walks each of the 256 columns downward
from the worldgen surface heightmap, tracking depth below stone and water
height in a `SurfaceRules.Context` that carries its own caches. Every write
is gated on the existing block still being the settings' **default block**,
which is what makes ore veins and aquifer water immune to being turned into
grass.
Two things sit outside the rule system entirely, and neither obeys that
gate. `SurfaceSystem.erodedBadlandsExtension` runs *before* the column walk
and fills air with the default block to raise the terracotta pillars.
`SurfaceSystem.frozenOceanExtension` runs *after* it and writes snow and
packed ice over air **and over water**, ungated. Both are selected by biome
rather than by rule, and `SurfaceSystem` owns the noises they need along
with the ones for the badlands bands and the icebergs.
## Carving, and who chooses the block
`ChunkGenerator.applyCarvers` does not carve the chunk it was given from the
chunk it was given. It loops over a **17×17 neighbourhood of source
chunks**, asks each configured carver of that source chunk's biome whether a
cave or a canyon *starts* there, and carves whatever does into the centre
chunk. That reach costs the dependency pyramid nothing: the neighbours are
read only as memo holders for `ChunkAccess.carverBiome`, and the biome
itself is recomputed from the biome source. The reach the carvers need is
already paid for: six of the generation steps ask for
`ChunkStatus.STRUCTURE_STARTS` eight chunks out, written as a bare literal —
the constant `ChunkStatus.MAX_STRUCTURE_DISTANCE` that holds the same eight is
read by nothing — and the accumulated pyramid the ticket system sizes itself
against is wider still ([the chunk generation
pipeline](../world/chunk-generation-pipeline.md)).
Three carvers are registered — `WorldCarver.CAVE`, `WorldCarver.NETHER_CAVE`
and `WorldCarver.CANYON` — each paired with a `CarverConfiguration` as a
`ConfiguredWorldCarver`, reading the world through a `CarvingContext` and
recording what they touched in a `CarvingMask`, the per-chunk bit set.
And then the hook. `WorldCarver.getCarveState` returns lava below the
configured lava level, and otherwise asks `Aquifer.computeSubstance` with a
density of **zero** what belongs at this point. `Aquifer.FluidStatus.at`
answers plain air above the local water table and the fluid below it — never
null; the null is `Aquifer.computeSubstance`'s own, and it means *do not carve
here at all*. So the water in a flooded cave
was decided by the same object that decided the water in the stone around
it, and a dry cave is the carver writing air one block at a time because the
aquifer told it to. What a cave may eat through is itself a data-pack
decision: `WorldCarver.canReplaceBlock` tests the configuration's
*replaceable* `HolderSet`, which is also what stops a carver from hollowing
out an ore vein it was not told about. If a grass or mycelium block was
passed on the way down, the dirt below is re-skinned through
`SurfaceSystem.topMaterial`.
`NetherWorldCarver` is the exception that proves the rule. It overrides
`WorldCarver.carveBlock`, never consults the aquifer at all, and writes lava
below thirty-one blocks above the dimension's minimum and cave air above.
## Questions players ask
**Why is there always lava at the same depth, in every world?** Because the
sea level moves with the noise settings and the lava level below it does
not — it is a constant in the generator, not a data-pack field.
**Do the caves change if I switch the dimension to the modern random
source?** Not their seeding.
`NoiseBasedChunkGenerator.applyCarvers` hardcodes a `LegacyRandomSource`
whatever the settings say. The settings'
`NoiseGeneratorSettings.useLegacyRandomSource` reaches further than the
level's root random in one other way, though: it re-seeds `BlendedNoise`, and
it changes the *Y* at which the surface pass samples a column's biome. The
legacy nether biome noises are not part of that — `RandomState`'s wiring
visitor builds those two from a `LegacyRandomSource` whatever the setting
says.
**Where does the flat shelf under a village come from?** From this page's
density field, not from any block edit. `NoiseChunk`'s constructor adds
`DensityFunctions.BeardifierMarker` to the router's final density *itself*
and then swaps that exact instance for the chunk's real `Beardifier` while
wrapping — so every noise dimension is beardified whether or not its router
JSON ever mentions a beardifier, and "structures flatten terrain" is
implemented as an object comparison inside a visitor
([structure placement](structure-placement.md)).
**What height does a structure think the ground is at, then?** The one
*before* it changes it. `NoiseBasedChunkGenerator.iterateNoiseColumn`,
behind `ChunkGenerator.getBaseHeight` and `ChunkGenerator.getBaseColumn`,
builds a throwaway one-cell `NoiseChunk` with an empty
[`Blender`](blending.md) and the
bare beardifier marker, samples a column, and discards it.
**What happens at the boundary with chunks generated by an older version?**
`Blender` reads `BlendingData` harvested from the neighbours and enters the
density graph as three nodes — [blending at the old-chunk
border](blending.md) is the page for it, and for `BelowZeroRetrogen`, the
world-deepening path that rides the same hooks: it wraps the biome
resolver, patches bedrock after the noise step, and gates the spawn step.
**Is any of this really running in a superflat world?** Almost none of it.
`FlatLevelSource` and `DebugLevelSource` implement surface, carvers and
spawning as no-ops, and `DebugLevelSource` writes its state grid at the
decoration step rather than the noise step. Development builds can switch
off much more: `SharedConstants` carries flags that disable the surface
pass, the carvers, the aquifers, the ore veins and fluid generation
outright, plus visualisation modes that make the aquifer and the ore veins
write marker blocks instead of real ones.
**Why does a half-generated chunk have the wrong heightmaps in it?**
Because which two are live is a property of the *status*, not of the step:
`ChunkStatus` registers every status up to and including
`ChunkStatus.SURFACE` with the worldgen pair and `ChunkStatus.CARVERS`
onward with the four final ones. A chunk saved mid-generation really does
persist its worldgen heightmaps, and they stop being written from the
carvers status on ([chunk anatomy](../world/chunk-anatomy.md)).
## Where to look
`ChunkGenerator` · `ChunkGenerators.bootstrap` ·
`NoiseBasedChunkGenerator.fillFromNoise` · `NoiseGeneratorSettings` ·
`NoiseSettings.getCellWidth` · `NoiseChunk.forChunk` ·
`NoiseChunk.fillSlice` · `NoiseChunk.getInterpolatedState` ·
`NoiseChunk.stopInterpolation` · `MaterialRuleList` ·
`Aquifer.computeSubstance` · `Aquifer.NoiseBasedAquifer` ·
`OreVeinifier.create` · `SurfaceSystem.buildSurface` ·
`SurfaceRules.RuleSource` · `SurfaceRules.Context` ·
`NoiseBasedChunkGenerator.applyCarvers` · `WorldCarver.getCarveState` ·
`WorldCarver.canReplaceBlock` · `CarvingContext` · `CarvingMask` ·
`Beardifier` · `Blender` · `BelowZeroRetrogen` · `Heightmap.Types` ·
`RandomState.create`
---
*Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes `tools/verify_names.py`.*
---
# Blending at the old-chunk border
> Verified against **Minecraft 26.2** · Part XII · a chunk generated beside one an older version left behind, and the five places its generation is bent toward that neighbour.
You are walking east through a world you have had for years. Behind you the
ground was decided by a version that is not this one; ahead of you it has not
been decided at all. Put the debug screen up and the chunk-generation entry
reads *Blending: Old* while you stand on the old side, and stops reading it
once you cross. For roughly a hundred blocks either side of that line the game
is generating chunks that are not free to be whatever the seed says — and
directly against the seam they are hardly generated at all. The three splines
that shape overworld terrain are swapped out for a ground height the game read
off the old chunk's blocks a moment earlier, the constant ten, and zero.
This is the deliberate exception [the part's premise](README.md) names.
Everywhere else in world generation, a chunk is a function of the seed and the
data packs. Here it is a function of the seed, the data packs, **and the
blocks measured by up to a hundred and ninety-three chunks around it** — which the
game does not remember and has to go and measure, one column at a time.
## The flag is a nullable field, and looking for it costs a disk read
A chunk is old if `ChunkAccess.blendingData` is non-null. That is the whole
test: `ChunkAccess.isOldNoiseGeneration` returns exactly whether that field is
set, and the field is *final* — it arrives through the constructor from
`SerializableChunkData`, which reads a *blending_data* compound out of the
chunk's NBT, and nothing sets it afterwards. Which saves carry that key is
`util/datafix`'s business, which
[this book skips](../anatomy/what-this-book-skips.md); by the time world
generation sees a chunk the key is either there or it is not.
> **For a 1.21-era reader.** The class that used to read and write the chunk
> NBT is now `SerializableChunkData`, a record with a *blending_data*
> component and its own parse and write halves. The old *ChunkSerializer*
> name is gone.
The awkward part is that a chunk being generated cannot ask its neighbours
whether they are old, because most of them do not exist yet. So `Blender.of` —
the factory every noise-generation step calls — starts by asking the *save
file*. `WorldGenRegion.isOldChunkAround` hands the question to the level's
`ChunkMap`, which inherits `SimpleRegionStorage.isOldChunkAround` and lands on
`IOWorker.isOldChunkAround`. That walks the region files covering a square of
radius seven, and for each region it needs a `BitSet` with one bit per chunk,
built by scanning all 1,024 chunks in the region for two NBT fields and
nothing else: *DataVersion* and *blending_data*. A chunk counts as old if its
stored *DataVersion* is below 4882 or if it already carries a *blending_data*
compound. The scan runs on the background executor, the caller joins it, and
the bitset is kept in a 1,024-entry region cache.
**Every** chunk generated in **every** world pays for that lookup at least
twice, whatever generator the dimension uses:
`ChunkStatusTasks.generateBiomes` and `ChunkStatusTasks.generateNoise` both
evaluate `Blender.of` eagerly, before knowing whether the answer can possibly
be yes. The noise generator adds two more —
`NoiseBasedChunkGenerator.buildSurface`, eagerly again, and
`NoiseBasedChunkGenerator.applyCarvers`, the only call site written inside a
supplier and so the only one that can be skipped. When nothing is old the
bitsets come back empty and `Blender.of` hands back the shared empty blender,
which is not an empty map but an anonymous subclass overriding the three
answering methods with identities: alpha one, offset zero, density unchanged,
resolver returned as given.
## The cast
| class | what it decides | when |
|---|---|---|
| `Blender` | the four answers — the height alpha and offset, the blended density, the biome override, and where carvers may not dig | built per step, used on the worldgen executor |
| `BlendingData` | one old chunk's measurements: a ring of sixteen columns holding a height, a density profile and a biome column each | measured once per loaded chunk object |
| `IOWorker` | whether any chunk within seven is old, from a per-region `BitSet` scanned out of the region files | on the background executor, joined by the caller |
| `NoiseChunk` | where the answers enter the density graph: two pre-filled flat caches and one wrapper | `ChunkStatus.BIOMES` onward, cached on the chunk |
| `NoiseRouterData` | which router functions are blendable at all — three overworld splines and one post-process wrapper | world creation, once |
| `CarvingMask` | the extra predicate that makes carvers treat old ground as already carved | `ChunkStatus.CARVERS` |
| `BelowZeroRetrogen` | the separate world-deepening path that rides the same hooks | `ChunkStatus.NOISE` and `ChunkStatus.BIOMES` |
| `SerializableChunkData` | the *blending_data* key: which chunks are flagged, and which measurements survive a save | chunk load and save |
## One measurement, five consumers
`BlendingData` is gathered once and then read by five unrelated pieces of
machinery at four chunk statuses — two inside the density graph, three nowhere
near it.
```mermaid
flowchart TB
OLD["An old chunk: its blocks on disk, plus a blending_data tag"]
BD["BlendingData: a ring of 16 columns round the chunk edge, each a height, a density profile and a biome column"]
B["Blender: 193 chunk positions consulted for height and biome, the inner 9 of them also for density"]
OLD --> BD
BD --> B
B --> R1["BIOMES: getBiomeResolver returns the nearest old biome, or defers"]
B --> R2["BIOMES and NOISE: blend_alpha and blend_offset, two flat caches filled in the NoiseChunk constructor"]
B --> R3["NOISE: blend_density, a marker wrapped round the final slide"]
BD --> R4["CARVERS: an extra carving mask, so carvers skip old ground"]
BD --> R5["FEATURES: border ticks on leaves and fluids, on the old chunk only"]
R4 -.- SN["static on Blender, straight off each chunk's BlendingData — the two maps are not consulted"]
R5 -.- SN
```
Two maps, not one. `Blender.of` sweeps the square from seven chunks west to
seven chunks east and clips it to a circle — the test is that the squared
offsets sum to no more than sixty-four — which leaves **193 positions**. Every
one that yields data goes into the height-and-biome map; only the inner three
by three also goes into the density map. That asymmetry is the whole reason
the terrain seam is a hundred blocks wide and the cave seam is a handful.
A neighbour yields data only if it passes two tests in
`BlendingData.getOrUpdateBlendingData`: it carries a `BlendingData`, **and**
`ChunkAccess.getHighestGeneratedStatus` is not before `ChunkStatus.BIOMES`.
The second test is what keeps this honest — during the *BIOMES* step the
dependency window only guarantees neighbours at *STRUCTURE_STARTS*, so a
half-built chunk in the queue contributes nothing, and in practice the only
chunks that pass are ones loaded whole from the save.
**Seven** — the radius in chunks the height blend reaches, which `Blender`
derives from the twenty-seven-cell height range: four quart cells per section
across seven sections, less one, plus three, converted back to chunks. It sits
inside the radius-eight
dependency window every noise step declares, so none of the 193 reads can
trip `WorldGenRegion.getChunk`'s out-of-range crash.
## Sixteen columns, read out of blocks
`BlendingData` does not store a copy of the old terrain. It stores sixteen
columns in a ring round the chunk's edge, measured out of that same old
chunk's own blocks the first time anyone asks —
`BlendingData.getOrUpdateBlendingData` fetches one chunk and hands it to its
own `BlendingData.calculateData`. Seven of them are *inside*
indices — the corner and three more along the north edge, three along the
west — at block coordinates 0, 4, 8 and 12. The other nine are *outside*
indices, sampled at block coordinate 15 along the east and south edges, which
in cell arithmetic belong to the next chunk's first cell. Sixteen slots is
what `BlendingData.Packed`'s codec validates the saved height array against,
and it is exactly the array length the class computes from a chunk being four
quarts wide.
Which of the sixteen get filled depends on which way the chunk faces new
ground. `BlendingData.sideByGenerationAge` asks each of the eight `Direction8`
neighbours whether it is old, and `BlendingData.calculateData` fills only the
columns on the sides that are **not** — the ones facing chunks the game is
about to generate. There is one call site in the whole game and it passes
*false*, so "sides by generation age" only ever means "sides facing new
chunks". The method also guards on `BlendingData.hasCalculatedData`, so a
chunk object measures itself once and never again: whichever sides were new at
that moment are the sides it will carry until it is unloaded.
Each filled column gets three things. **A height**:
`BlendingData.getHeightAtXZ` starts at the *WORLD_SURFACE_WG* heightmap if the
chunk has one primed and at the top of the old area if not, then walks
straight down looking for one of eleven block types — podzol, gravel, grass,
stone, coarse dirt, sand, red sand, mycelium, a snow *block*, terracotta or
dirt — and
returns the first Y at which it finds one, or the bottom of the old area if it
never does. **A density profile**: for each eight-block-tall cell,
`BlendingData` reads fifteen consecutive blocks downward, scoring each plus or
minus one for whether it is ground, and divides by fifteen — ground meaning
not air, not a leaf, not a log, not a mushroom block and with a non-empty
collision shape, so a cave counts as air and a tree does not count as terrain.
One more pass rewrites the two cells straddling the measured height so the
surface lands where the height said it did. And **a biome column**: one
`Biome` holder per four-block layer of the old area, read straight out of that
chunk's own biome container.
Of those three, **only the heights are saved.** `BlendingData.pack` writes the
minimum section, the maximum section and the sixteen doubles, and omits the
heights entirely if none of them was ever measured; the density array is
declared *transient* and the biome list is not in the codec at all. Unload the
region and come back and the game re-reads the old chunk's blocks to rebuild
both.
## Following one chunk through
```mermaid
sequenceDiagram
participant CST as ChunkStatusTasks
participant Blender as Blender
participant CM as ChunkMap
participant BD as BlendingData
participant NBC as NoiseBasedChunkGenerator
participant NC as NoiseChunk
Note over CST: ChunkStatus.BIOMES, on the worldgen executor
CST->>Blender: of — build a blender for this chunk
Blender->>CM: isOldChunkAround, radius 7 chunks
CM-->>Blender: yes, from a bitset scanned out of the region files
loop 193 positions, clipped to a circle
Blender->>BD: getOrUpdateBlendingData
BD->>BD: measure the sides facing new chunks, once per chunk object
end
Blender-->>CST: a live blender, two maps of BlendingData
CST->>NBC: createBiomes with the blender
NBC->>NC: forChunk — 25 columns of alpha and offset, filled in the constructor
NBC->>NBC: wrap the biome resolver, then wrap that in BelowZeroRetrogen
Note over CST,NC: ChunkStatus.NOISE, the same NoiseChunk, cached on the chunk
NC->>Blender: blendDensity, once per sampled point inside the marker
Blender-->>NC: the old density outright, or a lerp toward the noise
```
The order matters in one non-obvious way. `NoiseChunk` is created at *BIOMES*,
not at *NOISE*, because the biome step needs the chunk's climate sampler, and
it is then cached on `ChunkAccess`. So the blender that reaches the density
graph is the one built at *BIOMES* — the later three are passed to
`ChunkAccess.getOrCreateNoiseChunk`, find it already created, and are thrown
away.
The twenty-five columns are the other thing worth noticing. Before any router
mapping runs, the `NoiseChunk` constructor loops over the chunk's five-by-five
grid of quart columns, calls `Blender.blendOffsetAndFactor` for each, and
fills two `NoiseChunk.FlatCache` instances. Only afterwards does
`NoiseChunk.wrapNew` swap `DensityFunctions.BlendAlpha` and
`DensityFunctions.BlendOffset` — by object identity, the same trick the
beardifier uses — for those already-full caches
([density functions](density-functions.md)). An empty blender skips the swap:
the two singletons stay the constants one and zero that they are, and a
*blend_density* marker is replaced by its own child.
## What the blender actually answers
Three questions, three different shapes of answer, and only two of them are
blends.
**Height, as an alpha and an offset.** `Blender.blendOffsetAndFactor` first
checks whether the sample point sits exactly on a measured column; if it does,
it returns alpha zero and the old height, put through
`Blender.heightToOffset` — a rational function, not a polynomial. If not,
it walks every measured height in the height map, keeps those within
twenty-seven quart cells — a hundred and eight blocks — and averages them
weighted by the inverse fourth power of distance, with alpha the smoothstep of
the *nearest* distance over twenty-eight. Nothing in range means alpha one and
offset zero: the identity.
Alpha is then used as a mixing weight in `NoiseRouterData.splineWithBlending`,
which interpolates from a fixed target at alpha zero to
the real spline at alpha one, and this is where the page's opening claim comes
from. Exactly three router functions are built that way — *offset*, *factor*
and *jaggedness*, for each of the three overworld variants — and their targets
are, respectively, `DensityFunctions.BlendOffset`,
`NoiseRouterData.BLENDING_FACTOR` (the constant ten) and
`NoiseRouterData.BLENDING_JAGGEDNESS` (zero). Against the seam alpha is zero,
so overworld terrain is not shaped by its splines at all: the ground height is
whatever `BlendingData` measured, the factor is ten and the jaggedness is
nothing. The shipped data pack agrees — *blend_alpha* appears in nine density
function files and *blend_offset* in three, all of them under the three
overworld directories.
Because *offset* and *factor* are also the two inputs to
`NoiseRouterData.preliminarySurfaceLevel`, which `Aquifer.NoiseBasedAquifer`
reads to place its fluid levels, the water table follows the old ground. Nobody
wrote a rule for that: it falls out of the aquifer sampling a blended
function.
**Density, as a lerp with a very short reach.** `Blender.blendDensity` is
called per sample from inside `NoiseChunk.BlendDensity`, the wrapper the
*blend_density* marker becomes. It measures distance in cells with the Y
difference doubled — cells are twice as tall as they are wide — keeps
neighbours within two, and mixes toward the noise with an alpha of the closest
distance over three. An exact hit returns the old density with no mixing at
all. This one wrapper is in every dimension's router: *blend_density* appears
in all seven shipped noise settings, while the alpha and offset nodes are
overworld-only.
**Biome, as a replacement.** `Blender.getBiomeResolver` wraps the biome source
in a resolver that asks `Blender.blendBiome` first and only falls through when
it declines. And it is not a blend: it finds the nearest measured biome within
twenty-seven cells, adds twelve cells' worth of a fixed shift noise to that
distance, divides by twenty-eight, and returns the old biome if the result is
below one half and nothing at all if it is above. So the biome boundary is a
hard line at roughly half the terrain blending distance, roughened by noise —
one biome or the other, never a mixture, which is the only answer a palette of
biome holders can represent.
## The two consumers that never touch the noise router
**Carvers are told to stay out.** At *CARVERS*,
`Blender.addAroundOldChunksCarvingMaskFilter` collects the `BlendingData` of
all eight `Direction8` neighbours plus the chunk's own, turns each into a
`Blender.DistanceGetter` measuring distance to a box eight blocks either side
of the chunk centre in X and Z — a whole chunk wide — and as tall as that
chunk's old area, and installs the minimum of them as a
`CarvingMask.Mask` on the chunk's carving mask. A position within four blocks
of any such box — after each axis is displaced by the same shift noise,
scaled by four — reads as already carved. Since `WorldCarver.carveEllipsoid`
skips any position the mask already reports, the effect is that carvers refuse
to dig into or right up against old ground. The mask is additional: it is
consulted by `CarvingMask.get` alongside the real bits and never written to
the saved array.
**Leaves and water at the seam are marked for a second look.** At *FEATURES*,
after decoration, `ChunkStatusTasks.generateFeatures` calls
`Blender.generateBorderTicks` — and this is the one hook that fires on the
*old* side rather than the new one. It returns immediately unless the chunk
being generated carries a `BlendingData` of its own, which a chunk generated
fresh today never does: it acts only on an old chunk that is still being
carried through the statuses, one saved before *FEATURES* or one being
deepened. Given such a chunk it sweeps four Y levels — one below and one at
the bottom of the old area, one at and one above the top — across all 256
columns, and then, for each of the four horizontal neighbours that is *not*
old, walks the whole sixteen-wide face from the bottom of the old area up to
that column's *MOTION_BLOCKING* height. Every leaf block and every non-empty
fluid it passes goes to `ChunkAccess.markPosForPostProcessing`. Nothing is
changed: the positions are queued for the post-processing pass that runs when
the chunk becomes live, which is what makes water at the seam flow and
orphaned leaves decay instead of hanging there. The step's ordering is what
makes the heightmap read safe — `ChunkStatusTasks.generateFeatures` primes the
four final heightmaps before decorating, so *MOTION_BLOCKING* is current by
the time the border walk reads it.
## The other passenger
`BelowZeroRetrogen` is not blending, but it rides the same hooks and is easy
to mistake for it. A chunk carrying one is being *deepened* rather than
blended: `ChunkAccess.getHighestGeneratedStatus` folds its
`BelowZeroRetrogen.targetStatus` in, `ChunkStatusTasks.generateNoise` calls
`BelowZeroRetrogen.replaceOldBedrock` and then
`BelowZeroRetrogen.applyBedrockMask` if there are holes, and
`BelowZeroRetrogen.getBiomeResolver` wraps the blender's resolver in one more
layer that keeps three cave biomes from the new generation and takes
everything else from the chunk's existing biome column.
## Questions players ask
**Why is the seam wide for hills and narrow for caves?** Two maps, two radii.
Height and biome are averaged over every measured column within a hundred and
eight blocks, gathered from up to 193 chunks; density is mixed only from the
nine chunks nearest the one being generated, over two cells. The ground slopes
for a hundred blocks and the caves change their mind in eight.
**Can the blend leave a visible edge anyway?** Yes, twice over. A column whose
old surface is none of the eleven blocks the height scan recognises reports
the bottom of the old area instead, and that bogus height is averaged in with
the rest. And the biome switch is a threshold rather than a ramp, so the
surface rules can change block for block along a line while the terrain under
them is still sloping.
**Does any of this happen in a brand-new world?** The lookup does, the work
does not — every chunk asks `Blender.of` whether an old chunk is near, and it
returns the empty blender when none is. Even the lookup is cheap after the
first time: the per-region bitset it consults is memoised behind
`IOWorker.getOrCreateOldDataForRegion`, so the scan runs once per region and
not once per chunk. Development builds can
switch the whole thing off with the `SharedConstants.DEBUG_DISABLE_BLENDING`
flag, which short-circuits `Blender.of`, the carving-mask filter and the
border ticks alike.
## Where to look
Start at `Blender.of` and read down: the two maps, the two radii, the three
answering methods. Then `BlendingData.calculateData` and
`BlendingData.getHeightAtXZ` for what a measurement is, and
`BlendingData.Packed` for what survives a save. `NoiseChunk`'s constructor and
`NoiseChunk.wrapNew` show where the answers enter the density graph,
`NoiseRouterData` which three functions were built to receive them, and
`ChunkStatusTasks` when each hook fires.
---
*Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes `tools/verify_names.py`.*
---
# Features and placement
> Verified against **Minecraft 26.2** · Part XII · A chunk decorates: a stream of positions folded through filters, an order every chunk in the dimension already agreed on, and a data pack that can stop the world from opening.
The oak in the middle of a plains chunk was not placed by the plains biome.
It was placed by a list that every biome in the dimension contributed to,
sorted once at world load into one order per decoration step, with an index
per entry that the random seed for each feature is derived from. That is how the same
seed grows the same forest — and it is also why **two biomes that list the
same two features in opposite orders make the world refuse to open.** The
order is a topological sort of a graph, and a graph can have a cycle.
Decoration is everything the terrain steps did not put there: trees, flowers,
ores, lakes, patches, springs. The system separates three things a modder
usually wants separately — *what* to build, *where* to try, and *who* wants
it — and this page is how those three meet at `ChunkStatus.FEATURES`. The
biggest single feature, the tree, has its own page
([trees](trees.md)); the terrain that decoration lands on is
[terrain](terrain.md).
## The cast
| class | its job | notes |
|---|---|---|
| `Feature` | the algorithm, with one method: `Feature.place`, which returns whether it wrote anything | **63** registered into `BuiltInRegistries.FEATURE` |
| `FeatureConfiguration` | its parameters, per feature type | `NoneFeatureConfiguration` for the ones that need none |
| `ConfiguredFeature` | a feature plus its configuration, and **no position logic at all** | the unit a sapling grows |
| `PlacedFeature` | a configured feature plus an ordered list of modifiers | the unit a biome names — the only one of the three that owns placement modifiers |
| `PlacementModifier` | one function from a position to a *stream* of positions | 15 registered types |
| `GenerationStep.Decoration` | the eleven steps, in order, from raw generation to top-layer modification | a biome's list is a list of lists, by ordinal |
| `FeatureSorter` | flattens every possible biome's per-step lists into one sorted list per step, with an index lookup | once per generator, memoised |
| `WorldgenRandom` | the seed, reseeded absolutely twice: once per chunk, then once per feature | on the worldgen executor |
## The trace: a chunk decorates
```mermaid
sequenceDiagram
participant CST as ChunkStatusTasks
participant ChunkG as ChunkGenerator
participant FS as FeatureSorter
participant WR as WorldgenRandom
participant PlacedF as PlacedFeature
participant PMod as PlacementModifier
participant CF as ConfiguredFeature
CST->>ChunkG: applyBiomeDecoration — write radius 1, four final heightmaps primed
ChunkG->>FS: featuresPerStep — one sorted list per step, and an index per PlacedFeature
ChunkG->>WR: setDecorationSeed(level seed, chunk corner)
ChunkG->>ChunkG: union the biome palettes of the 3x3 chunks, intersect with possibleBiomes
Note over ChunkG: per step: structures first, then features in sorted index order
ChunkG->>WR: setFeatureSeed(decoration seed, feature index, step)
ChunkG->>PlacedF: placeWithBiomeCheck, from the chunk's minimum corner
PlacedF->>PMod: fold — each modifier flat-maps one position into zero or more
PMod-->>PlacedF: the surviving positions
PlacedF->>CF: place, once per surviving position
CF->>CF: Feature.place — ensureCanWrite checked once, for the origin
```
**The driver.** `ChunkGenerator.applyBiomeDecoration` starts at the chunk's
minimum corner, at the world's minimum Y. There is no eight-block population
offset; the scatter comes later, from a modifier.
**The seed.** A `WorldgenRandom` is built over a genuinely random seed and
then reseeded absolutely, twice, before anything uses it.
`WorldgenRandom.setDecorationSeed` derives a per-chunk seed from the world
seed and the chunk corner, and each feature then gets
`WorldgenRandom.setFeatureSeed` from that seed, its index within the step and
the step number, which the seed multiplies by ten thousand to keep the steps
apart. Features in a step therefore do *not* share a random stream: every one
is reseeded absolutely before it runs, so an extra draw inside a feature
perturbs the rest of *that* feature and nothing after it.
**Who wants what.** The biome palettes of the surrounding 3×3 chunks are
unioned and intersected with the biome source's possible biomes. Every placed
feature any of those biomes lists for this step is collected by its index in
that step's list, and the indices are **sorted** — that sort is the execution order, and
it is the same for every chunk *in that dimension*, because
`ChunkGenerator.featuresPerStep` is memoised per generator and built from
that generator's possible biomes. The Nether's order has nothing to do with
the Overworld's. Within each step, structures at that step are placed before
its features ([structure placement](structure-placement.md)).
## The fold
`PlacedFeature.placeWithBiomeCheck` starts a stream containing exactly one
position — the chunk corner — and flat-maps it through each modifier in list
order. Nothing about that is a filter chain in the usual sense: a modifier
may return nothing, one position, or many.
```mermaid
flowchart TB
A["1 position: the chunk corner, at minimum Y"] --> B["RarityFilter — 1 or 0"]
B --> C["CountPlacement — N copies of the SAME position"]
C --> D["InSquarePlacement — each scattered inside the 16x16"]
D --> E["SurfaceWaterDepthFilter — some drop out"]
E --> F["HeightmapPlacement — Y is finally set, on top of the surface"]
F --> G["BlockPredicateFilter — would a sapling survive here"]
G --> H["BiomeFilter — does the biome HERE want this exact feature"]
H --> I["ConfiguredFeature.place, once per surviving position"]
```
Three things about that chain are load-bearing and none of them is obvious
from a data pack.
**A repeating placement does not scatter.** `CountPlacement`,
`NoiseBasedCountPlacement` and `NoiseThresholdCountPlacement` are
`RepeatingPlacement`s: they emit the *same* position N times, and the scatter
is a separate modifier downstream. List order decides the outcome —
count-then-scatter gives ten trees in ten places, scatter-then-count gives
ten trees in one.
**Y is set late.** Positions travel through most of the chain at the world's
minimum Y; a `HeightmapPlacement` or a `HeightRangePlacement` is what puts
them on the ground, and a chain that forgets one places at the bottom of the
world. `WorldGenRegion.getHeight` returns the stored height **plus one**, so
a heightmap placement lands on top of the surface rather than in it. Two of
vanilla's heightmap presets read the *worldgen* heightmaps, which are not
among the four `ChunkStatusTasks.generateFeatures` primed on the way in.
**The biome is checked twice.** A feature was selected because *some* biome
in the 3×3 wanted it; `BiomeFilter` re-reads the biome at the scattered
position and asks whether *that* biome's generation settings contain this
exact placed feature. Without it every biome would bleed its trees a chunk in
each direction. Vanilla is not consistent about where it goes: the base tree
placement ends with the biome filter and the survival-checked variant appends
its block predicate *after* it, so both orders ship.
The rest of the fifteen modifiers move a position rather than counting or
filtering it: `InSquarePlacement` scatters within the chunk,
`RandomOffsetPlacement` jitters, `EnvironmentScanPlacement` searches up or
down for a surface, `FixedPlacement` names absolute positions. One of the
fifteen fits neither shape: `CountOnEveryLayerPlacement` is deprecated,
extends `PlacementModifier` directly, and does its own scatter and cave-layer
scan inside `PlacementModifier.getPositions`.
## A feature that is a tree of features
Six of the sixty-three registered features write no blocks. `Feature.NO_OP`
is a deliberate nothing; the other five take other placed features and choose
between them, which is how a data pack builds decoration out of decoration
rather than out of algorithms:
`RandomSelectorFeature` walks a weighted list rolling each entry's chance and
falls back to a default; `SimpleRandomSelectorFeature` picks a uniform index;
`WeightedRandomSelectorFeature` draws from a weighted list;
`RandomBooleanSelectorFeature` flips a coin between two;
and `SequenceFeature` places every entry in order and **stops at the first
failure**, reporting failure itself. The plains oak is one of these — a
random selector between a fancy oak, a fallen oak and a plain oak with bees.
All five call `PlacedFeature.place`, not
`PlacedFeature.placeWithBiomeCheck` — which is exactly why a `BiomeFilter`
inside a nested placed feature is an *error* rather than a no-op. The filter
needs the context's top feature, and only the biome-check entry sets it. That
is why the "checked" tree placements carry no biome filter and the
biome-level ones do.
## What a feature may write, and where it may read
`ChunkStatus.FEATURES` is the **only** step in the generation pyramid with a
positive block write radius: one, so a tree may cross into a neighbour
([the chunk generation pipeline](../world/chunk-generation-pipeline.md)). The
terrain steps declare zero and every other step declares minus one, which no
position satisfies — a write from a step that has not declared a radius is
logged and dropped.
Writes and reads are guarded in different places, and the reads reach much
further. `Feature.place` checks `WorldGenLevel.ensureCanWrite` for the origin
once, and then each individual write is re-checked by
`WorldGenRegion.ensureCanWrite`, which logs — and pauses, in a development
environment — and does not write. A canopy that would reach two chunks out is
**truncated**, not moved and not abandoned. A *read* outside the write zone is
only warned about by `WorldGenRegion.warnIfReadOutsideWriteZone` and still
happens. What makes cascading worldgen structurally impossible is one level
further out: `WorldGenRegion.getChunk` **throws** rather than loading once the
request passes the step's declared dependency radius — eight chunks of
chessboard distance at this step — and what it may legally see there is a
chunk at `ChunkStatus.STRUCTURE_STARTS`.
The supporting value types are worth naming because they are handed three
different amounts of world. An `IntProvider` gets a random source and nothing
else. `HeightProvider.sample` and `VerticalAnchor.resolveY` get a
`WorldGenerationContext`, which despite the name is two integers — the world's
minimum Y and its height. Only `BlockPredicate` sees the world itself: it
extends `BiPredicate`, and that is why a placement
can ask what block is under the sapling.
## Questions players ask
**How does a datapack make a world refuse to load?** Feature order is global:
every biome's list contributes "this before that" edges to one graph, and
`FeatureSorter.buildFeaturesPerStep` topologically sorts it. Two biomes
listing the same two features in opposite orders form a cycle, and the sort
throws rather than returning an order — it will even re-run itself, dropping
one source at a time, to name the smallest offending set. Where you find out
depends on which side you are: the **client** calls
`ChunkGenerator.validate` from `WorldOpenFlows` while opening the world,
catches the exception and offers safe mode. A dedicated server never calls
`ChunkGenerator.validate` at all, so there the cycle surfaces later, as a
crash report wrapped around the first chunk that tries to decorate.
**Why does a chunk keep changing after it has decorated?** Because all eight
neighbours write into it when *they* decorate. What makes that safe is not
locking and not the dependency graph: every decoration step in a level runs on
the single-threaded `ConsecutiveExecutor` named *worldgen*, so no two
neighbours are ever inside the centre chunk at once. The dependency graph
fixes the *order* — lighting requires the whole ring to have decorated
first — not the exclusion.
**Does a sapling grow the same way a worldgen tree does?** No — it skips this
whole page. `SaplingBlock.advanceTree` and bone meal run on the **server main
thread** and call `ConfiguredFeature.place` on the `ServerLevel` directly, so
there is no placement chain, no biome filter and no write guard at all
(`WorldGenLevel.ensureCanWrite` is an interface default that is always true).
It also hand-manages the sapling block, which is a story of its own
([trees](trees.md)).
**Is every `Feature` subclass in the registry?** No, and the exception is a
good one: `EndPodiumFeature` is constructed directly by the dragon fight and
appears in no registry at all. Being a `Feature` and being data-driven are
different things.
**Are structures and features really the same step?** They share the step
loop and not the index space, so a structure and a feature at the same index
in the same step draw the *same* feature seed. They also differ in reach: a
structure piece gets an explicit writable box covering exactly the centre
chunk, while a feature gets only the softer 3×3 write-zone check.
## Where to look
`Feature.place` · `ConfiguredFeature` ·
`PlacedFeature.placeWithBiomeCheck` · `PlacementModifier.getPositions` ·
`PlacementFilter` · `RepeatingPlacement` · `CountPlacement` ·
`InSquarePlacement` · `HeightmapPlacement` · `BiomeFilter` ·
`RandomSelectorFeature` · `SequenceFeature` ·
`GenerationStep.Decoration` · `FeatureSorter.buildFeaturesPerStep` ·
`ChunkGenerator.applyBiomeDecoration` · `ChunkGenerator.validate` ·
`WorldgenRandom.setDecorationSeed` · `WorldgenRandom.setFeatureSeed` ·
`WorldGenRegion.ensureCanWrite` · `WorldGenRegion.getChunk` ·
`BlockPredicate` · `HeightProvider` · `VerticalAnchor` ·
`SaplingBlock.advanceTree`
---
*Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes `tools/verify_names.py`.*
---
# Trees
> Verified against **Minecraft 26.2** · Part XII · One sapling grows: five pluggable parts over one algorithm, a ceiling the crown's size was decided before, and the dark-oak sapling that will never grow on its own.
Plant a single dark-oak sapling, feed it bone meal until you run out, and
nothing happens. Nothing is wrong with the sapling; there is simply no tree
for it to become. `TreeGrower` holds up to six configured features per
species — a normal tree and a mega tree, each with a secondary variant, plus
two flowering variants — and `TreeGrower.DARK_OAK` fills in exactly one of
them, the mega tree. The single-sapling slot is left empty, and
`TreeGrower.growTree` finds nothing to place. **The best-known growth rule in
the game is implemented as an absence.**
Which is a good introduction to this page, because the whole tree kit works
like that: the thirty-nine configured tree features are one algorithm,
`TreeFeature`, with five slots
in it, and almost everything you can say about how a cherry differs from a
mangrove is a statement about what is in the slots.
[Features and placement](features-and-placement.md) is how a tree gets a
position and whether it is attempted at all; this is what happens after
`Feature.place` is entered.
## The cast
| class | its slot | what varies |
|---|---|---|
| `TreeFeature` | the algorithm — *final*, one implementation, no subclasses | nothing |
| `TreeConfiguration` | nine fields: five of them are the parts below, three are `BlockStateProvider`s (trunk, foliage, and the dirt column laid under the trunk) and one is the *ignore vines* flag | everything |
| `TrunkPlacer` | writes the logs, returns where crowns hang | 9 registered types |
| `FoliagePlacer` | writes the leaves around one attachment | 11 registered types |
| `RootPlacer` | writes roots, and may lift the trunk off the ground | **1** registered type |
| `FeatureSize` | the clearance profile — a horizontal radius per height | 2 registered types |
| `TreeDecorator` | runs afterwards over what was placed | 10 registered types |
| `FoliagePlacer.FoliageAttachment` | the only channel from trunk to crown: a position, a signed radius nudge, and *is the trunk under me two-by-two* | — |
Every one of those five is a codec-dispatched type in a built-in registry, so
a data pack composes trees freely and cannot add a new *kind* of placer
([the data-driven type pattern](../foundations/data-driven-types.md)).
## One algorithm, five slots
```mermaid
sequenceDiagram
participant TF as TreeFeature
participant RootP as RootPlacer
participant TP as TrunkPlacer
participant FolP as FoliagePlacer
participant TDec as TreeDecorator
participant WGL as WorldGenLevel
TF->>TP: getTreeHeight — two random draws
TF->>FolP: foliageHeight, then foliageRadius — both from the UNCLIPPED height
TF->>TF: build-height check, then getMaxFreeTreeHeight — the clearance scan
Note over TF: clipped below the profile minimum, or none declared, abandons with nothing written
TF->>RootP: placeRoots — false abandons the tree
RootP->>WGL: roots, and the moss above them
TF->>TP: placeTrunk(clipped height)
TP->>WGL: logs
TP-->>TF: a list of FoliageAttachments
loop one per attachment
TF->>FolP: createFoliage(clipped height, attachment, foliageHeight, leafRadius)
FolP->>WGL: leaves, DISTANCE 7 as provided
end
TF->>TDec: place(Context) — logs, leaves and roots, each sorted by Y
TDec->>WGL: hives, vines, podzol, propagules
TF->>WGL: updateLeaves — a bucketed walk that rewrites every DISTANCE
```
Four things in that diagram are the page's real content.
**The crown is sized before the ceiling is measured.** `TreeFeature` samples
the trunk placer's proposed height, derives `FoliagePlacer.foliageHeight` and
`FoliagePlacer.foliageRadius` from *that* number, and only then runs the
clearance scan. The scan's answer, the *clipped* height, is passed on to
`TrunkPlacer.placeTrunk` and `FoliagePlacer.createFoliage` — but the two crown
numbers travel beside it, already decided. It is a real asymmetry that no
shipped tree can express: the only species vanilla lets survive a clipping is
the fancy oak, and `FancyTrunkPlacer` derives its cluster count from the
clipped height, so a clipped fancy oak gets a *smaller* crown, not a bigger
one. The one placer whose foliage height reads the tree height at all is
`SpruceFoliagePlacer`, and no spruce declares a minimum clipped height, so a
spruce under an overhang abandons itself instead.
**Clipping usually kills the tree instead.** `FeatureSize.minClippedHeight`
is the only thing that permits a clipped tree at all, and in vanilla exactly
one tree declares it: the fancy oak, at four. Every other species abandons
itself the moment the scan comes back short. The scan returns *two below* the
first blocked layer, so an obstruction at head height yields a negative
number and nothing survives it.
**The scan asks the trunk placer what "free" means, not the feature size.**
`FeatureSize` supplies only the radius to test.
`TrunkPlacer.isFree` is air, anything in the replaceable-by-trees tag, **or
an existing log** — which is how a new tree grows up through an old one — and
it delegates to a *virtual* `TrunkPlacer.validTreePos`, so
`UpwardsBranchingTrunkPlacer` quietly widens the definition with its own
*can grow through* block set. A vine anywhere in the scanned column also
fails, unless the configuration sets `TreeConfiguration.ignoreVines`.
**Nothing rolls back.** There are three places a tree can abandon itself —
the build-height check, the clipped-height check, and `RootPlacer.placeRoots`
returning false — and all three happen before a single block is written.
After `TrunkPlacer.placeTrunk` begins there is no undo, and `TreeFeature`
reports success even for a tree that was truncated to a stump.
## The nine trunk placers
The base contract is three numbers — `TrunkPlacer.getTreeHeight` is a base
height plus two independent random draws — and one method that writes logs
and returns attachments.
| type | how it differs |
|---|---|
| `StraightTrunkPlacer` | one column, one attachment one block *above* the top log |
| `ForkingTrunkPlacer` | leans near the top, then grows a side branch in a second random direction — and if that direction happens to equal the first, the branch is skipped and the draw is spent anyway. The main fork's attachment carries a radius nudge of +1 |
| `GiantTrunkPlacer` | a 2×2 column, four dirt blocks beneath it, and only the (0,0) column placed on the very top layer. Its attachment sets *double trunk*, as `DarkOakTrunkPlacer`'s does |
| `MegaJungleTrunkPlacer` | the giant trunk, plus side branches laid along a random angle every few levels, each attachment nudged **−2** |
| `DarkOakTrunkPlacer` | a leaning 2×2 trunk whose lean is two minus a draw from three, so two steps, one or none with equal chance, plus a ring of downward log stubs on a one-in-three roll per position — placed relative to the *original* trunk, not the leaned one. Its main attachment sits on the top log rather than above it |
| `FancyTrunkPlacer` | see below |
| `BendingTrunkPlacer` | rises, nudges once, then walks *horizontally* for a sampled bend length — and emits an attachment at every position along the whole arc, including ones where the log was not placed |
| `UpwardsBranchingTrunkPlacer` | a straight column that rolls a probability after each log and, on success, runs a diagonal staircase branch outward, attaching foliage at every branch log. The one placer that widens what counts as free |
| `CherryTrunkPlacer` | one to three branches that random-walk toward a computed endpoint, choosing vertical or horizontal per step by the remaining ratio, with the log axis rotated sideways for the horizontal runs. It derives a **fourth branch-height provider in its constructor that no codec ever sees**, which is why the codec insists the declared range spans at least two blocks |
`FancyTrunkPlacer` is the one worth watching, because it is the only placer
that plans before it writes. It works out a crown position per level from a
circle equation, then walks the line from trunk to crown *twice*: once with
placement switched off, purely to ask whether every block on the way is free,
and again for real only if it was. A branch whose base has slid below the
trunk top is clamped, which is the whole "branches slope down as they go out"
look. And it carries one computation that cannot do anything:
**One** — crown candidates a fancy oak tries per level, always, because the
count is a minimum against one, over an expression that is never below one
(`FancyTrunkPlacer`). The named density constant it multiplies has no effect
on any tree of any height.
## The eleven foliage placers
A foliage placer gets one attachment and three numbers — a height, a radius,
and an offset it samples itself — and its two real degrees of freedom are how
the radius changes with height and which positions inside a row it *skips*.
| type | how it differs |
|---|---|
| `BlobFoliagePlacer` | the plain oak blob: radius tapers by half the row index, corners clipped on a coin flip and always clipped on the row at *y* = 0 |
| `FancyFoliagePlacer` | the blob's subclass, but the skip test is a genuine circle rather than a corner roll |
| `BushFoliagePlacer` | the blob with a much steeper taper — the full row index, not half of it |
| `SpruceFoliagePlacer` | the saw-tooth: a radius that grows a block per row and resets to nothing whenever it reaches a ceiling that is itself climbing. The only placer whose row loop is bounded by the foliage height alone rather than the offset |
| `PineFoliagePlacer` | one cone, and the only placer that overrides `FoliagePlacer.foliageRadius` — it adds a draw scaled by the trunk height on top of the configured radius |
| `AcaciaFoliagePlacer` | not a loop at all: three explicit rows, with a cross cut through the flat plate. Its declared foliage height is a constant zero |
| `DarkOakFoliagePlacer` | two explicit rows, or three or four when the trunk is 2×2, wider with it, and **the only placer that overrides the signed skip test** — it removes the four true corners of the widest row before the signed-to-absolute fold can hide them |
| `MegaJungleFoliagePlacer` | registered as *jungle_foliage_placer*, not *mega_jungle*. A circle plus a hard Manhattan cap that skips anything seven or more blocks out |
| `MegaPineFoliagePlacer` | the only one that iterates absolute world Y, so it can make its taper jagged by widening every other row |
| `RandomSpreadFoliagePlacer` | **never places a row.** It fires a configured number of shots at a box, each coordinate the difference of two draws, so the leaves cluster toward the attachment and thin out. Its skip test is unreachable dead code, and it ignores the offset the base class sampled for it |
| `CherryFoliagePlacer` | two narrowing cap rows, a stack of full-radius rows, then the only two uses of the hanging-leaves row helper. It punches probabilistic holes: an edge hole on the bottom row, and on wide rows an unconditional corner removal plus a probabilistic diagonal band |
Two of the contract's parameters are dead in all eleven implementations:
`FoliagePlacer.createFoliage`'s tree height, and the `TreeConfiguration` that
`FoliagePlacer.foliageHeight` receives. Nobody reads either.
## Roots, and the tree that plants itself by failing
`RootPlacerType` registers one type. `MangroveRootPlacer` is the only root
placer in the game, and the base class exists for it: `RootPlacer.trunkOffsetY`
is what lifts a mangrove's trunk one to three blocks clear of the mud, and
`AboveRootPlacement` is the moss carpet that lands on top of a root.
It simulates the whole root system before writing anything. Starting from the
trunk position it recurses outward in each of the four horizontal directions;
each step's candidates are *straight down*, *sideways*, or both, depending on
how far out the walk already is and a skew roll. The termination rule reads
backwards: the recursion returns **true only when it has run out of
placeable candidates**, and reaching the maximum root length returns false —
which propagates all the way out and abandons the tree. A mangrove is
therefore planted only where its roots find solid ground before they run out
of length.
One asymmetry inside it: a root that lands in mud is written from the muddy
provider instead, and that branch skips the base implementation entirely — so
**muddy mangrove roots never get their moss carpet.**
## The decorators, and the pass that undoes half of them
`TreeFeature` accumulates four sets as it writes — roots, logs, leaves and
decorations — and hands the first three to each `TreeDecorator` as a
`TreeDecorator.Context`, which sorts all three **ascending by Y**. That sort
is the reason four different decorators can say "the lowest log" and mean it.
A decorator returns nothing, so one that finds no valid spot is
indistinguishable from one that succeeded.
Ten types, in four groups. The ones that hang things off the tree are
`TrunkVineDecorator` and `LeaveVineDecorator` (vine curtains),
`PaleMossDecorator`, `CocoaDecorator` and `AttachedToLeavesDecorator` — that
last one blacklists an exclusion box around each placement so the propagules
cannot crowd each other. The one that *changes* a block already placed is `CreakingHeartDecorator`,
which shuffles the tree's logs and converts one that is completely surrounded
by other logs — a random such log, not the first. `BeehiveDecorator` looks
like a third but is not: it hangs its nest in an air block beside a log, and
populates the block entity with two or three bees on the spot
([block entities](../blocks/block-entities.md)). The ones that write on the
ground around the tree are `AlterGroundDecorator` (the podzol discs under a
mega spruce, which reach several blocks beyond the trunk) and
`PlaceOnGroundDecorator` (leaf litter, over an inflated box). And
`AttachedToLogsDecorator` is used by `FallenTreeFeature` rather than by trees.
Then the last step, and it is the one that reaches furthest.
`TreeFeature.updateLeaves` runs a bucketed breadth-first walk out from the
**log** set and rewrites `BlockStateProperties.DISTANCE` on every block in
the tree's bounding box that has that property. Three consequences follow,
and all three are visible in game. A neighbouring tree's leaves caught inside
the box get rewritten too. Blocks in the prevents-nearby-decay tag report
distance zero and act as extra roots for the walk. And the decoration and
root sets are marked as *occupied* before the walk starts, so a
decorator-placed block or a mangrove root **blocks leaf-distance propagation
through itself**. Anything the walk cannot reach within six steps keeps the
`BlockStateProperties.DISTANCE` of 7 the foliage provider gave it — already decaying — and falls
apart on its first random tick.
## Five species, side by side
| | trunk | foliage | roots | clearance | decorators |
|---|---|---|---|---|---|
| oak | straight, 4 + two draws | blob, radius 2 | — | two layers | — |
| fancy oak | fancy, base 3 | fancy, radius 2, offset **4** | — | two layers, min clipped **4** | — |
| dark oak | dark oak, 6 + draws | dark oak, radius **0** — all the width is hardcoded in the placer | — | three layers | — (pale oak adds moss, and a creaking heart) |
| cherry | cherry, 7, one to three branches | cherry, radius 4, four hole probabilities | — | two layers | — (a 5% bee-nest variant exists) |
| mangrove | upwards branching, per-log branch probability | random spread, 70 shots | mangrove, trunk lifted 1–3 | two layers | vines, propagules, a 1% bee nest |
The columns nobody expects to matter are where the personality lives: dark
oak's configured leaf radius is *zero*, and mangrove's foliage placer is the
one that does not place rows.
## Questions players ask
**Why does a bone-mealed oak sometimes come out enormous?** Because
`TreeGrower` picks between two configured features on a probability — a plain
oak most of the time, a fancy oak one time in ten — and separately checks for
a flower within a 5×3×5 box, which swaps in the bee-nest variants. The same
mechanism is why a spruce sapling grown as a 2×2 is a mega pine half the time
rather than a mega spruce.
**Why does a player-grown pale oak have no creaking heart?**
`TreeGrower.PALE_OAK`'s mega tree is the *bone-meal* variant of the
configured feature, which is the one with no decorators on it at all. The
moss and the heart only arrive on a worldgen pale oak.
**Why does a mangrove propagule grow underwater?** Sapling growth is the
other entry into this machine and it hand-manages the sapling block: for the
single-sapling path it replaces the sapling with whatever the fluid there
would be, so a waterlogged propagule grows into water. The 2×2 path is
stranger — it clears all four saplings with no-update writes and puts them
back if the feature fails.
**Do leaves know which tree they came from?** No. Nothing in the placed tree
records its species; a leaf's only per-block state is
`BlockStateProperties.DISTANCE` and `BlockStateProperties.WATERLOGGED`, the
first written by the feature's own breadth-first pass and the second taken
from what was already in the world. A log carries a `BlockStateProperties.AXIS` the trunk
placer chooses, and that is the whole of it. `FoliagePlacer.tryPlaceLeaf` also refuses to overwrite a leaf a
player placed, by testing the persistent flag.
**Is any of this shared with the rest of decoration?** The clearance idea,
no — it is `TreeFeature`'s alone. But the four *setter* consumers,
the sets they fill and the final shape update are the same machinery
`StructureTemplate` uses to fix block shapes at the edge of a placed
structure, and every block a tree writes goes in with the same flags: update
neighbours, update clients, and *known shape*
([blocks and states](../blocks/blocks-and-states.md)).
## Where to look
`TreeFeature.place` · `TreeConfiguration` ·
`TreeConfiguration.TreeConfigurationBuilder` · `TrunkPlacer.placeTrunk` ·
`TrunkPlacer.getTreeHeight` · `TrunkPlacer.isFree` ·
`FancyTrunkPlacer` · `CherryTrunkPlacer` ·
`UpwardsBranchingTrunkPlacer` · `FoliagePlacer.createFoliage` ·
`FoliagePlacer.shouldSkipLocation` · `FoliagePlacer.FoliageAttachment` ·
`FoliagePlacer.tryPlaceLeaf` · `RandomSpreadFoliagePlacer` ·
`DarkOakFoliagePlacer` · `MangroveRootPlacer.placeRoots` ·
`MangroveRootPlacement` · `FeatureSize.getSizeAtHeight` ·
`TwoLayersFeatureSize` · `ThreeLayersFeatureSize` ·
`TreeDecorator.Context` · `BeehiveDecorator` · `AlterGroundDecorator` ·
`TreeFeatures` · `TreeGrower.growTree` · `SaplingBlock.advanceTree`
---
*Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes `tools/verify_names.py`.*
---
# Structure placement
> Verified against **Minecraft 26.2** · Part XII · A village is decided: a lottery that never looks at the world, a layout that is deferred and then run by the method that deferred it, an absence stored as a hole, and a command that generates chunks to answer a question.
Type `/locate structure village` and one of two things happens. Usually the
answer is instant, from a couple of thousand blocks away, in a direction you
have never been. Occasionally the game stops for a second first. Both come
out of the same machinery, and the difference is a cache.
The reason the fast answer is possible at all is that **whether a village
*could* be here is pure arithmetic on the world seed**. No biome is
consulted, no terrain is sampled, no chunk is read. Divide the chunk
coordinates by a spacing, seed a random source from the level seed and the
grid cell, draw two offsets, and compare. Everything the world has a say
in — the biome, the ground height, whether the layout fits — happens
*afterwards*, and can still say no.
A structure is a thing the generator decides to build **at** a place rather
than **from** it. This page is the framework all sixteen structure types
share: the decision, the caching, the reference scan, the way terrain bends
around it, and the moment blocks are finally written. What builds the pieces
is one of two assemblers — [jigsaw and templates](jigsaw-and-templates.md) for
villages and their relatives, [hand-built structures](hand-built-structures.md)
for the other fifteen types.
## The cast
| class | the decision it owns | when |
|---|---|---|
| `StructureSet` | which structures share a grid, with weights, and which `StructurePlacement` lays that grid out | data pack |
| `StructurePlacement` | where the grid falls. `RandomSpreadStructurePlacement` is the spacing-and-separation lottery; `ConcentricRingsStructurePlacement` is strongholds | world start, then per chunk |
| `ChunkGeneratorStructureState` | which sets are possible in this dimension at all, and the stronghold ring positions | once per world, on the main thread |
| `Structure` | the settings wrapper: allowed biomes, spawn overrides, the decoration step, the terrain adjustment — and `Structure.findGenerationPoint` | `ChunkStatus.STRUCTURE_STARTS` |
| `StructureStart` | the answer: a structure, the chunk it started in, a `PiecesContainer`, a reference count and a cached box | stored on the chunk |
| `StructureManager` | the per-level view of starts and references | worldgen and main thread |
| `StructureCheck` | the presence cache — two caches over a partial-NBT reader — and the thing `/locate` actually asks | **main thread only**, unsynchronised |
| `Beardifier` | how much the terrain bends, as a density term | built with the `NoiseChunk` at `ChunkStatus.BIOMES` |
## Four decisions, on four different clocks
```mermaid
flowchart TB
W["world start, main thread: filter the structure sets to biomes this dimension can host, fire the stronghold ring searches"]
W --> S1["STRUCTURE_STARTS — the lottery, then the layout. No blocks, no neighbours read"]
S1 --> S2["STRUCTURE_REFERENCES — each chunk scans the 17x17 around itself for starts overlapping it"]
S2 --> N["BIOMES and NOISE — the Beardifier is built with the NoiseChunk, and bends the density field"]
N --> F["FEATURES — StructureStart.placeInChunk writes blocks, before that step's features"]
```
The odd thing about that ladder is where it starts. `ChunkStatus.STRUCTURE_STARTS`
is the **second** status a chunk passes through, two before
`ChunkStatus.BIOMES` — so a structure is decided before the biomes and the
terrain it will sit in exist. Everything the structure needs to know about
the world it asks for directly, from the generator, rather than reading it
out of a chunk.
## Which chunk: a grid, and nothing else
`ChunkGenerator.createStructures` walks the possible structure sets. For a
village that means `RandomSpreadStructurePlacement.getPotentialStructureChunk`:
divide the chunk coordinates by the spacing, seed a `WorldgenRandom` from the
level seed, the grid cell and the set's own salt, and draw two offsets inside
the cell. This chunk is the village chunk only if the draw lands exactly
here. `RandomSpreadType` decides whether the draw is uniform or triangular,
and `StructurePlacement.isStructureChunk` adds a frequency roll, a
`StructurePlacement.FrequencyReductionMethod` and a deprecated
`StructurePlacement.ExclusionZone` that lets one set repel another.
Two qualifiers, and both matter. The *other* placement type is not like this
at all: `ConcentricRingsStructurePlacement` positions strongholds by asking
`BiomeSource.findBiomeHorizontal` for real biome positions, on the background
pool, at world start. And even for villages a coarse biome test has already
happened once — when `ChunkGenerator.createState` built the
`ChunkGeneratorStructureState` and dropped every set no biome in this
dimension can host.
## Which structure, and whether the biome allows it
The set has entries with weights, so a second `WorldgenRandom` picks one.
Then `Structure.findValidGenerationPoint` runs
`Structure.findGenerationPoint` and filters it through
`Structure.isValidBiome` — **the biome is sampled at the proposed point,
after the lottery has already chosen the chunk.**
If that fails — most often the biome, but also an empty start pool, a missing
start jigsaw, or a structure that would sit too close to the world height
limits — the entry is *removed*, its weight subtracted, and the roll
repeated. A chunk on a biome border therefore usually gets *a* village where
a single-candidate set would get none. If every entry fails, the loop drains
and the cell stays empty: the slot still exists, the village does not.
## Whether it is worth laying out
`Structure.findGenerationPoint` does not return pieces. It returns a
`Structure.GenerationStub`, and the stub holds the *child expansion* as an
unexecuted consumer. `Structure.generate` then calls
`Structure.GenerationStub.getPiecesBuilder` on the same line it takes the stub
back, so on the generation path the deferral lasts one statement. What the
deferral is actually for is `StructureCheck.canCreateStructure`, which calls
`Structure.findValidGenerationPoint` and asks only whether the result is
present: the presence question is answered without ever expanding the
children, so the layout is run **once** and never twice.
What is *not* deferred is the centre: the start template, its rotation and
its ground height are all resolved before the stub comes back.
`StructureCheck` is the cache in front of all of this, and it is two caches
over a partial-NBT reader: chunk → structure → **reference count** (which is
what makes "unreferenced only" searches possible), and structure → chunk →
would-generate. On a miss it reads the chunk off disk through
`ChunkScanAccess`, pulling only the data version and the structure starts and
data-fixing that fragment alone. It is main-thread-only and unsynchronised,
which is why `ServerLevel.onStructureStartsAvailable` hops back to the server
thread from the worldgen executor to feed it.
**Absence is stored as a hole, not as a marker.** An invalid start is never
written at all: `ChunkGenerator.tryGenerateStructure` calls
`StructureManager.setStartForStructure` only for a valid start, and
`StructureStart.INVALID_START` is dropped on the floor. What lets a partial
scan prove absence is that every saved chunk carries a *starts* compound
unconditionally, empty or not — so a structure simply missing from that map
is a definite "not here". The *INVALID* ids that do turn up in old saves are
legacy, and `StructureCheck` skips them while loading.
## Who needs to know
At `ChunkStatus.STRUCTURE_REFERENCES`, `ChunkGenerator.createReferences` scans
the **17×17 chunk square around each chunk** and records the packed position
of every start whose bounding box overlaps it. Discovery is outside-in: a
village never walks its own pieces to announce itself, and this is why almost
every later step in the generation pyramid requires structure starts within
eight.
The box that scan tests is not always the box the assembler produced.
`Structure.adjustBoundingBox` inflates it by twelve the moment
`TerrainAdjustment` is anything but *none* — and that inflated box is what
the reference scan, `StructureManager.getStructureAt` and the spawn overrides
all see. The margin the beardifier needs is therefore also the margin in
which a village counts as "here" for mob spawning. The 128-block cage that
keeps a 17×17 scan sufficient is enforced when the **data pack loads**, not
when the structure generates: a jigsaw whose maximum distance plus the
terrain margin exceeds `JigsawStructure.MAX_TOTAL_STRUCTURE_RANGE` fails
validation.
## The ground bends, and then the blocks arrive
`Beardifier.forStructuresInChunk` reads those references and turns the nearby
pieces into `Beardifier.Rigid` boxes plus their junctions. It is built with
the `NoiseChunk`, which the *biomes* step creates and the noise step only
reuses, so the beardifier exists a status before the density field it bends. **No blocks are edited.** The flat shelf under a village is
the density field being told to be solid there
([terrain](terrain.md)), and `TerrainAdjustment` picks the shape: only two of
its five values use the kernel the name *beard* refers to — the two beard
modes, where junctions contribute at half the weight of the pieces, which is
where the smooth shoulders under village streets come from. *Bury* and
*encapsulate* use a plain linear distance falloff instead. The *rigid* filter
applies only to jigsaw pieces, which have a projection to test; a
hand-built piece contributes unconditionally. Neither example is a desert
pyramid or a mineshaft, because a structure that names no *terrain_adaptation*
defaults to `TerrainAdjustment.NONE` and is filtered out before its pieces are
looked at — twenty-three of the thirty-four shipped structure files, those two
among them. The hand-built pieces that do reach the branch belong to the stronghold
and the nether fossil.
Then at `ChunkStatus.FEATURES`, `ChunkGenerator.applyBiomeDecoration` places
structures at their declared decoration step, *before* that step's features.
`StructureStart.placeInChunk` derives a reference position from **piece
zero** — piece order is semantic, not cosmetic, and every `PosRuleTest`
measures its distances from that point — and calls `StructurePiece.postProcess`
on every
piece overlapping this chunk's writable area. Every chunk the structure
touches does this with its own box, so a house straddling four chunks is
written in four slices, at four different times, and a piece's
`StructurePiece.postProcess` must be idempotent.
## Questions players ask
**Why does `/locate` sometimes pause?** Because on a cache miss it can drive
world generation, from the server thread. `StructureCheck` re-runs the
start-point and biome test — the grid arithmetic having already produced the
candidate chunk — and on a result of `StructureCheckResult.CHUNK_LOAD_NEEDED`
it loads the chunk to structure starts, **synchronously**, for up to a
hundred expanding rings of grid cells.
**Why do two exploration maps usually not point at the same monument?**
`StructureStart.getMaxReferences` is one, and `ExplorationMapFunction`
defaults *skip_existing_chunks* to true, so a map asks for an *unreferenced*
structure and takes a reference when it finds one — and the reference count is
exactly what `StructureCheck`'s first cache stores. It is a default, not a
guarantee: the three buried-treasure map tables, in shipwrecks and both ocean
ruins, set the flag to false and will happily send two players to the same
chest.
**Do structures override the biome for mob spawning?** Yes, and first.
`ChunkGenerator.getMobsAt` consults `Structure.spawnOverrides` before
`Biome.getMobSettings` ([biomes](biomes.md)), scoped either to the piece or
to the whole start. Nether fortresses are special-cased earlier still, inside
`NaturalSpawner`.
**Which `StructureManager` is which?** There are two unrelated things with
that shape of name, and both live on the level.
`ServerLevel.structureManager` is the starts-and-references view on this
page; `ServerLevel.getStructureManager` returns the `.nbt` template loader
owned by the server ([jigsaw and templates](jigsaw-and-templates.md)). There
is also a second, unrelated `StructureCheck` in the entity-variant package.
**Is there dead code in here?** Some, and it reads as load-bearing.
`PostPlacementProcessor` is referenced by nothing at all, and
`PieceGenerator` and `PieceGeneratorSupplier` are referenced only by each
other. The live post-placement hook is `Structure.afterPlace`.
## Where to look
`Structure.generate` · `Structure.findValidGenerationPoint` ·
`Structure.StructureSettings` · `Structure.adjustBoundingBox` ·
`StructureSet` · `StructurePlacement.isStructureChunk` ·
`RandomSpreadStructurePlacement.getPotentialStructureChunk` ·
`ConcentricRingsStructurePlacement` ·
`ChunkGeneratorStructureState.generatePositions` ·
`ChunkGenerator.createStructures` · `ChunkGenerator.createReferences` ·
`ChunkGenerator.tryGenerateStructure` · `StructureStart.placeInChunk` ·
`StructureStart.INVALID_START` · `StructureManager.startsForStructure` ·
`StructureManager.addReferenceForStructure` · `StructureCheck.checkStart` ·
`ChunkScanAccess` · `Beardifier.forStructuresInChunk` ·
`TerrainAdjustment` · `ChunkGenerator.findNearestMapStructure` ·
`BuiltinStructures` · `BuiltinStructureSets`
---
*Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes `tools/verify_names.py`.*
---
# Jigsaw and templates
> Verified against **Minecraft 26.2** · Part XII · A village assembles itself: pieces that find each other through connector blocks, a priority queue instead of a stack, and a growth limit that works by taking the right pool away.
A village stops somewhere. Follow a street out from the town centre and the
houses run out and the path ends in a stub of dirt path with nothing on it.
Nothing measured the distance and nothing counted the buildings. That stub is
a *terminator*, and it comes from the street pool's **fallback** pool — which
`JigsawPlacement.Placer` appends to the candidate list at **every** depth,
behind the pool the piece actually asked for. A street ends wherever the
street pieces stop fitting. What the depth limit does is stop offering the
asked-for pool at all, so at the limit the fallback is the only thing left.
The edge of a village is a substitution, not a stop condition.
This is the assembler one of the sixteen structure types uses — the jigsaw —
together with the `.nbt` template system that turns each of its pieces into
blocks. Everything *outside* the assembler is
[structure placement](structure-placement.md): the lottery that chose this
chunk, `StructureStart`, the reference scan, the beardifier, and the moment
`StructurePiece.postProcess` is called. The other fifteen types use a
different assembler and reach this page only for the templates
([hand-built structures](hand-built-structures.md)).
## The cast
| class | what it holds | when |
|---|---|---|
| `JigsawStructure` | the start pool, the start jigsaw name, a depth, a start height, a maximum distance and the pool aliases | data pack |
| `StructureTemplatePool` | a weighted list of `StructurePoolElement`s and a **fallback** pool — the two fields its codec has | data pack |
| `StructurePoolElement` | one candidate: a single template, a legacy single, a list, a placed *feature*, or nothing | data pack |
| `JigsawPlacement.Placer` | the assembly loop and its priority queue; the `VoxelShape` of free space travels with each queue entry | `ChunkStatus.STRUCTURE_STARTS`, worldgen worker |
| `JigsawBlock` | the connector, with `JigsawBlockEntity.JointType` deciding whether rotation must match | in the template |
| `PoolElementStructurePiece` | one accepted candidate, with its junctions | in memory until `ChunkStatus.FEATURES` |
| `StructureTemplate` | a parsed `.nbt` file: block palettes, entities, and the jigsaw blocks in it | loaded by `StructureTemplateManager` |
| `StructurePlaceSettings` | rotation, mirror, the chunk box, liquid handling and an **ordered** list of `StructureProcessor`s | per piece, per chunk |
## The trace: a village, from town centre to blocks
```mermaid
sequenceDiagram
participant ChunkG as ChunkGenerator
participant JS as JigsawStructure
participant JPP as JigsawPlacement.Placer
participant STP as StructureTemplatePool
participant PESP as PoolElementStructurePiece
participant STemp as StructureTemplate
ChunkG->>JS: Structure.generate — the lottery already chose this chunk
JS->>JPP: findGenerationPoint, which is JigsawPlacement.addPieces
JPP->>JPP: sample the start height, pick a town centre from the start pool
JPP->>JPP: drop it so its ground level sits on getFirstFreeHeight
JPP-->>JS: a GenerationStub — the children are still a deferred consumer
JS->>JPP: getPiecesBuilder runs it — build a free-space shape around the centre
loop until the priority queue drains, depth within the limit
JPP->>JPP: jigsaw blocks shuffled, then sorted by selection priority
JPP->>STP: the target pool's shuffled templates, then the fallback's
Note over JPP: at the depth limit the target pool is skipped entirely
JPP->>JPP: canAttach — opposed faces, matching names, rotation if aligned
JPP->>JPP: collide the candidate box against the free shape
JPP->>PESP: accept — subtract the box, record a junction on BOTH sides
end
JPP-->>JS: the pieces builder, filled
JS-->>ChunkG: StructurePiecesBuilder.build — a PiecesContainer inside a StructureStart
Note over ChunkG: later, at FEATURES, once per chunk the village touches
PESP->>STemp: placeInWorld, clipped to this chunk's writable area
Note over STemp: processors in order, jigsaw blocks replaced, a fresh loot seed stamped
```
## The pools
A `StructureTemplatePool` is the unit of choice, and its codec has exactly two
fields — all 188 shipped pool files carry those two and nothing else. The
weighted *elements* list is the candidates. The **fallback** pool is a second
pool appended behind them, tried whenever nothing in the first list fits and
the only thing tried at the depth limit. The third thing you might expect on
the pool is not there: `StructureTemplatePool.Projection` is a field of each
*element*, so one pool can mix them. It decides how Y is chosen: *rigid* keeps
the parent piece's vertical offset, and *terrain matching* asks the generator
for the ground height and brings a gravity processor with it.
Five kinds of element can sit in that list. `SinglePoolElement` is one
template. `LegacySinglePoolElement` is the same with an older block-shape
rule. `ListPoolElement` is several placed as a unit. `EmptyPoolElement` is a
deliberate nothing. And `FeaturePoolElement` places a `PlacedFeature`
instead of a template — which is how village trees arrive
([features and placement](features-and-placement.md)).
`PoolAliasBinding` and `PoolAliasLookup` sit on top: one structure can swap
which pool a name resolves to, per placement, resolved once from a positional
random source. Trial chambers are the only vanilla structure that uses it, and
what they swap is which pool the spawners draw their mobs from.
## The assembly loop
`JigsawPlacement.addPieces` builds a `VoxelShape` of free space around the
start piece and hands it to `JigsawPlacement.Placer`. From then on the
algorithm is: take a placed piece, look at its jigsaw blocks, and for each one
try to hang something off it.
Four details in that loop are the ones worth watching.
**It is a priority queue, not a stack.** Children are expanded in order of
the jigsaw block's placement priority, with insertion order breaking ties —
not depth-first — so a pool can insist its connections are made before its
siblings'. `JigsawPlacement.Placer.placing` is that queue.
**Attachment is a name match plus a geometry match.** `JigsawBlock.canAttach`
requires the two jigsaw blocks to face each other and their target names to
agree; an *aligned* joint additionally requires the rotations to match, while
a rollable one does not.
**Collision is against a shrinking shape, not against a list.** Every
accepted piece subtracts its own box from the free-space shape, so the next
candidate is tested against what is genuinely left. A candidate that
intersects is simply not built, and the next one on the shuffled list is
tried.
**A junction is recorded on both sides.** Each connection writes a
`JigsawJunction` into the parent piece *and* the child, which is what lets
`Beardifier` treat junctions as their own terrain contribution rather than
inferring them from the boxes
([structure placement](structure-placement.md)).
## From a piece to blocks
Nothing above has written a block. When `ChunkStatus.FEATURES` finally calls
`StructurePiece.postProcess` on a `PoolElementStructurePiece`, the element
assembles a `StructurePlaceSettings` — the chunk box, the rotation, the
ignore processor, then `JigsawReplacementProcessor`, then the element's own
processor list, then the projection's. `LegacySinglePoolElement`, which is
what every vanilla village piece is, then pops its ignore processor and
re-appends a wider one at the **end** of that list, so for the pieces a player
actually sees the ignore step runs last and drops the template's air as well
as its structure blocks. `StructureTemplate.placeInWorld`
runs every block in the template through
`StructureTemplate.processBlockInfos`.
A `StructureTemplate` is a parsed `.nbt` file: `StructureTemplate.Palette`s
of `StructureTemplate.StructureBlockInfo`, an entity list, and
`StructureTemplate.JigsawBlockInfo` for the connectors. Placing it writes
what falls inside the box, loads block-entity data
([block entities](../blocks/block-entities.md)) and stamps a **fresh loot
seed** into containers rather than a table's contents
([loot tables](../items/loot-tables.md)) — which is why a village chest's
contents are decided when you open it, not when the village generated.
The processors are the interesting layer, because they are shared with the
other assembler and with the structure blocks a player can use. `RuleProcessor`
applies `ProcessorRule`s, and each rule holds **two** block tests with
different subjects: an *input predicate* against the template's own block and
a *location predicate* against the block already in the world. A position test,
a replacement state and an optional block-entity modifier follow, and the
first rule that matches wins.
`BlockRotProcessor` deletes a fraction of the blocks. `GravityProcessor`
drops them to a heightmap. `BlockIgnoreProcessor` skips a named list of
blocks, and its three presets name the structure block, air, or both — never
structure void, which `JigsawReplacementProcessor` handles instead. And `JigsawReplacementProcessor` is the one that cleans up after the
assembler: it swaps each jigsaw block for the state named in its final-state
string, or removes it entirely. **The assembly graph is invisible in the
finished village** unless the debug flag in `SharedConstants` is set.
`StructureTemplateManager` loads templates in a fixed order — the world's
generated directory, then the gametest source, then data packs — and the
folder it looks in is *structure*, singular.
> **For a 1.21-era reader.** The `.nbt` folder is
> *data/<namespace>/structure/*, not *structures/*. The plural
> directory is the one you remember and it is not read.
## Questions players ask
**Why is one village bigger than another?** Because the growth limit is
probabilistic in effect even though the depth cap is fixed. A village's
declared *size* is six, well under the twenty `JigsawStructure.MAX_DEPTH`
allows, and what usually ends a branch is not the cap at all: it is every
candidate in the street pool failing the collision test, which hands the
branch to the fallback's terminators. A street that happens to run downhill
into free space grows further than one that turns back on itself. Villages
also set *use_expansion_hack*, which inflates a candidate's box upward before
the test, so a piece that would fit can be rejected for the children it would
need room for.
**Can I watch the assembler run?** Yes, two ways. The jigsaw *editor* runs
in both directions: `ServerboundSetJigsawBlockPacket` and
`ServerboundJigsawGeneratePacket` let a creative player run the assembler live
against a loaded `ServerLevel`, and `JigsawBlockEntity` syncs its pool, target
and joint back the other way. `/place jigsaw` does the same from a command,
with a pool, a target and a depth. Both are exceptions to "structures cross
the network as ordinary blocks", and there is a third that only a developer
sees: `DebugSubscriptions.STRUCTURES` ships every piece's bounding box to the
client for the debug renderer.
**Why do the same houses appear in different rotations?** Because rotation is
chosen per piece and applied to the block *states* as they are written, not
to a pre-rotated template. Whether a neighbour may differ in rotation is the
joint type's decision.
**Does the layout depend on the terrain?** Yes, wherever a piece is
terrain-matching — which is every village street. Whenever the source or the
target is not rigid, `JigsawPlacement.Placer` asks
`ChunkGenerator.getFirstFreeHeight` for the ground under the source's jigsaw
block and puts the candidate's box there; that box is exactly what the
collision test then tests, so the ground decides what fits. What the assembly
never does is *read a chunk*: `ChunkGenerator.getFirstFreeHeight` samples the
density graph, which is why the whole thing can run at
`ChunkStatus.STRUCTURE_STARTS`, before any terrain has been written.
**What is a village made of, if not blocks?** Until
`ChunkStatus.FEATURES`, a `PiecesContainer` of `PoolElementStructurePiece`s
inside a `StructureStart`, saved to the chunk as NBT and reloaded on demand.
The village is data for its entire generated life and becomes blocks last.
## Where to look
`JigsawStructure` · `JigsawStructure.MAX_DEPTH` ·
`JigsawPlacement.addPieces` · `JigsawPlacement.Placer` ·
`StructureTemplatePool` · `StructureTemplatePool.Projection` ·
`StructurePoolElement` · `SinglePoolElement` · `FeaturePoolElement` ·
`JigsawBlock.canAttach` · `JigsawBlockEntity.JointType` ·
`JigsawJunction` · `PoolAliasBinding` ·
`PoolElementStructurePiece.place` · `StructurePiecesBuilder` ·
`StructureTemplate.placeInWorld` · `StructureTemplate.processBlockInfos` ·
`StructureTemplate.Palette` · `StructureTemplateManager` ·
`StructurePlaceSettings` · `StructureProcessorList` · `RuleProcessor` ·
`ProcessorRule` · `GravityProcessor` · `JigsawReplacementProcessor`
---
*Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes `tools/verify_names.py`.*
---
# Hand-built structures
> Verified against **Minecraft 26.2** · Part XII · A stronghold is generated: a piece grammar written in Java, a graph assembled at an imaginary height and moved down afterwards, and a whole structure thrown away and rebuilt because it had no portal room.
Every stronghold has exactly one end portal. Not usually, not almost always —
exactly one, in every stronghold in every world, and the mechanism is not a
counter or a guarantee. The portal room is weighted heavily, capped at one
placement, and forbidden near the entrance; and if the maze finishes without
one, `StrongholdStructure` **clears the whole builder, adds one to the seed
and generates the entire stronghold again.** It is the only structure in the
game that regenerates itself until it likes the result.
[Jigsaw and templates](jigsaw-and-templates.md) traces a village, and a
village is a jigsaw: pieces come from a data-pack registry and find each
other through connector blocks. That is one of the sixteen structure types.
**The other fifteen use an older assembler that is still the majority of the
code** — 32 classes and about 10,200 lines under
`levelgen/structure/structures`, against roughly 1,300 for the whole jigsaw
package. Strongholds, mineshafts, nether fortresses, ocean monuments,
woodland mansions, end cities, ruined portals, igloos, shipwrecks, ocean
ruins, desert pyramids, jungle temples, swamp huts, buried treasure and
nether fossils are all built this way.
Everything *around* the assembler is shared, and belongs to
[structure placement](structure-placement.md): the lottery, `Structure`,
`StructureStart`, `StructureCheck`, the reference scan, `Beardifier`, and the
per-chunk write. This page is only the part where the pieces come from.
## The idea
There is no pool and no registry of pieces. A piece is a **Java class that
knows how to write its own blocks**, and it grows the structure by
constructing its own neighbours.
| class | its role |
|---|---|
| `StructurePiece` | the base, and the reason the system holds together: a **mutable** `BoundingBox`, an orientation, a mirror, a rotation, a depth, and a piece type |
| `StructurePiece.placeBlock` | the conventional write path — converts to world coordinates, drops anything outside the chunk box it was handed, applies the piece's mirror and rotation *to the block state*, and schedules a tick for whatever fluid is at the position **after** the write. Not a choke point: the structure classes call `LevelWriter.setBlock` on the level directly two dozen times |
| `StructurePiece.BlockSelector` | a stateful per-block state chooser, and the entire visual character of a structure |
| `StructurePieceAccessor` | eleven lines, two methods, and `StructurePiece.findCollisionPiece` is a **linear scan returning the first overlapping box**. There is no spatial index |
| `StructurePiecesBuilder` | accumulates the pieces, and can move all of them vertically at once |
| `TemplateStructurePiece` | the bridge to the `.nbt` machinery, for structures that are procedural in *layout* and templated in *content* |
| `ScatteredFeaturePiece` | the base for one-shot surface buildings, with two ground-finders |
| `SinglePieceStructure` | the forty-line `Structure` that places exactly one of those |
Three things about that base class do most of the work.
**Orientation is not independent of mirror and rotation.**
`StructurePiece.setOrientation` derives both from the facing direction, and a
south-facing piece is expressed as a **left-right mirror** rather than a
180° rotation. That trick is why every piece in this package is written once,
in a north-facing local frame, and comes out correct four ways.
**Local Y is measured from the box floor.** `StructurePiece.getWorldX`,
`StructurePiece.getWorldY` and `StructurePiece.getWorldZ` map local
coordinates into the world, and because Y is relative to the floor, moving a
finished graph vertically is free. When the orientation is null the transform
is the identity, which is how `BuriedTreasurePieces` gets away with a
bounding box one block wide.
**`StructurePiece.addChildren` is not a framework hook.** Its default body is
empty and nothing in the framework ever calls it; every call site is a
structure's own generation code. The recursion is arranged by each family for
itself, in one of two shapes. Strongholds and nether fortresses use a
**shuffled work queue**: a new piece goes into the builder *and* onto the
start piece's pending list, and the structure drains that list by repeatedly
removing a **random** index and expanding it, so growth is breadth-ish and
unbiased. Mineshafts use **inline recursion** and expand each new piece
immediately, so the first branch of a crossing is fully grown before the
second is attempted.
The vocabulary a piece writes with is the rest of the base class:
`StructurePiece.generateBox` fills a local box while distinguishing edge
cells from interior ones, and `StructurePiece.generateAirBox`,
`StructurePiece.generateMaybeBox`,
`StructurePiece.generateUpperHalfSphere`,
`StructurePiece.fillColumnDown` and `StructurePiece.createChest` are the
rest. `StrongholdPieces.SmoothStoneSelector` is the canonical block selector:
on a box edge it rolls cracked, mossy or infested stone brick and otherwise
plain, and interior cells become cave air. One small object is the whole look
of a stronghold. `JungleTemplePiece.MossStoneSelector` is the other one.
## The trace: a stronghold
All of this runs at `ChunkStatus.STRUCTURE_STARTS`, inside the same
`Structure.GenerationStub` consumer the jigsaw assembler runs in — so the
whole graph is built in memory, on a worldgen worker, with no world access
and no blocks written. One structure skips the consumer:
`MineshaftStructure.findGenerationPoint` hands the stub a builder it has
already filled, which is the only *Either.right* in the game.
```mermaid
sequenceDiagram
participant ChunkG as ChunkGenerator
participant SStr as StrongholdStructure
participant SPie as StrongholdPieces
participant SPB as StructurePiecesBuilder
participant SStart as StructureStart
ChunkG->>SStr: Structure.generate — findGenerationPoint, then the stub
loop until a portal room exists
SStr->>SPB: clear
SStr->>SStr: setLargeFeatureSeed(world seed plus the try counter, chunk)
SStr->>SPie: resetPieces — the static weight table and the imposed piece
SStr->>SPie: a start room, then addChildren on it
loop drain the pending list at a random index
SPie->>SPie: pick by weight, reject the previous type, five attempts
SPie->>SPB: findCollisionPiece — a linear scan of what is placed
SPB-->>SPie: free, so construct it — or a hit, so try the next candidate
SPie->>SPB: addPiece, and append to the pending list
end
SStr->>SPB: moveBelowSeaLevel — shift every piece at once
end
SPB-->>SStart: build — a PiecesContainer, then a StructureStart
Note over SStart: at FEATURES: postProcess, once per chunk each piece overlaps
```
**It is built at an imaginary height.** The start piece is constructed at a
fixed Y — sixty-four for strongholds and fortresses, fifty for mineshafts —
with no idea where the ground is. Every collision test, every staircase
descent and the floor guard that refuses a box below Y 10 happens in that
frame. Only afterwards does `StructurePiecesBuilder.moveBelowSeaLevel` shift
the whole graph so its top sits below sea level. Nether fortresses use
`StructurePiecesBuilder.moveInsideHeights` to land in a band, and a mesa
mineshaft uses `StructurePiecesBuilder.offsetPiecesVertically` to sit between
sea level and the surface. This is the payoff for local-Y-from-the-floor.
**Growth stops when the budget is spent, not when the depth runs out.**
`StrongholdPieces.STRONGHOLD_PIECE_WEIGHTS` pairs each piece class with a
weight *and* a maximum placement count: corridors and turns are unlimited, a
room crossing may appear six times, a library twice, a portal room once — and
the library and portal room additionally refuse to appear before a certain
depth. The picker makes up to five weighted attempts, rejecting whichever
type was placed immediately before, and falls back to a filler corridor. The
depth cap is fifty, far more than any real stronghold reaches; what actually
ends generation is the picker returning nothing once every limited type has
hit its limit.
**Collision is the other brake, and some pieces negotiate.** Each candidate
constructor computes its box and asks
`StructurePieceAccessor.findCollisionPiece`; a hit means the candidate simply
is not built. A mineshaft corridor tries decreasing lengths until one fits,
and a stronghold library falls back from its tall variant to its short one.
## The four families
| family | how the pieces come to exist | members |
|---|---|---|
| **procedural piece graphs** | the pieces write their own blocks and construct their own neighbours — the pattern in its pure form | `StrongholdPieces`, `MineshaftPieces`, `NetherFortressPieces` |
| **grid and graph solvers** | a layout is *solved* first and pieces are emitted afterwards, so neither ever calls `StructurePieceAccessor.findCollisionPiece` — the layout **is** the collision guarantee | `WoodlandMansionPieces`, `OceanMonumentPieces` |
| **template-backed pieces** | procedural placement, `.nbt` content, and therefore the same processors and the same `StructureTemplate.placeInWorld` the jigsaw path uses | `EndCityPieces`, `RuinedPortalPiece`, `OceanRuinPieces`, `ShipwreckPieces`, `IglooPieces`, `NetherFossilPieces`, `WoodlandMansionPieces` |
| **one-shot surface buildings** | no graph and no children: one box, dropped on the ground, over `ScatteredFeaturePiece` | `DesertPyramidPiece`, `JungleTemplePiece`, `SwampHutPiece` |
The nether fortress is the most elaborate of the first family: it runs *two*
weight tables and a mode switch, where a castle entrance is a one-way door
out of bridge mode into castle mode, and only a T-balcony can fall back, on a
one-in-eight roll per branch.
Almost nothing here is data-driven, and that is the point. Piece choice,
weights, budgets, layout rules and adjacency are all Java.
`Registries.STRUCTURE` still supplies the settings wrapper, and the templated
families read `.nbt` files, but **a data pack cannot add a room to a
stronghold.**
## Where the families bend the idea
**The mansion is grown and then tidied to a fixed point.** Corridors are
recursed out from the entrance on an 11×11 grid, rooms are stamped alongside
them, and then an edge-cleaning pass runs **repeatedly until nothing
changes**, filling any cell with enough occupied neighbours. That pass is why
a mansion is a solid block of building rather than the thin maze the corridor
walk actually produced. Rooms are then greedily merged into 2×2, 1×2 and 1×1
units, with type, id and flags packed into a single integer per cell — and a
room that ends up with no corridor edge becomes a **secret room**, reachable
only from above. A mansion may also have two floors instead of three: the
third needs a second-floor room with a door to hang its staircase on, and if
there is none, or no free direction to grow into, the third-floor grid is
blanked entirely.
**The ocean monument carves its maze backwards.** It wires a lattice of rooms
fully connected, then repeatedly closes a random opening and **keeps the
closure only if both sides can still reach the entrance room**, using a
depth-first reachability walk with an increasing scan counter in place of a
visited set. Rooms are then fitted by a list of room-shape fitters in fixed
order, first match wins, so the large double rooms get first refusal and the
plain room is the fallback.
**End city sections collide as groups, not as pieces.** Each candidate
section is generated into a scratch list and tagged with one shared random
`StructurePiece.genDepth` — used as a **group identity, not a depth**. The
section is accepted only if every collision it finds is with a piece carrying
the *parent's* tag; one foreign overlap discards the entire candidate list
atomically. Bridges opt out with a tag of minus one, and the ship becomes
likelier the longer the bridge gets, with at most one per city.
**Ruined portal decay is a processor stack, not code.** The rot, the
gold-block gaps, the lava-to-magma substitutions and the mossiness are
`StructureProcessor`s assembled per portal and stored in the saved piece, so
decay reproduces exactly on reload. Only some of that stack is shared with the
jigsaw path ([jigsaw and templates](jigsaw-and-templates.md)):
`BlockAgeProcessor` — which is the mossiness, not a separate step —
`LavaSubmergedBlockProcessor` and `BlackstoneReplaceProcessor` are built in
`RuinedPortalPiece` and appear in no data pack at all. The forty shipped
processor lists between them use four types: rule, protected blocks, block rot
and capped.
## Questions players ask
**Does `/locate stronghold` point at the portal?** No — at the corner of the
start chunk. `ChunkGenerator.findNearestMapStructure` returns
`StructurePlacement.getLocatePos`, which is the chunk's minimum block plus the
placement's own offset, and the eye of ender takes the same answer. The
stronghold *does* keep a portal-room pointer —
`StrongholdPieces.StartPiece.getLocatorPosition` overrides the base method to
return it — but nothing in 26.2 calls that method. What the pointer is really
for is the regeneration loop's exit condition: the portal room's entire
`StructurePiece.addChildren` body is a record of itself on the start piece.
**Would two strongholds generating at once interfere?** In principle, yes,
and visibly so. `StrongholdPieces` keeps its remaining-piece list, its
running weight total and a one-shot "force this piece next" override in
**private static fields**, reset by `StrongholdPieces.resetPieces` from
inside a generation lambda that runs on chunk workers. The nether fortress's
placement counters live on static array elements merely reset at start-piece
construction, so its per-structure budget is an illusion. It is rare enough
not to bite, and it is the sharpest contrast with the stateless jigsaw path.
**Why do some structures come back different after a reload?** One does.
Ocean monument room pieces are held privately on the main building, never
reach the builder, are therefore never saved — and their save method is empty
anyway. `StructureStart.loadStaticStart` carries a hardcoded type check that
calls `OceanMonumentStructure.regeneratePiecesAfterLoad`, which reads position
and orientation from the save and rebuilds every room from the world seed.
Every other structure deserialises what it wrote.
**Is a saved bounding box where the structure is?** Not always. Buried
treasure rewrites its own box while placing; igloos are built at a hardcoded
Y 90 and re-seated at write time from the live heightmap, then put *back*;
shipwrecks latch a flag so the second chunk does not move them again. For
those types the persisted box is a placement hint, not a location. Two pieces
go further and deliberately **widen the chunk they were given** — a ruined
portal and a nether fossil both encapsulate the writable area so they are
placed whole from a single chunk rather than sliced across several. Since
`BoundingBox` is mutable and shared between the pieces of one start, that
widening leaks; harmlessly today, because both structures have exactly one
piece.
**Does a hand-built template know about jigsaw blocks?**
`TemplateStructurePiece.postProcess` scans what it placed for jigsaw blocks
and replaces each with its final state, so a stray jigsaw block in a mansion
`.nbt` resolves quietly instead of connecting to anything. `Beardifier`'s
projection test is the only place at runtime where the two assemblers are
told apart.
**Is any of this on its way out?** The whole-graph move is.
`StructurePiecesBuilder.moveBelowSeaLevel`,
`StructurePiecesBuilder.offsetPiecesVertically`,
`TemplateStructurePiece.move` and the mansion's siting helper are all marked
for removal, and the jigsaw path has nothing deprecated in it at all. Mojang
has flagged the idiom, not just the methods. Three separate *magic start Y*
constants in this package are also read by nothing — the literals are
retyped at their use sites, which is a trap for anyone changing one. And one
discarded random draw is load-bearing:
`MineshaftStructure.findGenerationPoint` opens by drawing a double and
throwing it away, a random-stream alignment relic that has to stay or every
mineshaft in every existing world moves.
## Where to look
`StructurePiece` · `StructurePiece.addChildren` ·
`StructurePiece.placeBlock` · `StructurePiece.generateBox` ·
`StructurePiece.BlockSelector` · `StructurePiece.setOrientation` ·
`StructurePiece.getWorldY` ·
`StructurePieceAccessor.findCollisionPiece` · `StructurePiecesBuilder` ·
`StructurePiecesBuilder.moveBelowSeaLevel` ·
`StructurePiecesBuilder.moveInsideHeights` · `StrongholdStructure` ·
`StrongholdPieces.STRONGHOLD_PIECE_WEIGHTS` ·
`StrongholdPieces.resetPieces` · `MineshaftPieces` ·
`NetherFortressPieces` · `WoodlandMansionPieces` ·
`OceanMonumentPieces` · `EndCityPieces` · `RuinedPortalPiece` ·
`TemplateStructurePiece` · `ScatteredFeaturePiece` ·
`SinglePieceStructure` · `OceanMonumentStructure.regeneratePiecesAfterLoad` ·
`StructureStart.loadStaticStart`
---
*Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes `tools/verify_names.py`.*
---
# Creating a world
> Verified against **Minecraft 26.2** · Part XII · *Create New World*: a seed typed in, Superflat chosen, a layer deleted, an experiment switched on, and the settings object that comes out the other end.
You click *Create New World* and nothing happens for a moment. Then a
three-tab screen: a name, a game mode, a seed box, a world-type button, and
buttons for game rules, data packs and experiments. You type a seed, cycle the
type to *Superflat*, open *Customize* and delete the dirt layer, switch on an
experiment, set one game rule, and press *Create*.
That opening pause is what this page is about. Before the screen can draw a
single widget the game has already run a **complete server-side data-pack
load** — the same `WorldLoader.load` a dedicated server runs at startup — on a
background thread, with the client's main thread parked on
`BlockableEventLoop.managedBlock` until it finishes. The object the screen
exists to edit, `WorldGenSettings`, was built halfway through that load, out
of registries the load had just filled. Every widget on the *World* tab is an
edit to it — the name, the game mode, the difficulty, Allow Commands and the
game rules go somewhere else entirely, into the `LevelSettings` and the
`GameRules` that ride beside it. Two of the buttons throw the whole load away
and run it again. And *Create* barely
touches it: by the time you press the button, the world's generation settings
have existed in memory for as long as the screen has.
Everything else in [Part XII](README.md) reads that object. This page is where
it comes from, where it goes, and the three different programs that build one.
## The cast
| class | what it owns | its thread |
|---|---|---|
| `WorldGenSettings` | the whole answer: a `WorldOptions` and a `WorldDimensions`, and nothing else. It is a `SavedData` | built on the worker, read on the server thread |
| `WorldOptions` | the seed, *generate structures*, *bonus chest* — four fields, all immutable, each *with*-method returning a new one | — |
| `WorldDimensions` | a map from `LevelStem` key to `LevelStem`, each a dimension type plus a `ChunkGenerator`. Refuses to exist without an overworld | — |
| `WorldLoader` | the data-pack load every path shares, and the seam (`WorldLoader.WorldDataSupplier`) where the caller decides what the settings are | worker, with two hops to the main thread |
| `WorldCreationContext` | the settings *plus* the loaded registries and `ReloadableServerResources` they were parsed against — the screen's whole world | render thread, replaced wholesale |
| `WorldCreationUiState` | the widget-visible state, and the seven listeners that keep the tabs' widgets agreeing with it | render thread |
| `WorldOpenFlows` | every route from the world list into a running server, each one a chain of methods that can interpose a confirmation | render thread |
| `MinecraftServer` | takes the finished object out of the `WorldStem` and hands it to `SavedDataStorage`, which is what puts it on disk | server thread |
## Five stages, and only one of them is the screen
```mermaid
flowchart TB
A["1 · load — WorldLoader.load opens the packs, fills the WORLDGEN registries, then the LEVEL_STEM registry"]
A --> B["2 · decide — the WorldDataSupplier callback builds a WorldGenSettings from the registries just loaded"]
B --> C["3 · finish the load — ReloadableServerResources reads recipes, loot and functions against the dimensions stage 2 chose"]
C --> D["4 · edit — WorldCreationUiState mutates the object, and any data-pack change restarts at stage 1"]
D --> E["5 · commit — bake the dimensions, write level.dat, spin MinecraftServer"]
```
The ordering that matters is stage 2 before stage 3. `WorldLoader.load` takes
the settings-building callback as a parameter and calls it **after** the
worldgen registries and the `Registries.LEVEL_STEM` registry are loaded and
**before** `ReloadableServerResources.loadResources` runs. The registry set
that recipes, loot tables and functions are then parsed against includes the
dimension registry that callback produced. So the seed and the dimension list
are settled before a single recipe is read, and they are settled by a lambda
the *caller* supplied — which is the only reason the client's create screen,
the client's world-opener, the dedicated server and the game-test server can
share one loader.
`RegistryDataLoader.DIMENSION_REGISTRIES` is a list of exactly one registry,
`Registries.LEVEL_STEM`, loaded in its own pass because its entries need every
worldgen registry already in hand. That single-entry list is the *dimension/*
folder of a data pack.
## The object, and what is not in it
`WorldGenSettings` has two fields. `WorldOptions` holds the seed, a
*generate structures* flag, a *bonus chest* flag and a string that only a very
old save carries. `WorldDimensions` holds the map of `LevelStem`s. There is no
world name in it, no difficulty, no game mode, no game rule and no data-pack
list — those are `LevelSettings` and `GameRules`, and
[level data and rules](../../reference/level-data-and-rules.md) says which file
each of them ends up in.
A seed is not a number the box gives you. `WorldOptions.parseSeed` trims the
text, returns nothing at all for an empty string, parses a long if it can, and
otherwise returns the Java string hash of what you typed — which is why a seed
of *glacier* is a seed and a seed of *99999999999999999999* is the hash of
that text rather than the number. And *nothing at all* is not zero:
`WorldOptions.withSeed` turns an absent seed into `WorldOptions.randomSeed`,
one draw from a fresh `RandomSource`. The seed field's responder calls
`WorldCreationUiState.setSeed` on every keystroke, so an empty box is
re-rolling a new random world every time you touch it.
> **For a 1.21-era reader.** The seed has left *level.dat*. `WorldGenSettings`
> extends `SavedData` and carries its own `SavedDataType`, so it is written to
> *data/minecraft/world_gen_settings.dat* beside *raids.dat* — and the game rules to
> *data/minecraft/game_rules.dat* — by the ordinary saved-data machinery rather than by
> the level-data writer. `PrimaryLevelData` keeps the old key name only as the
> constant `PrimaryLevelData.OLD_WORLD_GEN_SETTINGS`.
## Every widget is an edit to a live object
`WorldCreationUiState` is not a form. It holds the `WorldCreationContext`
itself, rebuilds it on almost every change, and then walks a listener list so
that each widget re-reads what it should now show. The state is also opinionated
about what it returns: `WorldCreationUiState.getDifficulty` reports *hard* in
hardcore whatever the button last set, `WorldCreationUiState.isAllowCommands`
reports true in a debug world and false in hardcore, and
`WorldCreationUiState.isBonusChest` reports false in both. The buttons are
disabled to match, but the state would lie to them anyway.
The world-type button is the destructive one. `WorldCreationUiState.setWorldType`
calls `WorldPreset.createWorldDimensions` and replaces **all** the dimensions
with the preset's, so a trip through *Superflat* and back to *Default* discards
every layer you edited. The button cycles the *normal* world-preset tag — five
presets in 26.2 — and holding Alt swaps it for the *extended* tag, which is the
same five plus *debug_all_block_states*. Seven world presets ship as JSON under
*data/minecraft/worldgen/world_preset/*; the seventh,
`WorldPresets.FLAT_ALL_DIMENSIONS`, is in neither tag and appears on no button:
the only thing that ever selects it is `CreateWorldScreen.testWorld`, behind a
*TW* button the title screen adds when `SharedConstants.IS_RUNNING_IN_IDE`.
*Customize* is rarer than it looks. `PresetEditor.EDITORS` is a two-entry map:
`WorldPresets.FLAT` opens `CreateFlatWorldScreen` and
`WorldPresets.SINGLE_BIOME_SURFACE` opens `CreateBuffetWorldScreen`. For the
other five presets the button is inactive. Both editors end the same way, in
`WorldCreationContext.DimensionsUpdater` lambdas that call
`WorldDimensions.replaceOverworldGenerator` — the overworld only. Nothing in
the create screen can edit the nether or the end.
## The layer editor edits the generator you already have
`FlatLevelGeneratorSettings` is the odd object in a part where everything else
is a record. Its layer list is mutable, its *lakes* and *features* flags are
set by void methods, and `FlatLevelGeneratorSettings.getLayersInfo` hands out
the live list. `PresetEditor` passes `CreateFlatWorldScreen` the settings of
the current overworld generator when that generator is already a
`FlatLevelSource` — the same object, not a copy — and the *Remove Layer* button
removes an entry from that list directly and calls
`FlatLevelGeneratorSettings.updateLayers`.
So **the *Cancel* button on the layer editor does not undo a layer deletion.**
Cancel only skips the `WorldCreationContext.DimensionsUpdater` that would build
a new `FlatLevelSource`; the list it would have been built from has already
changed. The *Presets* screen is the well-behaved half of the same screen:
`PresetFlatWorldScreen` reads and writes the layer stack as a text string and
hands back a *new* settings object through
`FlatLevelGeneratorSettings.withBiomeAndLayers`. Nine flat presets ship as
JSON, one for each key `FlatLevelGeneratorPresets` registers a value for; the
tenth key, `FlatLevelGeneratorPresets.TEST_WORLD`, is declared, never given a
value, and read by nothing in the game.
One thing the flat generator does not do is place all its own blocks.
`FlatLevelGeneratorSettings.adjustGenerationSettings` walks the built layer
stack and, for every layer whose block fails
`Heightmap.Types.MOTION_BLOCKING`'s opacity test, replaces it with a null and
re-adds it as an inline `Feature.FILL_LAYER` placed feature in the
*TOP_LAYER_MODIFICATION* decoration step. That test is *blocks motion or holds
a fluid*, so the water in *Water World* stays terrain; what leaves it are the
air of *The Void* and the snow layer on top of *Snowy Kingdom*, which arrive
as [features](features-and-placement.md).
## An experiment is a data pack, so switching one on reloads everything
`ExperimentsScreen` looks like a toggle list and is a filtered pack browser: it
walks the repository's available packs and keeps only those whose
`Pack.getPackSource` is `PackSource.FEATURE`. Three ship in 26.2 —
*minecart_improvements*, *redstone_experiments* and *trade_rebalance* — one per
non-vanilla flag in `FeatureFlags`. Pressing *Done* rewrites the repository's
selection and lands in `CreateWorldScreen` exactly where the data-pack screen
lands, in `CreateWorldScreen.tryApplyNewDataPacks`.
That method has a fast path and a slow one. If the enabled-pack list and the
feature set both come back unchanged, `WorldCreationUiState.tryUpdateDataConfiguration`
swaps the configuration in and nothing reloads. Otherwise
`CreateWorldScreen.applyNewPackConfig` puts a *validating* message on screen and
runs `WorldLoader.load` again from the top — and it has to carry your settings
across a registry set that is about to be replaced. It does that by
**serialising them**: `WorldGenSettings.CODEC` encodes the current options and
dimensions to JSON using the old registries as context, and re-parses that JSON
against the new ones. Every `Holder` in the object — every biome, every noise
settings, every structure set the flat generator overrides — is written out as
an id and looked up again. If the new packs have no world preset or no biome,
or the re-parse fails, the future completes exceptionally and the player gets a
retry-or-reset confirmation instead of a screen.
The two routes into that method differ in one boolean.
`CreateWorldScreen.tryApplyNewDataPacks` shows
`ConfirmExperimentalFeaturesScreen` only when the requested flags are
experimental **and** the caller was the data-pack screen. Toggling an experiment
in the Experiments screen skips it — that screen carries a red warning line of
its own instead.
Data packs added here do not go into a world folder that does not exist yet.
`CreateWorldScreen.getOrCreateTempDataPackDir` makes a temporary directory
prefixed *mcworld-*, the pack browser is pointed at that, and the directory is
copied into the new world's *datapacks* folder by
`CreateWorldScreen.createNewWorldDirectory` at the very end. It is then deleted
on **every** exit including that one: `CreateWorldScreen.removeTempDataPackDir`
runs on the line after the create callback returns.
## What *Create* does
```mermaid
sequenceDiagram
autonumber
participant CWS as CreateWorldScreen
participant WCUS as WorldCreationUiState
participant WOF as WorldOpenFlows
participant MC as Minecraft
participant MS as MinecraftServer
participant Disk as Disk
Note over CWS,Disk: render thread
CWS->>WCUS: read the context one last time
WCUS-->>CWS: WorldOptions and the selected WorldDimensions
CWS->>CWS: WorldDimensions.bake into a frozen LEVEL_STEM registry, then allRegistriesLifecycle plus the feature flags'
CWS->>WOF: confirmWorldCreation with that lifecycle
WOF-->>CWS: proceed, or an experimental or deprecated warning first
CWS->>Disk: create the world directory, copy the temp datapacks in
CWS->>WOF: createLevelFromExistingSettings with the WorldStem parts
WOF->>MC: doWorldLoad
MC->>Disk: saveDataTag writes level.dat through a temp file
Note over MC,MS: MinecraftServer.spin builds the server on the render thread, then starts the Server thread
MC->>MS: new IntegratedServer with the WorldStem and the screen's GameRules
MS->>MS: savedDataStorage.set marks WorldGenSettings dirty
Note over MS,Disk: server thread, first save
MS->>Disk: data/minecraft/world_gen_settings.dat and data/minecraft/game_rules.dat
```
Three details in that order are worth stopping on. `CreateWorldScreen.onCreate`
bakes the dimensions to decide the *lifecycle* and the
`PrimaryLevelData.SpecialWorldProperty`, but the `WorldGenSettings` it stores
holds the **unbaked** selection — the bake is what runs, the selection is what
is saved. The warning is skipped when the world is not a re-create and the baked
registries are stable, and `WorldDimensions.checkStability` asks, per key,
whether the built-in three carry the vanilla dimension type, the vanilla noise
settings **and** the vanilla biome source — anything under a fourth key is
experimental by construction. Not every shipped preset passes: *Flat (all
dimensions)* gives the nether and the end flat generators, which fail on the
noise settings, so this warning does come from a world type as well as from
data packs. And *level.dat* is written by
the client, in `Minecraft.doWorldLoad`, **before the server thread exists** —
while the settings file is written by the server after it starts, because
`MinecraftServer`'s constructor is the first thing to hand the object to
`SavedDataStorage`.
The game rules take a third path again. `CreateWorldScreen.onCreate` copies the
screen's `GameRules` into an `Optional` that travels through
`CreateWorldCallback`, `WorldOpenFlows.createLevelFromExistingSettings` and
`Minecraft.doWorldLoad` to the `MinecraftServer` constructor, which builds a
fresh rule set from the saved-data default and then overlays the screen's
values on top.
## The same object, from a properties file
The dedicated server never sees a screen, and the comparison is the clearest
way to see which parts of this page are the subject and which are its interface.
| | client create screen | dedicated server | *Re-Create* |
|---|---|---|---|
| who builds it | `CreateWorldScreen.onCreate` | `Main.createNewWorldData` | `WorldOpenFlows.recreateWorldData` |
| the seed | the seed box, per keystroke | *level-seed*, once, in the `DedicatedServerProperties` constructor | copied from the old world's settings |
| the dimensions | a `WorldPreset` plus screen edits | *level-type* as a world-preset id, with *default* and *largebiomes* as legacy aliases | the old world's saved `LevelStem` map |
| customising | `PresetEditor`, overworld only | *generator-settings* JSON, parsed by `FlatLevelGeneratorSettings.CODEC` **and only when the preset is** `WorldPresets.FLAT` | the create screen again |
| game rules | `WorldCreationGameRulesScreen` | one, and it is a legacy key: *announce-player-achievements* sets `GameRules.SHOW_ADVANCEMENT_MESSAGES` | read back from *game_rules.dat* |
| when | only if the folder is new | only if there is no *level.dat* | always a new folder |
Both seed paths are the same method. `DedicatedServerProperties` calls
`WorldOptions.parseSeed` on *level-seed* and falls back to
`WorldOptions.randomSeed`, exactly as the seed box does — so an empty
*level-seed* draws its random seed the moment the properties file is parsed,
whether or not a world is about to be created. An unrecognised *level-type* is
a warning in the log and the *normal* preset, not a failure.
*Re-Create* is the interesting column. `WorldOpenFlows.recreateWorldData` reads
the old world with a deliberately **empty** `LevelStem` registry, so the
dimensions come from the saved settings rather than from any pack, then hands
`CreateWorldScreen.createFromExisting` a `LevelSettings` and a context. The
result is a new world folder with the old seed pre-filled. Nothing in the
family edits an existing world's `WorldGenSettings` in place: `EditWorldScreen`
offers a rename, an icon reset, a folder button, a backup and *Optimize World*,
and not one generation setting.
## The rest of the family
`client/gui/screens/worldselection` holds nineteen classes, nine of them
screens. `SelectWorldScreen` is a search box, a `WorldSelectionList` and six
footer buttons; the list's rows are `LevelSummary` objects read by
`LevelStorageSource.readLightweightData`, an NBT parse that deliberately skips
the *Data/Player* and *Data/WorldGenSettings* subtrees so that listing a
hundred worlds never costs a settings parse. `WorldOpenFlows.openWorld` is a
chain of eight methods — itself, then level data, version compatibility, the
world stem, stem compatibility, a bundled resource pack, disk space, and
finally `Minecraft.doWorldLoad` — each of which can stop and put a
confirmation screen in the way. `OptimizeWorldScreen` and `FileFixerProgressScreen` are the
progress bars over save migration, which
[this book does not cover](../anatomy/what-this-book-skips.md).
## Where to look
Start with `net/minecraft/world/level/levelgen`: `WorldGenSettings` is fifty-one
lines and tells you the whole shape, `WorldOptions` and `WorldDimensions` are
the two halves, and `WorldDimensions.bake` is the method that turns a selection
into a registry. Then `net/minecraft/server/WorldLoader` — one method, and the
spine every path shares. Only then
`net/minecraft/client/gui/screens/worldselection`, in the order
`WorldCreationContext`, `WorldCreationUiState`, `CreateWorldScreen` and
`WorldOpenFlows`, with `net/minecraft/client/gui/screens/CreateFlatWorldScreen`
and `net/minecraft/world/level/levelgen/flat` beside them. The comparison is
`DedicatedServerProperties.createDimensions` and `Main.createNewWorldData`, and
the destination is the first forty lines of the `MinecraftServer` constructor.
---
*Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes `tools/verify_names.py`.*
---
# XIII · Commands and data packs
> Verified against **Minecraft 26.2** · Part XIII · a string typed into a chat box becomes a call with typed arguments, on a queue, with a permission attached — and four whole systems are built on top of that and nothing else.
Type a slash. The text turns grey and green and red as you type, a hint
appears behind the cursor, and pressing Enter sends the *string* — the
parse the client just did is thrown away. On the server the same string is
parsed again, against a tree whose nodes carry permission requirements, and
becomes a piece of work on a queue rather than a Java call. Everything else
in this part rides that machinery: an advancement is a subscription
delivered by a trigger and edited by `/advancement`, a scoreboard is a
number written by `/scoreboard` or by `execute store`, a dialog is a
data-pack form opened by `/dialog`, and a game test is a data-pack test run
by `/test`. **None of those four needs any of the others.** What they need
is the parse and the queue, and a reader who has those two can explain any
of the four from them.
Counting the nine packages [the atlas](../../maps/packages.md#where-each-part-lives)
lists for this part, the way it counts everything else, that is
**470 classes and 43,126 lines** — of which the command
catalogue alone (`net/minecraft/server/commands`) is 102 classes and 12,800
lines, each of them a thin lambda over machinery some other part of this
book owns. So what a command *does* once dispatched is almost always another
part's page, and [Brigadier and commands](brigadier-and-commands.md) carries
the list of which; the statistics, which are criteria, are in [what this
book skips](../anatomy/what-this-book-skips.md).
## The shape of the part
Part XIII is **a stack of three floors**, and for a watcher the dependency
runs one way: all four systems on the top floor need both of the floors
below, and none of them needs another. The code is less tidy than the
lecture order — a selector's *advancements=* and *scores=* options reach
straight up into two of the top-floor systems — but nothing on the top floor
reaches sideways.
```mermaid
flowchart TB
subgraph P["PARSE — a string becomes a call"]
direction LR
L1["1 · Brigadier and commands"] --- L2["2 · Permissions"] --- L2b["3 · Entity selectors"]
end
P --> X
subgraph X["EXECUTE — the call becomes work on a queue"]
direction LR
L3["4 · The execution engine"] --- L4["5 · Functions and macros"]
end
X --> U
subgraph U["WHAT COMMANDS ARE FOR — four systems whose write surface is a command"]
direction LR
L5["6 · Advancements"]
L6["7 · Scores, teams and stored data"]
L7["8 · Dialogs"]
L8["9 · Game tests"]
end
```
The four pages on the top floor are peers, not a sequence: watch them in any
order, or only the ones you care about. The two floors below them are not
optional for any of the four.
## Before you start
[The server tick](../server/server-tick.md#what-minecraftservertickchildren-runs-and-in-what-order)
from Part III, because *when* turns out to matter twice: command functions
run near the top of `MinecraftServer.tickChildren`, before any level ticks,
and the **connection** phase — where `ServerGamePacketListenerImpl.tick`
calls `ServerPlayer.doTick` — runs *after* the levels, which is what puts a
periodic advancement trigger one tick behind the packet that should have
carried it.
[Codecs, NBT and JSON](../foundations/codecs-nbt-json.md) and
[the data-driven type pattern](../foundations/data-driven-types.md#the-idea-stated-once)
from Part II. Dialogs and game tests are the pattern's clearest two
instances — a form and a test suite, both reduced to JSON dispatching on a
registry of types — and the pattern page is where that argument is made.
[The connection](../networking/the-connection.md#the-threads-underneath-it)
from Part IX, for the Netty-thread / server-thread boundary that the command
packets cross in two different ways on purpose.
[Contexts and predicates](../items/contexts-and-predicates.md) from Part
VII, if you are here for advancements: a trigger's conditions are loot
conditions, evaluated against a loot context, and that page owns the
machine.
## Watch in this order
1. [Brigadier and commands](brigadier-and-commands.md) — three parsers for
one string, and a tab-completion whose fast path never leaves the
machine. Also: which sixty-two of the four hundred and fifty-nine
argument nodes do leave it, and why they feel like all of them.
2. [Permissions](permissions.md) — the biggest API break in the game since
the flattening. A permission is no longer an integer, an operator does
not have everything, and a permission failure is reported as a typo.
3. [Entity selectors](entity-selectors.md) — a selector is a compiled
query, and eight of its twenty-one options are not filters but the query
plan. Why *@p* crosses dimensions, why *sort=nearest* is what takes your
*limit* away, and why one permission is checked twice.
4. [The execution engine](the-execution-engine.md) — a command engine with
no Java recursion. A fan-out that materialises one player at a time, and
a `/return` that deletes work out of a queue rather than unwinding a
stack.
5. [Functions and macros](functions-and-macros.md) — what a `.mcfunction`
file becomes, in two steps, the second of which usually does nothing.
The one that fails silently every tick, forever.
6. [Advancements](advancements.md) — the game's general-purpose "tell me
when the player does X", built as a per-player subscription table that
shrinks as criteria are met and is rebuilt when one is revoked. The tree
is laid out on the server and shipped.
7. [Scores, teams and stored data](scoreboard-and-data.md) — one number per
thing, one query language for any tag, and the `execute store` seam that
joins them. Why fake players exist.
8. [Dialogs](dialogs.md) — a data pack puts a form on your screen, possibly
before you are in a world at all. The values are read at the moment of
the click and not before.
9. [Game tests](game-tests.md) — the game's own test suite, as a data pack.
The annotations are gone, a batch *is* an environment, and the shipped
jar contains exactly one test.
## Reference this part uses
[Packets](../../reference/packets.md) for this part's own traffic, which is
almost all server → client: the scoreboard has five packets and no
serverbound counterpart at all.
[Registries](../../reference/registries.md) and
[the data-driven type pattern](../foundations/data-driven-types.md) for the
six type registries dialogs and tests dispatch on.
[Loot context parameter sets](../../reference/loot-context-params.md) for the
sets an advancement trigger and an advancement reward run in.
[Diagram lanes](../../reference/lanes.md) for the abbreviations these figures
use, and [the glossary](../../reference/glossary.md) for *Brigadier*,
*selector head*, *world-limited*, *criterion*, *objective*, *macro*, *dialog*
and *game test*.
---
*Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes `tools/verify_names.py`.*
---
# Brigadier and commands
> Verified against **Minecraft 26.2** · Part XIII · You type `/give @p diamond_sword[minecraft:damage=5]` into the chat box: three parsers see that string, two of them throw their answer away — and not one of the completions you accepted along the way left your machine.
Open the chat box and type a slash. Before you have finished the word, the
text is coloured, a grey hint has appeared behind the cursor and a
completion popup is open — and every bit of that was produced by a **real
Brigadier dispatcher running on your own machine**, with real parsers built
against your own registries, from a tree the server sent you when you
joined. The client is not pattern-matching strings. It parses the whole line
on every keystroke, throws the parse away, and sends the string.
Which raises the question this page exists to answer: if the client can
parse the command, why is completing an item id instant and completing a
loot table not? Because the tree the client rebuilt says, node by node, who
is allowed to answer. Vanilla registers **459 argument nodes**, and only
**62** of them serialise as *ask the server* — so the local path is the
rule, not the exception. What makes the round trip feel ubiquitous is
*which* nodes take it: they are the ones that complete over data the client
was never sent.
## The cast
| class | what it decides | side |
|---|---|---|
| `Commands` | the one server-side `CommandDispatcher`, every registration, and what a failed parse is called | server |
| `CommandSourceStack` | *who is running this, from where* — position, rotation, level, entity, permissions, output sink. Immutable; a change returns a copy | server |
| `CommandBuildContext` | the registries an argument type parses against, so a data pack's biome is completable with no code change | both |
| `ArgumentTypeInfos` | the wire description of an argument type: a `ArgumentTypeInfo.Template` that can be written to a buffer and instantiated on the far side | both |
| `SuggestionProviders` | the three named providers a node may ask for. Everything else serialises as *ask_server* | both |
| `ClientSuggestionProvider` | the client's source: the tab list, the looked-at block and entity, and the one method that sends a packet | client |
| `CommandSuggestions` | the 688-line widget over it — the highlighter, the usage hint, the popup and the parse cache | client |
| `BrigadierExceptions` | installed once into Brigadier's global exception provider, which is why a parse error is a translatable `Component` | both |
Brigadier itself — the dispatcher, the tree of literal and argument nodes,
each with a requirement predicate and optionally executable — is Mojang's
parsing library and lives outside the game's packages. Everything on this
page is what Minecraft builds on top of it.
## Three parsers see one string
```mermaid
sequenceDiagram
participant CSug as CommandSuggestions
participant CPL as ClientPacketListener
participant CSP as ClientSuggestionProvider
participant SGPL as ServerGamePacketListenerImpl
participant Cmds as Commands
participant GC as GiveCommand
CSug->>CPL: parse the whole line against the client's dispatcher, every keystroke
CSug->>CPL: getCompletionSuggestions on that parse, with CSP as the source
CSP->>SGPL: ServerboundCommandSuggestionPacket — only if the node asks the server
SGPL->>CPL: ClientboundCommandSuggestionsPacket — capped at a thousand, id-matched
CPL->>SGPL: ServerboundChatCommandPacket — the raw string, no signatures for /give
Note over SGPL: the illegal-character check runs on the Netty thread
SGPL->>Cmds: hand to the server thread, then parse again with the player's real source
Cmds->>Cmds: the node requirement is consulted inside the parse
Cmds->>Cmds: performCommand — one queue, limits read from the level's game rules
Cmds->>GC: the registered lambda — resolve the selector, read back the ItemInput
GC->>GC: Inventory.add, then sendSuccess and a broadcast to admins
```
Each arrow is a decision.
**The client parses first, and the parse never leaves the machine.**
`CommandSuggestions.updateCommandInfo` runs the whole string through the
client's dispatcher on every keystroke, and that parse produces the red
underline, Brigadier's smart-usage hint and the completion list. What is
sent is the string.
**Item, block-state and component completion never leaves the machine
either.** `ItemArgument` and `BlockStateArgument` are registered as
**context-aware** singletons, so the client instantiated a real `ItemParser`
and a real `BlockStateParser` against its own registries. Item ids, data
components and block properties complete locally, and so does every argument
type whose suggestion method reads a synced registry.
**A node that asks for suggestions by hand almost always asks the server.**
This is the part the shape of the code invites you to get backwards — not
that the round trip is common, but that opting *in* to a provider is what
costs you one. `SuggestionProviders.getName` returns the registered name for
a `SuggestionProviders.RegisteredSuggestion` and *ask_server* **for
everything else**, so any node whose suggestions come from a plain lambda
serialises as a request. Of the **67** vanilla nodes that attach a provider
at all, five name one of the three registered providers and the other **62**
become *ask_server*. That is how `/function`, `/datapack`, `/bossbar`,
`/scoreboard`, `/team`, `/schedule` and `/whitelist` complete. The other 392
argument nodes attach nothing and fall back to their argument type's own
suggestions — which is why `/give`, the command in the line at the top of
this page, never asks the server anything. A second route reaches the same packet:
`ClientSuggestionProvider.suggestRegistryElements` failing to find a
server-only registry — loot tables, advancements, recipes — and falling
through. Two mechanisms, one packet.
**Replies are matched by id, so a stale answer never flashes.**
`ClientSuggestionProvider.customSuggestion` cancels the in-flight future and
increments a counter;
`ClientSuggestionProvider.completeCustomSuggestions` compares the reply's id
against that counter and drops anything older. The reply itself is
**truncated to a thousand entries, silently** — no marker, no message.
**Enter parses a second time on the client, for one reason.**
`ClientPacketListener.sendCommand` runs `SignableCommand.of` to find out
whether any argument is a `SignedArgument`. `/give` has none, so the plain
packet goes; a `/msg` would take a timestamp, a salt and the last-seen
message set, sign each signable argument and send the signed variant. A
command the player did *not* type — a dialog button, a click event, a sign —
goes through `ClientPacketListener.sendUnattendedCommand` instead and is
parsed twice more before anything is sent
([permissions](permissions.md)).
**The two inbound packets cross the thread boundary differently, on
purpose.** `ServerboundCommandSuggestionPacket` goes through
`PacketUtils.ensureRunningOnSameThread`, so its parse happens on the main
thread. `ServerboundChatCommandPacket` does not:
`ServerGamePacketListenerImpl.tryHandleChat` runs the character-legality
check on the Netty thread — and may disconnect from there — before handing
the body to `MinecraftServer.execute`, calling
`ServerPlayer.resetLastActionTime` on the way past, so a little
`ServerPlayer` state really is written off the main thread. The signed
variant does more still: `ServerGamePacketListenerImpl.handleSignedChatCommand`
unpacks the last-seen message set under a lock, and can disconnect for
chat-validation failure, before the legality check runs. This is the
principle [the connection](../networking/the-connection.md) describes —
cheap validation early — with the honest qualifier that "cheap" here
includes two disconnect paths and one field write.
**The authoritative parse is the server's**, with a `CommandSourceStack`
from `ServerPlayer.createCommandSourceStack` carrying the real permission
set, and Brigadier consults each node's requirement *during* that parse.
Execution then is not a Java call: `Commands.performCommand` flattens the
parse into a context chain and hands it to
`Commands.executeCommandInContext`, which is
[the execution engine](the-execution-engine.md).
**`/give` itself is unremarkable and instructive.**
`EntityArgument.getPlayers` resolves the selector, `ItemArgument.getItem`
hands over the `ItemInput` that was built during *parsing*,
`ItemInput.createItemStack` validates it, and the stacks go through
`Inventory.add` with anything that will not fit dropped on the floor
([items and stacks](../items/items-and-stacks.md)). Success goes to
`CommandSourceStack.sendSuccess` — which takes a *supplier*, so the message
is never built when nobody will see it — and broadcasts to admins under two
game rules.
## Arguments that are recipes, not values
Three argument families do not produce a value at all. They produce
something evaluated against the `CommandSourceStack` at run time, which is
why one parsed command means different things at different links of an
`/execute` chain.
**`Coordinates` holds relativity, not a position.**
`Coordinates.getPosition` resolves it against the source, so `~` means
something different at every link and a single parsed argument yields N
positions in a forked execution. `LocalCoordinates` (`^ ^ ^`) is the
interesting one: it builds a basis from the source's rotation *and* its
`EntityAnchorArgument.Anchor`, so it is the only `Coordinates` shape that
depends on eye height — it is not itself an argument type, and both
`Vec3Argument` and `BlockPosArgument` can produce one. `SwizzleArgument`
parses an axis subset (*xz*), and `/execute align` is its only user.
**`EntitySelector` is a compiled query, not a parse tree** — thirteen final
fields with no reader and no grammar in them, assembled by
`EntitySelectorParser` from `EntitySelectorOptions` and resolved against a
`CommandSourceStack` much later, which is why one parsed selector yields a
different set at every link of a chain. `ScoreHolderArgument` and
`GameProfileArgument` reimplement the same selector-or-literal fork for their
own value types. The grammar, the twenty-one options, the two-phase
permission check and the two data structures a selector can be resolved
against are [entity selectors](entity-selectors.md).
**`FunctionArgument` reads an id and nothing else**, deferring the lookup to
execution — which is what lets a function be compiled against a null server
([functions and macros](functions-and-macros.md)).
## The parser under the parser
Five argument types and the whole SNBT reader are not hand-written
`StringReader` walks. They are grammars, written against
`net/minecraft/util/parsing/packrat` — Mojang's own parser-combinator
framework, with `Term` as the combinator algebra, `Dictionary` and
`NamedRule` binding named productions, `Scope` as the typed capture
environment, and `CachedParseState` as the memo table keyed by position and
rule. That memo table is the *packrat* in the name: a backtracking grammar
that would otherwise re-parse the same prefix once per alternative looks it
up instead.
The reason it matters to a command page is `ErrorCollector` and
`SuggestionSupplier`: **the grammar produces completions as a by-product of
failing.** A hand-written argument type can only suggest at a token boundary
it thought to check; a grammar knows every terminal that could have
continued the parse, which is why `/clear @s minecraft:diamond_sword[…`
still completes mid-token. The consumers are exactly nine —
`ComponentArgument`, `NbtTagArgument`, `ResourceOrIdArgument`,
`StyleArgument`, `ItemPredicateArgument`, `ComponentPredicateParser`, and
`TagParser` / `SnbtGrammar` / `SnbtOperations` on the NBT side
([codecs, NBT and JSON](../foundations/codecs-nbt-json.md)).
## The tree on the wire
`Commands.sendCommands` makes a deep copy of the dispatcher's tree, filtered
by each node's requirement for that player's source
(`Commands.fillUsableCommands`), and serialises it. An argument node carries
the registry id of its `ArgumentTypeInfo` plus whatever the template writes:
usually nothing (`SingletonArgumentInfo` writes zero bytes), often a flags
byte from `ArgumentUtils.createNumberFlags` or `EntityArgument`'s
single / players-only pair, sometimes a registry key. The client then
*builds real parsers* from those templates against its own
`CommandBuildContext`, which is why a data pack's biomes and dialogs are
parseable on the client for free.
`Commands.validate` is what keeps that honest — though only in development:
`Bootstrap` calls it under `SharedConstants.IS_RUNNING_IN_IDE` alone, so a
shipped client never runs it. It throws if any registered argument type is
missing from `ArgumentTypeInfos`. **Thirty-eight**
argument-type classes live in the top `net/minecraft/commands/arguments`
package, plus the *blocks*, *item*, *coordinates* and *selector*
subpackages; **fifty-seven** are registered on the wire.
Two things about that packet surprise people. It has exactly **one call
site**, `PlayerList.sendPlayerPermissionLevel`, so the tree and the op-level
entity event are always sent together — on join, respawn, a dimension
*change*, op and deop, and the four LAN toggles, and **not** after `/reload`.
And an **unknown argument type deletes the node, not its children**: a
modded type reaching a vanilla client decodes to a null stub,
`ClientboundCommandsPacket.NodeResolver` substitutes a bare
`RootCommandNode`, the children are resolved and attached to that throwaway
root, and the parent then skips any child that is a `RootCommandNode` — so
the node and everything under it vanish from the tree the player can see.
The packet is not rejected. What the *filtering* means, and the second
elision that rides the same packet, is [permissions](permissions.md).
`/reload` builds a whole new `Commands` and a whole new dispatcher inside
`ReloadableServerResources` and tells nobody. The consequence is narrower
than the folklore on either side: both server-side parses read through
`MinecraftServer.getCommands` and pick up the new dispatcher immediately, so
a newly added function *does* tab-complete after a reload — that completion
is an *ask_server* round trip. What goes stale on the client is the tree's
*shape* and its flags, which no vanilla data pack can change.
## Commands that are a door to somewhere else
Most of `net/minecraft/server/commands` — a hundred classes and 12,800
lines — is a thin lambda over machinery another part of this book owns. A
reader looking for "how does `/locate` work" wants the mechanism page, so
here is the index.
| command | what it really reaches | where that lives |
|---|---|---|
| `LocateCommand` | three barely related parts: `LocateCommand.locateStructure` can **drive world generation on the server thread**, because deciding whether a structure is at a chunk means asking the structure check; `LocateCommand.locateBiome` asks the biome source and never reads a stored palette; `LocateCommand.locatePoi` asks the POI index | [structure placement](../worldgen/structure-placement.md), [biomes](../worldgen/biomes.md) |
| `FillBiomeCommand` | writes the biome palette of the affected sections and resends them — the only command that edits a chunk's biomes | [chunk anatomy](../world/chunk-anatomy.md) |
| `PlaceCommand` | four doors: a configured feature with no placement layer, a whole structure, jigsaw assembly directly, and a structure template with rotation, mirror, integrity and a seed | [features and placement](../worldgen/features-and-placement.md), [jigsaw and templates](../worldgen/jigsaw-and-templates.md) |
| `LootCommand`, `ItemCommands` | `ItemCommands.applyModifier` runs a loot *function* over an existing stack — `/item modify`, and the `from … ` form of `/item replace`. Both take a table or modifier through `ResourceOrIdArgument`, so an inline literal works where an id does | [loot tables](../items/loot-tables.md) |
| `EnchantCommand`, `ExperienceCommand` | thin faces over two systems | [enchanting](../items/enchanting.md), [hunger and experience](../player/hunger-and-experience.md) |
| `ExecuteCommand`, `FunctionCommand` | not commands so much as the front end of the engine | [the execution engine](the-execution-engine.md) |
| `ScoreboardCommand`, `TeamCommand`, `TriggerCommand`, `DataCommands` | the entire write surface of the scoreboard and of stored NBT | [scores, teams and stored data](scoreboard-and-data.md) |
And one class of command a reader will look for in a shipped game and not
find. `Commands` registers `DebugConfigCommand`, `RaidCommand`,
`DebugPathCommand`, `DebugMobSpawningCommand`, `WardenSpawnTrackerCommand`,
`SpawnArmorTrimsCommand` and `ServerPackCommand` only when
`SharedConstants.DEBUG_DEV_COMMANDS` or `SharedConstants.IS_RUNNING_IN_IDE`
is set, and `ChaseCommand` behind a flag of its own. `DebugConfigCommand` is
additionally dedicated-server-only, which matters: it is the only vanilla
caller of the play-to-configuration transition and back
([protocol phases](../networking/protocol-phases.md)).
One more thing crosses the wire from here and belongs to nobody else:
`ClientboundCustomChatCompletionsPacket`, a server pushing arbitrary
non-command completions into the tab list, add / remove / set.
## Signed arguments, in one paragraph
`MessageArgument` is the **only** signed argument in the game — the sole
implementor of `SignedArgument` — and seven command classes register it
under ten literals a player can type: `/ban-ip`, `/ban`, `/me`, `/kick`,
`/say`, `/msg` with its `/tell` and `/w` redirects, and `/teammsg` with
`/tm`. All of
`SignableCommand`, `ArgumentSignatures`, `ArgumentVisitor` and
`CommandSigningContext` exists to serve them: `ArgumentVisitor.visitArguments`
walks a parse to find which arguments need a signature, and the map carries
them afterwards. A command with signable arguments sent *unsigned* is
refused outright when the server enforces secure profiles, and a signature
that does not match the parse breaks the player's whole message chain
([chat and signing](../networking/chat-and-signing.md)).
## Where to look
`Commands` for what exists and what a failed parse is called;
`CommandSourceStack` for what a command knows; `ArgumentTypeInfos` for the
catalogue of argument types and their wire forms; `EntitySelectorOptions`
for the grammar players actually write; `CommandSuggestions` for everything
that happens while you type; and `ClientboundCommandsPacket` for the one
place the two sides agree on a shape.
---
*Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes `tools/verify_names.py`.*
---
# Permissions
> Verified against **Minecraft 26.2** · Part XIII · You are a level-four operator, you type `/msg`, and the server's own answer to *may this player send a chat command* is **no** — because an operator's permission set grants exactly one thing that is not a command level.
Op yourself to four, the highest rung there is, and ask the server whether
you hold `Permissions.CHAT_SEND_COMMANDS`. It says no. Not because you are
restricted — because the set you were given is a
`LevelBasedPermissionSet`, and its answer to any permission that is not a
command level is *false*, with one hard-coded exception. The chat atoms
live only in the *client's* set, which the server never sees and never
sends. There are two permission universes in this game and they overlap in
one place.
That is the shape of the change 26.2 made, and it is the largest API break
in this book: **a permission is no longer an integer.** The five levels are
still there, still numbered nought to four, still stored as integers in
*ops.json* — but a command node no longer asks for a number. It asks a
`PermissionSet` a question, and a set is free to answer however it likes.
## The cast
| class | what it decides | where it lives |
|---|---|---|
| `Permission` | *what is being asked for*: a `Permission.Atom` (a named capability with an `Identifier`) or a `Permission.HasCommandLevel` (a rung) | both jars |
| `PermissionLevel` | the five rungs — *all*, *moderators*, *gamemasters*, *admins*, *owners* — and `PermissionLevel.isEqualOrHigherThan` | both |
| `PermissionSet` | *the answer*. A functional interface with one method, `PermissionSet.hasPermission` | both |
| `LevelBasedPermissionSet` | the ordinary answer: an interface with five constants, one rung each | both |
| `PermissionSetUnion` | the OR of several sets, and the rule that a union may not contain a union | both |
| `PermissionCheck` | *what a node demands*: `PermissionCheck.Require` or `PermissionCheck.AlwaysPass` | both |
| `PermissionProviderCheck` | the `Predicate` Brigadier actually holds, over anything implementing `PermissionSetSupplier` | both |
| `ChatAbilities` | the client's own set, built by **subtraction** from local reasons | client only |
Eleven classes and 398 lines in `net/minecraft/server/permissions` — the
smallest package in this book that changes how everything above it is
written.
## A question, an answer, and a check
Three types do three jobs that a single integer used to do, and keeping them
apart is what makes the rest legible.
```mermaid
flowchart TB
subgraph Q["THE QUESTION — Permission"]
A["Permission.Atom — an Identifier: commands/entity_selectors, chat/send_messages"]
L["Permission.HasCommandLevel — a PermissionLevel: all, moderators, gamemasters, admins, owners"]
end
subgraph S["THE ANSWER — PermissionSet, one method"]
LB["LevelBasedPermissionSet — a rung. Satisfies any level at or below it, plus the entity-selector atom from gamemaster up, and nothing else"]
CH["ChatAbilities — a literal Set of the four chat atoms, minus whatever local restrictions removed"]
CL["ClientPacketListener's two — the player's own set OR-ed with a synthetic restricted atom, and NO_PERMISSIONS"]
UN["PermissionSetUnion — OR over the above. Refuses to contain another union"]
end
subgraph C["THE CHECK — PermissionCheck, what a node holds"]
RQ["PermissionCheck.Require — asks the source's set one question"]
AP["PermissionCheck.AlwaysPass — a singleton, and Commands.LEVEL_ALL is literally it"]
end
Q --> S
S --> C
```
The **question** is data: both shapes are records, both are codec-dispatched
over `BuiltInRegistries.PERMISSION_TYPE`, and `Permission.CODEC` accepts an
atom written as a bare identifier as well as the full dispatched form. The
**answer** is behaviour: `PermissionSet` is one method, so every set in the
game above is a lambda or a small object, and there is no set-of-permissions
data structure anywhere except inside `ChatAbilities`. The **check** is what
a command node carries: `Commands.hasPermission` wraps a `PermissionCheck`
in a `PermissionProviderCheck`, and that predicate is what Brigadier
consults.
**Ninety-five** — `Commands.hasPermission` call sites, which is every
requirement predicate on every command node in the game. Ninety-four are server-side
command registrations; the ninety-fifth is on the *client*, in
`ClientPacketListener`'s node builder.
Two things about `LevelBasedPermissionSet` decide most of this page. It is
an **interface with five constants**, not a class carrying a level, so
`LevelBasedPermissionSet.GAMEMASTER` is a singleton and comparing two sets
is comparing two references. And its `LevelBasedPermissionSet.hasPermission`
tests a `Permission.HasCommandLevel` against its own rung, grants
`Permissions.COMMANDS_ENTITY_SELECTORS` from gamemaster upward as a special
case written into the method, and returns **false to every other atom**.
An operator does not have everything; an operator has a number and one
exception.
Union has a special case of its own, and it runs the opposite way to its
name. `LevelBasedPermissionSet.union` of two level-based sets is not a
`PermissionSetUnion` at all, and it is not the higher of the two: both
branches of the override return the **lower**-levelled set, so it is a
minimum. `CommandSourceStack.withMaximumPermission` is that union, which
means "raising" a function body to gamemaster ([functions and
macros](functions-and-macros.md)) *caps* an owner's source at gamemaster
rather than leaving it alone. Only for sets that are not level-based does
`PermissionSet.union` fall through to `PermissionSetUnion`, which does OR the two.
## Where a set comes from
`MinecraftServer.getProfilePermissions` is the whole of it, and it returns a
`LevelBasedPermissionSet` — never a union, never an atom set. It is
consulted afresh every time `ServerPlayer.permissions` is called; nothing is
cached on the player.
Its cascade is short. Not on the operator list at all, and you get
`LevelBasedPermissionSet.ALL` — rung zero, and deprecated in place. On the
list, and the ops-file entry's own stored set wins. Failing that: the
singleplayer owner gets `LevelBasedPermissionSet.OWNER`; any other
singleplayer player gets owner or rung zero depending on the *allow cheats
for other players* toggle; and on a dedicated server the fallback is the
configured *op-permission-level* property.
The integer survives at every edge of that model, which is worth knowing
before you go looking for a permissions file. *ops.json* stores a number.
*server.properties* stores a number. The op level reaches the client as one
of five values on `ClientboundEntityEventPacket`. And
`PermissionLevel.byId` **clamps** rather than failing, so an *ops.json*
hand-edited to level 9 is an owner and level −1 is rung zero. What does not
exist anywhere is a data pack that grants a permission:
`PermissionCheck.CODEC` has exactly one consumer in the whole game,
`ArgumentUtils.serializeNodeToJson` writing the generated command report,
and both type registries are bootstrapped in code by `PermissionTypes` and
`PermissionCheckTypes`.
## The requirement is consulted inside the parse
This is the design decision a server administrator feels most often, and it
is not a message-formatting choice. Brigadier evaluates a node's requirement
predicate *while walking the tree*, so a node you may not use is a node that
is not there. `Commands.getParseException` then reports an empty parse range
as an **unknown command** and a non-empty one as an unknown *argument*.
An unopped player typing `/give` is told there is no such command. The
server cannot tell them otherwise without a second parse, and it does not
do one.
The entity-selector atom is checked **twice**, in two different phases, and
it is the only permission in the game that is: `EntitySelectorParser.allowSelectors`
tests it at parse time, and `EntitySelector.checkPermissions` tests it again
when the selector is resolved against the world. A command holding a parsed
selector can therefore be re-run later against a source that may no longer
use it — which is exactly what an `/execute` chain does.
Two more consequences worth naming. `Commands.LEVEL_MODERATORS` gates
**nothing**: the rung exists, is settable and is stored, and no vanilla
command asks for it. And of the ninety-one gates that name a level
constant, sixty-six ask for gamemaster, sixteen for admin and nine for
owner — while `Commands.LEVEL_ALL` appears exactly twice, both times as the
*else* branch of a ternary, in `SeedCommand` and `VersionCommand`, which
drop their requirement when the server is the integrated one.
## What the client is allowed to believe
The client has permissions of its own, and they are not a copy of the
server's — no packet carries a `PermissionSet`. It has three sources of
belief, and they behave differently enough to be worth separating.
**The op level**, which arrives on an entity event and is mapped by
`LocalPlayer.handleEntityEvent` onto one of the five sets. Note the bottom
rung: level zero maps to `PermissionSet.NO_PERMISSIONS`, not to
`LevelBasedPermissionSet.ALL`. The two are indistinguishable in practice —
rung zero satisfies no level and no atom either — but the client's copy is a
different object from the server's.
**The command tree**, whose nodes were filtered for this player before being
sent. Two elisions ride that packet and they mean different things.
A node whose requirement the player failed is simply **absent**, and the
filter is recursive, so a gated literal takes its whole subtree with it. A
node that a *no-permission* source would fail is **flagged**
(`ClientboundCommandsPacket.FLAG_RESTRICTED`) — "this needs some
permission", asserted independently of you. `Commands` computes that flag
against a source it builds once from `PermissionSet.NO_PERMISSIONS`, and
only for nodes that already survived your own filter.
**Its own atoms.** `ClientPacketListener` mints a single synthetic
`Permission.Atom` for restricted commands and keeps two sources over its one
dispatcher: the ordinary one, whose set is the player's own **OR-ed with**
that atom, so restricted nodes still highlight and complete; and a
no-permission one. `ChatAbilities` is the other client-only set, and it is
built the opposite way round from everything else here — it starts from all
four chat atoms *granted* and lets each `ChatRestriction` remove some. Three
of the four are decisions the machine you are sitting at makes — two chat
options and a launcher flag — and the fourth, `ChatRestriction.DISABLED_BY_PROFILE`, comes
from the account service: a user flag fetched with your profile. The
client's chat permissions are never granted by the *game* server; they are
only ever taken away, and only ever from outside it.
## Asking a question the client cannot answer
Put those two client sources together and you get the one thing the client
*can* diagnose: it can tell "you would need permission for this" apart from
"that is a typo", for a command it was asked to send on your behalf. A
dialog button and a chat click event both route through
`ClientPacketListener.sendUnattendedCommand`, whose two callers are
`Screen.clickCommandAction` and an adapter `ClientPacketListener` builds for
itself. A **sign does not**: `SignBlockEntity`
runs its click command on the server, through a `CommandSourceStack` it
builds itself at a hard-coded `LevelBasedPermissionSet.GAMEMASTER`, and the
client is never consulted.
```mermaid
flowchart TB
IN["an unattended command — a dialog button, a click event, a sign"]
IN --> P1{"parses against the ordinary source?"}
P1 -- no --> E1["PARSE_ERRORS — confirm: parse errors"]
P1 -- yes --> P2{"any signable argument?"}
P2 -- yes --> E2["SIGNATURE_REQUIRED — confirm: signature required"]
P2 -- no --> P3{"parses against the NO_PERMISSIONS source too?"}
P3 -- no --> E3["PERMISSIONS_REQUIRED — confirm: permissions required"]
P3 -- yes --> OK["NO_ISSUES — send it"]
E1 --> CS["a ConfirmScreen: the player decides"]
E2 --> CS
E3 --> CS
```
`ClientPacketListener.verifyCommand` parses the same string against both
sources and reads the *difference*. Succeeding with your set and failing
without it means some node on the path was gated — which is as much as the
client can ever know, because it was never told which permission or whose.
Three of the four outcomes pop a confirmation screen; the fourth,
`ClientPacketListener.CommandCheckResult` *NO_ISSUES*, sends with no screen at all. So an unattended command that is
merely *unusual* is always shown to you first — but a clean one goes
straight out, and a waxed sign's command never came this way to begin
with.
The client runs *server* permission checks against its own set in several
places — `WorldOptionsScreen` gates the hardcore and gamemode buttons on
`Permissions.COMMANDS_OWNER` and `Permissions.COMMANDS_GAMEMASTER`,
`KeyboardHandler` gates three debug keys — but only one of those checks is a
constant the server itself uses. `GameModeCommand.PERMISSION_CHECK`, a
`PermissionCheck.Require` for gamemaster exported from the command class, is
read twice by `KeyboardHandler` and once by `GameModeSwitcherScreen` against
`LocalPlayer`'s own set — which is why F3+F4 refuses to open the switcher at
all, with *debug.gamemodes.error*, rather than opening a greyed-out one —
and read again by `ServerGamePacketListenerImpl` when the packet arrives.
One constant, five references in four classes, two sides of the network: the
exception that shows what the rule costs.
> **For a 1.21-era reader.** *ServerPlayer.hasPermissions(int)* and
> *CommandSourceStack.hasPermission(int)* are gone. The nearest thing is
> `PermissionSet.hasPermission(Permission)` reached through
> `ServerPlayer.permissions` or `CommandSourceStack.permissions`, and the
> names that survived the rewrite unchanged — `Commands.LEVEL_GAMEMASTERS`
> and its siblings — **changed type**, from an integer to a `PermissionCheck`. Code
> that compiles against the old signature does not exist; code that reads
> the old *semantics* ("an op has everything") compiles and is wrong.
## Where to look
`PermissionSet` first — seventeen lines, and the whole model is in them.
Then `LevelBasedPermissionSet` for the two special cases that decide
everything, `Permissions` for the nine vanilla permissions, and
`Commands.hasPermission` for the one idiom every command registration uses.
`MinecraftServer.getProfilePermissions` for where a player's set is
decided, and `ClientPacketListener.verifyCommand` for the only place either
side reasons about a permission it does not have.
---
*Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes `tools/verify_names.py`.*
---
# Entity selectors
> Verified against **Minecraft 26.2** · Part XIII · you type */kill @e[type=!player,distance=..8,sort=nearest,limit=1]*, and the characters inside the brackets decide, between them, which levels are searched and which of two entirely different data structures answers.
Put a command block at the Overworld origin and give it */tp @p 0 100 0*.
One player is two hundred blocks away across the Overworld; another is
standing on the Nether roof directly "above" the block, at the Nether's own
*0, 128, 0*. The command block teleports the one in the Nether. *@p* is not
"the nearest player in this world": it is the player whose raw *x*, *y* and
*z* are nearest, chosen from the list of everybody on the server, compared as
though the dimensions were stacked in the same coordinate space. (Type the
same command yourself and you always win it — a player's own source sits at
distance zero from a player's own position.) Nothing in a selector is confined to
one level unless you write one of **seven** options that says so — and the
cheapest of those seven, *distance*, is also the one that decides whether the
game walks every entity in the level or asks the chunk sections for a box.
That is the shape of the whole subject. A selector looks like a filter
language, and it is one, but a handful of its twenty-one option names are not
filters at all: they are the query plan. This page is about which is which.
## The cast
| class | what it decides | when it runs |
|---|---|---|
| `EntitySelectorParser` | the reader, the grammar and thirty-two half-built fields. It owns the selector's own syntax; the brace grammars of *scores* and *advancements* are read by the handlers themselves | parse time |
| `EntitySelectorOptions` | the name-to-handler map — twenty-one entries, filled once by `Bootstrap.bootStrap` and never again | class init |
| `InvertableSetOptionState` | the three-state machine behind *type=!zombie,!skeleton*: one positive assertion **or** any number of negations and tags, never both | parse time |
| `SetOnceOptionState` | one boolean, for the four options that may appear at most once | parse time |
| `EntitySelector` | the compiled query: thirteen final fields, no reader, no grammar, no string | built at parse time, run later |
| `EntityArgument` | four argument shapes (single or many, entities or players) and the parse-time rejections that enforce them | parse time and on the wire |
| `CommandSourceStack` | the only thing a selector can be resolved against — origin, level, server, permission set | resolve time |
| `LevelEntityGetterAdapter` | the fork in the road: an `EntityLookup` walk, or an `EntitySectionStorage` box query | resolve time |
`net/minecraft/commands/arguments/selector` is five classes and 1,717 lines,
and every one of them is in the server jar *and* the client jar. That matters
later.
> **For a 1.21-era reader.** The parser's crowd of *hasNameEquals* /
> *hasNameNotEquals* / *hasGamemodeEquals* booleans is gone, replaced by
> eight state objects — four `InvertableSetOptionState` and four
> `SetOnceOptionState` — that enforce the same rules structurally. *ResourceLocation* is `Identifier`. And the check
> that used to be an op-level comparison is now an atom,
> `Permissions.COMMANDS_ENTITY_SELECTORS` ([permissions](permissions.md)).
## Three stages, and the last one is not on the parser's clock
```mermaid
flowchart LR
S["the argument text"]
subgraph P["PARSE"]
direction TB
P1["one head of six sets four defaults"]
P2["each option name looked up in a hash map"]
P3["its handler writes a field or appends a predicate"]
P1 --> P2 --> P3
end
subgraph C["COMPILE — once, still at parse time"]
direction TB
C1["thirteen final fields"]
C2["a box, or null"]
C3["a position resolver"]
C1 --- C2 --- C3
end
subgraph R["RESOLVE — server thread, once per execution"]
direction TB
R1["which levels"]
R2["which of two data structures"]
R3["order, then cut to the limit"]
R1 --> R2 --> R3
end
S --> P
P --> C
C --> R
```
The first two stages happen while Brigadier walks the command tree; the third
happens when the command's lambda asks for its argument. In between, the
selector is an ordinary immutable object sitting in a parsed context, which is
why an */execute* chain can resolve the same parsed selector once per source
and get a different answer each time
([the execution engine](the-execution-engine.md)).
## Parse: six heads and twenty-one names
`EntitySelectorParser.parseSelector` reads the character after the *@* and
sets four things — a result limit, whether non-players are in scope, an order,
and sometimes a type. There are six, and no more: the default branch of that
switch throws.
| head | limit | non-players | order | extras |
|---|---|---|---|---|
| *@a* | unbounded | no | arbitrary | typed to player |
| *@e* | unbounded | yes | arbitrary | adds an `Entity.isAlive` test |
| *@n* | 1 | yes | nearest | adds an `Entity.isAlive` test |
| *@p* | 1 | no | nearest | typed to player |
| *@r* | 1 | no | random | typed to player |
| *@s* | 1 | yes | arbitrary | resolves to the source's own entity |
Only *@e* and *@n* add that test, and only `LivingEntity.isAlive` makes it
mean anything — it is the override that adds "and has health left" to the
base class's "and has not been removed". So a player sitting on the death
screen is invisible to *@e* and still a target for *@a* and *@p*.
Then the bracket. `EntitySelectorParser.parseOptions` reads a name, looks it
up through `EntitySelectorOptions.get`, and hands the reader to the handler it
finds. **Twenty-one** names are registered, counted by reading every
`EntitySelectorOptions.register` call in `EntitySelectorOptions.bootStrap`.
| option | what its handler does | repeatable? |
|---|---|---|
| *name* | compares `Nameable.getPlainTextName` | one positive, or many negatives |
| *team* | compares `Entity.getTeam` — the empty string means *no team* | one positive, or many negatives |
| *gamemode* | compares `ServerPlayer.gameMode`, **and drops non-players** | one positive, or many negatives |
| *type* | an id sets the `EntityTypeTest`; a tag only adds a test | one positive id, or many negatives and tags |
| *tag* | reads `Entity.entityTags` — empty means *no tags at all* | freely |
| *nbt* | serialises the candidate and compares with `NbtUtils.compareNbt` | freely |
| *predicate* | runs a loot condition in `LootContextParamSets.SELECTOR` | freely |
| *scores* | a brace map of objective to `MinMaxBounds.Ints` | once |
| *advancements* | a brace map of advancement to done-ness, **players only** | once |
| *limit* | sets the result cap, rejects anything below 1 | once, never on *@s* |
| *sort* | picks one of four orders | once, never on *@s* |
| *distance* | a `MinMaxBounds.Doubles`, rejects negatives, **world-limits** | once |
| *level* | a `MinMaxBounds.Ints`, rejects negatives, **drops non-players** | once |
| *x*, *y*, *z* | override one axis of the resolve origin, **world-limit** | once each |
| *dx*, *dy*, *dz* | build the box, **world-limit** | once each |
| *x_rotation*, *y_rotation* | angle ranges that wrap through 360 | once each |
Three of the twenty-one are freely repeatable — *tag*, *nbt* and *predicate* —
because they are the three registered as always available, with no state
object behind them at all. That is why *tag=a,tag=b* is the idiom for "has
both" and *type=zombie,type=skeleton* is a parse error rather than an empty
result: `InvertableSetOptionState` moves to a terminal state the moment a
positive id is accepted, and `EntitySelectorOptions.get` then refuses the
whole option by name before its handler ever runs. Its *other* terminal state
is the permissive one — after a negation or a tag, more negations and more
distinct tags are allowed, which is why *type=!zombie,!skeleton* works and why
two entity tags can be written together and AND.
## Compile: what a box is, and where it comes from
`EntitySelectorParser.getSelector` runs once, at the end of the parse, and
turns the pile of fields into thirteen final ones. Two of the thirteen are the
interesting decisions.
**The box.** If any of *dx*, *dy* or *dz* was written,
`EntitySelectorParser.createAabb` builds the box from those three, treating
the missing ones as zero and adding one to each maximum — a *dx=0* volume is
one block wide, not zero. Otherwise, if *distance* was written **and has a
maximum**, the box is a cube of that radius, again with one added to the
positive corner. Otherwise there is no box. So *distance=8..* — a minimum with
no maximum — produces no box at all, and *dx=3,distance=..64* ignores the
distance entirely when choosing the box, because the delta branch wins.
**The origin.** If any of *x*, *y* or *z* was written, the position becomes a
function that overrides those axes of the source's position and keeps the
rest. Otherwise it is the identity. This is applied per execution, which is
what makes *x=0* mean the same thing everywhere and *@s* mean something
different at each link of a chain.
Everything else that was written is already a test in a list, in written
order — with exceptions appended afterwards whatever order they appeared in.
`EntitySelectorParser.finalizePredicates` adds the two rotation tests and the
experience-level test last, and `EntitySelector.getPredicate` then appends up
to three more at resolve time: the feature-flag test, the exact box test and
the range test. `Util.allOf` evaluates them in that order and short-circuits,
so the range test — the cheapest thing in a selector — runs **after** an *nbt*
comparison that serialised the whole entity.
## Resolve: which levels, which structure, which order
```mermaid
flowchart TB
A["findEntities, on the server thread"] --> B{"non-players in scope?"}
B -- no --> P["findPlayers — a linear walk of a player list, always"]
B -- yes --> C{"a bare name or a UUID?"}
C -- name --> N["PlayerList.getPlayerByName — a linear case-insensitive scan"]
C -- UUID --> U["PlayerList.getPlayer — the id map, one lookup"]
C -- neither --> D{"is it the source itself?"}
D -- yes --> S["test the source's own entity, or return nothing"]
D -- no --> E{"world-limited?"}
E -- yes --> F["this level only"]
E -- no --> G["every level the server has"]
F --> H{"is there a box?"}
G --> H
H -- yes --> I["EntitySectionStorage — only the sections the box touches"]
H -- no --> J["EntityLookup — every visible entity in the level, one by one"]
I --> K["order, then cut to the limit"]
J --> K
P --> K
```
**World-limited is a parse-time flag, not a runtime one.** Exactly seven
option handlers call `EntitySelectorParser.setWorldLimited`: *distance*, *x*,
*y*, *z*, *dx*, *dy* and *dz*. Write none of them and
`EntitySelector.findEntities` iterates every level the server has. */kill
@e[type=item]* is a three-dimension operation.
**The two structures are genuinely different.** With a box,
`Level.getEntities` goes through `EntitySectionStorage`, which visits only the
accessible non-empty 16-cubes the box overlaps. Without one,
`ServerLevel.getEntities` goes through `EntityLookup`, which walks the level's
entire visible-entity map and calls `EntityTypeTest.tryCast` on each. **There
is no index by entity type.** *type=zombie* narrows nothing structurally; it
is a cast applied one entity at a time, ahead of the tests. Only the seven
box options narrow the search itself, and only when they add up to a box.
**And the box path finds things the walk cannot.** `Level.getEntities` also
offers each ender dragon's eight `EnderDragonPart` sub-entities to the type
test and the predicate, and every part reports its parent's type. So a box
query in the End can return the dragon and up to eight more "ender dragons";
the same selector without a box returns one.
**Players never get a box.** Both player paths — `ServerLevel.getPlayers` for
one level, `PlayerList.getPlayers` for the whole server — are linear walks of
a list, and the box survives only as one more test. *@a[distance=..8]* costs
what *@a* costs.
**Sort is what takes the limit away.** `EntitySelector.getResultLimit` returns
the parsed limit **only when the order is arbitrary**, and the unbounded value
otherwise, because a sort has to see everything before it can know what comes
first. When it is the parsed limit, it reaches the level query as an early
abort. So *@e[limit=1]* stops at the first match, and *@e[limit=1,sort=nearest]*
collects every match in range, sorts the list and throws all but one away.
*@n* and *@p* live permanently in the second mode: their heads set the nearest
order, so they always collect first and cut afterwards.
**So the query plan is written by eight of the twenty-one names.** Seven of
them build the box and world-limit the search — *distance*, *x*, *y*, *z*,
*dx*, *dy* and *dz* — and the eighth, *sort*, un-decides part of it by taking
the limit away. The other thirteen only filter what the plan returns.
## One permission, checked in two places, for two different reasons
The gate is a single atom, `Permissions.COMMANDS_ENTITY_SELECTORS`, granted by
`LevelBasedPermissionSet` from gamemaster upward as the one hard-coded
exception in that class ([permissions](permissions.md)). It is read in seven
places, all of them under `commands/arguments`, and they divide cleanly in
two.
**At parse time**, `EntitySelectorParser.allowSelectors` asks the source and
the answer becomes a constructor argument. If it is false, an *@* throws
`EntitySelectorParser.ERROR_SELECTORS_NOT_ALLOWED` immediately, and a bare
name or UUID still parses. This is the check that matters for the three
vanilla commands that take a selector-capable argument with **no permission
requirement at all** — `MsgCommand`, `EmoteCommands` and `TeamMsgCommand`, so
*/msg*, */tell*, */w*, */me*, */teammsg* and */tm*. For an ordinary player
this atom is the only gate on those.
`MessageArgument` alone treats a refusal as a formatting decision rather than
an error: without the permission the message is taken as literal text, so an
unopped */msg Bob @a* sends those two characters. With the permission, an *@*
that is not a valid selector head is skipped and the scan continues — which is
how an email address survives — but a *malformed* selector body throws, and
the whole command fails to parse.
**At resolve time**, `EntitySelector.checkPermissions` asks again, guarding on
`EntitySelector.usesSelector`, which only `EntitySelectorParser.parseSelector`
ever sets. A selector compiled from a bare player name is exempt. The second
check is not redundant, because there is a whole route into the machinery that
never passed the first one: `EntitySelector.COMPILABLE_CODEC` compiles a
selector out of a **text component** — the *selector* content type, the
*score* name field and the *entity* NBT data source — and does so with
selectors unconditionally allowed, because a codec has no source to ask. The
resolve-time check is what decides whether a */tellraw* written by a data pack
may actually enumerate entities, and it asks the source the component is being
resolved *against*, never whoever wrote it.
## Questions a command author asks
**Does the client parse selectors?** Yes, by two routes, and it cannot resolve
one. All five selector classes ship in the client jar.
`EntityArgument.listSuggestions` builds a real `EntitySelectorParser` against
the client's own permission set, parses as far as it can, swallows the
exception and asks the half-finished parser for its suggestions — which is why
completion inside a bracket knows which options are still legal. And
`ComponentSerialization` decodes a *selector* content type on the client with
the same compiler the server uses. What the client cannot do is run one: every
find method takes a `CommandSourceStack`, and the client's suggestion source
is a `ClientSuggestionProvider`. The one place a client resolves a component
that might contain a selector is `ServerStatusPinger`, whose
`ResolutionContext` deliberately carries no source, so a server-list
description containing a selector renders as nothing at all.
**Why did */damage @e 1* complain before it touched the world?** Because
`EntityArgument` rejects on the compiled selector's *shape*, during the parse:
a limit above one in a single-target slot — which is what `/damage`, `/ride`
and `/data get entity` all take — or non-players in a players-only slot, as
in */msg @e*. Note that */kill @e* is fine: `/kill` takes the many-entities
shape, so neither rejection can fire on it. *@s* is exempt from the second
test, so */msg @s* parses and then finds nobody when the source is not a
player.
**What does the client suggest for an entity argument?** Online player names,
plus — from `ClientSuggestionProvider.getSelectedEntities` — the UUID of
whatever your crosshair is on. Point at a cow, press tab, and it offers you
that cow.
**Is *sort=random* seeded?** No. It is the JDK's list shuffle, with no world
seed and no `RandomSource` anywhere near it, so *@r* is not reproducible from
a save.
**Why does *distance=..8* not return something 8.9 blocks away?** Because the
box is only a pre-filter. The cube built for a maximum of 8 spans −8 to +9 on
each axis, deliberately larger than the sphere it approximates, and the exact
test that follows is `MinMaxBounds.Doubles.matchesSqr`, which compares squared
distances against pre-squared bounds and so never takes a square root.
## Where to look
`EntitySelector` first — thirteen fields and four find methods, and the design
is in them. Then `EntitySelectorParser.getSelector` for the one place those
thirteen are decided, and `EntitySelectorOptions.bootStrap` for the grammar
players actually write. `LevelEntityGetterAdapter` is six methods long and is
where the cost of every selector is settled. Note that the name
`EntitySelector` is used twice in the game: this one, and an unrelated bag of
predicate constants in `world/entity` that the mob AI and the hoppers use.
---
*Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes `tools/verify_names.py`.*
---
# The execution engine
> Verified against **Minecraft 26.2** · Part XIII · `/execute as @a at @s run say hi` on a server with four players: a command engine with no Java recursion, a fan-out that materialises one player at a time, and a `/return` that deletes work out of a queue rather than unwinding a stack.
Write a data pack that calls a function that calls itself, load it, and the
server does not crash. It runs a very large number of commands, logs one
line at *info*, and carries on. That is not a recursion limit — there is no
recursion limit, and no depth game rule either. It is that **nothing in
command execution uses the Java call stack.**
Every construct that used to nest — `/execute … run`, `/function`,
`execute if function`, `/return run` — is expressed as *queued work* on a
heap-allocated deque, and the driver is a flat loop. A stack made of heap
objects costs you nothing except the obvious, and buys you three things a
real stack cannot give: you can inspect it, you can **delete** entries out
of the middle of it, and you can hand each entry to a tracer on the way
past. `/return`, `/debug function` and the fork limit all exist because of
that one decision.
## The cast
| class | what it decides | notes |
|---|---|---|
| `ExecutionContext` | one per outermost command: the queue, the staging list, the budget, the fork limit, the tracer | 153 lines, and the whole engine is in its `ExecutionContext.runCommandQueue` loop |
| `CommandQueueEntry` | a `Frame` and an `EntryAction`. That is the entire unit of work | — |
| `Frame` | **not** a stack frame: a depth, a `CommandResultCallback` a `/return` feeds, and a `Frame.FrameControl` that knows how to delete this frame's pending work | one object shared by reference across a whole function body |
| `BuildContexts` | walks the stages of a parsed chain, forking sources as it goes | `BuildContexts.TopLevel`, `BuildContexts.Continuation` and `BuildContexts.Unbound` |
| `ContinuationTask` | the lazy fan-out: emits one element's entry, then re-queues itself | the reason N players cost N entries, not N at once |
| `CallFunction` / `IsolatedCall` | the only two things besides the top level that open a frame | `IsolatedCall`'s `/return` cannot reach the caller |
| `ExecutionCommandSource` | the interface the engine is generic over, which is why none of it mentions `CommandSourceStack` | `CommandSourceStack` implements it |
| `CommandResultCallback` | a success flag and an integer. This pair is what "the result of a command" means everywhere in the game | `CommandResultCallback.EMPTY` short-circuits |
`net/minecraft/commands/execution` is the whole engine and it is entirely
server-side. Nothing here crosses the network; only the *effects* of
commands produce packets.
## The queue, four moments apart
`/execute as @a at @s run say hi`, four players online. Read each panel as
the queue at one moment, head at the top.
```mermaid
flowchart TB
subgraph T1["1 · the command is queued"]
direction TB
A1["BuildContexts.TopLevel — the whole parsed chain, one entry, depth 0"]
end
subgraph T2["2 · that entry ran, ALL of it"]
direction TB
B1["ContinuationTask over 4 sources"]
B2["as @a and at @s were walked inside entry 1 — 1 source became 4, and one cost unit was spent per stage"]
B1 -.- B2
end
subgraph T3["3 · the continuation ran once"]
direction TB
C1["ExecuteCommand for player A"]
C2["ContinuationTask — B, C and D do not exist yet"]
C1 --- C2
end
subgraph T4["4 · A's say hi is done"]
direction TB
D1["ContinuationTask — and only NOW is B materialised"]
end
T1 --> T2 --> T3 --> T4
```
**A fork does not create frames, and it does not create entries.**
`BuildContexts.execute` walks every non-execute stage inside a *single*
queue entry, spending one cost unit per stage no matter how many sources
that stage produces, and turning a one-element source list into an
N-element one. Frames are opened in exactly three places:
`ExecutionContext.createTopFrame`, `CallFunction` and `IsolatedCall`. A
hundred-player fork opens none.
**The queue is a stack with a staging buffer, and that is what makes it
depth-first.** An action does not push directly: it appends to
`ExecutionContext.newTopCommands` while it runs, and
`ExecutionContext.pushNewCommands` splices that list onto the *head*
afterwards, in order. So whatever the current action spawned runs before
whatever was already pending — the semantics of a call stack, out of an
`ArrayDeque`.
**The fan-out is lazy, and the arithmetic is exact.**
`ContinuationTask.schedule` queues nothing for an empty list, one entry for
one element, **two entries for two**, and for **three or more** queues
exactly one: a `ContinuationTask` that emits the current element's entry and
then re-queues itself behind it. Because staging preserves order, element
*i* and everything it spawns runs to completion before element *i+1* is even
materialised. The queue cost is constant in N.
**A chain can split across entries, and only a custom modifier does it.**
When the stage walk meets a `CustomModifierExecutor` it hands off and
returns mid-walk; the rest of the chain resumes later as a
`BuildContexts.Continuation`. That is how `execute if function` and
`/return run` interrupt a chain that otherwise runs to its leaf inside one
entry.
## Deleting work, which is what `/return` is
`/return` does not unwind and does not throw. `Frame.returnSuccess` pushes
the value sideways into the callback the caller installed on that frame, and
`Frame.discard` splices the abandoned work out of the queue. There is no
search.
The splice is one rule: **pop from the head while the entry's depth is at
least *d***. That works because the queue is depth-first, so entries deeper
than a frame are always in front of that frame's own remaining entries —
which means the rule removes exactly the callee's pending work plus the rest
of this frame's body, and nothing older. Depth-zero frames are the special
case: their frame control clears the queue outright.
The laziness pays off here too. Discarding one `ContinuationTask` self-entry
abandons every element not yet materialised, so `/return` out of a
thousand-line function is the same cost as `/return` out of a two-line one.
Who installed the callback decides where a returned value goes, and this is
the part the page's shape invites getting backwards. Two corrections worth
carrying:
- The single-source reduction on the `/return run` path lives in
`BuildContexts.execute`, not in `ReturnCommand`, and it is gated twice —
on return mode, and on the leaf *not* being a `CustomCommandExecutor`. So
`return run execute as @a run function foo` queues one function call per
player with no reduction at all.
- The chaining runs the opposite way from "inner onto outer": what is
chained is the *source's own* callback with the current frame's return
consumer, and on the `/return run function` path the outer frame's
consumer is chained **into** the inner frame.
## A result is a flag and a number, and nothing aggregates
The result of a command is always a `CommandResultCallback` pair: a success
flag and an integer. There is no aggregation anywhere in the engine. A fork
over N players delivers N independent results to N sources, so an
`execute store result` writes N times and the last one wins — there is no
success count. A sum exists in exactly one place: `/function` on a *tag*,
and only when the caller installed a real callback.
A command typed in chat has an *empty* frame callback and
`Commands.performCommand` returns nothing — yet `execute store` still works
on it, because the result also reaches the **source's** own callback, which
is what `ExecuteCommand.wrapStores` decorated
([scores, teams and stored data](scoreboard-and-data.md)).
`FallthroughTask` exists so that a chain which produced no sources still
*fails* rather than returning nothing, and every site that queues it is
inside a return or a conditional.
Six classes implement the escape hatch for a command that wants the engine
rather than Brigadier's plain "return an int" —
`FunctionCommand.FunctionCustomExecutor`,
`ReturnCommand.ReturnValueCustomExecutor`,
`ReturnCommand.ReturnFailCustomExecutor`,
`DebugCommand.TraceCustomExecutor`,
`ExecuteCommand.ExecuteIfFunctionCustomModifier` and
`ReturnCommand.ReturnFromCommandCustomModifier` — and
`CustomCommandExecutor.WithErrorHandling` is the base *two* of the six use —
`FunctionCommand.FunctionCustomExecutor` and `DebugCommand.TraceCustomExecutor` —
routing a thrown `CommandSyntaxException` to both the source's error handler
and its callback. The other four handle their own.
## Two ways to die, and they are not the same event
**The quota runs out.** `ExecutionContext.runCommandQueue` checks at the top
of every iteration, logs at *info*, and breaks. The queue is **not** cleared;
it is simply abandoned with the context. Nothing reaches the player.
**The queue overflows.** `ExecutionContext.queueNext` trips when staged plus
queued entries exceed ten million; `ExecutionContext.handleQueueOverflow`
clears *both* lists and sets a latch that silently drops every subsequent
queue attempt, and the driver then logs at **error**. Different level,
different clean-up, and a latch the quota path has no equivalent of.
The budget itself is spent in exactly three places — `BuildContexts` on a
modifier stage, `CallFunction` on a function call, and the leaf
`ExecuteCommand` task on an executed command — and the first has a gate
worth knowing. The increment happens only when the stage carries a non-null
redirect modifier, and only after the custom-modifier hand-off has been
ruled out. So a plain `execute run` costs nothing for its redirect, and
**`execute if function` and `/return run` are free**: neither custom
modifier ever reaches the counter. A `ContinuationTask` is free too, so an
N-way fan-out costs N, not N+1.
**Ten million** — the cap on *queue length*, staged plus queued
(`ExecutionContext`). The constant that names it reads as though it bounded
depth, and is never read by name.
## Questions a data-pack author asks
**Can a function yield?** No. Work it queues drains inside the same driver
loop, in the same tick, before the call returns. The only escape is
`/schedule` ([functions and macros](functions-and-macros.md)).
**How deep can recursion go?** Unbounded, structurally. Depth is used only
to order discards and to indent the tracer. Recursion is bounded
transitively by the cost budget and fan-out by the ten-million entry cap.
**Why did my command fail silently inside `execute if`?** Because
conditionals are fork nodes. `execute as @s`, `execute at @s`,
`execute if block` and friends set the forked flag on `ChainModifiers` for
the rest of the chain, and **a forked source suppresses failure messages**.
Putting a harmless-looking conditional in front of a command converts its
errors into nothing. They still reach the tracer, which is what
`/debug function` is for.
**Does `/return` inside a fork stop the other sources?** Not normally.
`/function` dispatches its N sources eagerly in a plain Java loop, each
opening its own frame at the next depth, so a `/return` in one discards only
that callee. It is `return run …` that sets
`CallFunction.returnParentFrame`, making the inner discard run at the outer
frame's depth and delete the siblings.
**Why does my fork stop one short?** The fork limit is checked per
contributing source with a greater-or-equal comparison, so the effective
ceiling is one below the configured value. When it trips the handler returns
without queueing even a `FallthroughTask`, so a `/return run` chain that
hits the fork limit yields nothing at all rather than a failure. The limits
are `GameRules.MAX_COMMAND_FORKS` and
`GameRules.MAX_COMMAND_SEQUENCE_LENGTH`, both 65536 by default — and both
are read **once**, by the outermost command, so a `/gamerule` changed part
way through a long fan-out does not take effect until the next top-level
command. (There is nothing dimensional in this: `ServerLevel.getGameRules`
returns the server's one `GameRules` instance, so no level has rules of its
own to pick up.)
**Does a nested command get its own budget?** No.
`Commands.CURRENT_EXECUTION_CONTEXT` is a thread-local: a command that
starts another top-level execution appends to the running queue rather than
making a new context, and the limits were read once by the outermost call.
Its top frame is nested one depth deeper, so its discards cannot eat the
outer queue.
## The two commands that are part of the engine
`ExecuteCommand.scheduleFunctionConditionsAndTest` is how `execute if
function` works and it is the cleverest thing in the area: instantiate each
function once, wrap every source in an `IsolatedCall` whose callback appends
that source to a list, then queue a `BuildContexts.Continuation` over *the
same mutable list*, which the isolated calls fill before the continuation
reads it. The staging order is what makes that safe, and the inner
`CallFunction` opens against the isolated frame, which is why a `/return`
inside a condition function cannot reach the caller.
`DebugCommand.Tracer` installs itself on the whole `ExecutionContext` rather
than per frame, so it traces everything in that context. It refuses to nest,
refuses return mode, and implements `CommandSource` as well, so a traced
function's chat output lands in the trace file alongside the call lines.
## Where to look
`ExecutionContext` and `Frame` first — 153 lines and 24 explain the
whole design. Then `BuildContexts` for how a parse becomes work,
`ContinuationTask` for the laziness both the fan-out and the function body
ride on, and `ExecutionCommandSource` for why none of it names a command
source.
---
*Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes `tools/verify_names.py`.*
---
# Functions and macros
> Verified against **Minecraft 26.2** · Part XIII · A macro function in `#minecraft:tick` is reached with no arguments: it fails, silently, twenty times a second, forever — nothing logged, nothing printed, nothing counted.
Put a `$`-prefixed line in a function, add that function to
`#minecraft:tick`, and the game will call it every tick with no argument
compound at all. Instantiating it raises a
`FunctionInstantiationException`, `ServerFunctionManager.execute` catches
that one exception with an **empty body**, and the tick moves on. Any
*other* exception from the same call is logged at *warn*; this one is the
single case the manager was written to swallow, because the `/function`
command is expected to report it instead — and `#minecraft:tick` is not the
`/function` command.
That is the sharp end of a two-step model that is otherwise very tidy: a
`.mcfunction` file becomes a runnable thing in two steps, and for the
overwhelming majority of functions the second step does nothing at all.
## The pipeline
```mermaid
flowchart TB
F["a .mcfunction text file, and the tag JSON beside it"]
F --> C["1 · COMPILE, at reload — CommandFunction.fromLines, off the main thread, against a null level and a null server"]
C --> P["a CommandFunction: a PlainTextFunction, or a MacroFunction if any line begins with a dollar"]
T["TRIGGER — the tick tag, the load tag, /function, /schedule, an advancement reward, an enchantment effect, a test environment"]
P --> I["2 · INSTANTIATE, per call — plain returns itself, a macro substitutes and RE-PARSES, cached eight deep"]
T --> I
I --> R["an InstantiatedFunction: an id and an ordered list of unbound actions"]
R --> Q["3 · QUEUE — CallFunction opens a frame, ContinuationTask schedules the lines"]
Q --> E["the execution engine — the lines run to completion inside this tick"]
```
The two halves of that live in `net/minecraft/commands/functions` (the model)
and `net/minecraft/server` (the two managers), and both are entirely
server-side.
| class | what it decides |
|---|---|
| `ServerFunctionLibrary` | the reload listener: parses every file, holds the volatile function and tag maps, and the compile-time `PermissionSet` |
| `ServerFunctionManager` | the runtime face: the two tags, the source they run as, and the one empty catch |
| `CommandFunction` | the compiled, uninstantiated function — `CommandFunction.fromLines` is the compiler |
| `PlainTextFunction` | a macro-free function. It is both the compiled *and* the instantiated form, so instantiating one allocates nothing |
| `MacroFunction` | the other case: parameters, an eight-entry LRU, and a re-parse per miss |
| `StringTemplate` | the `$(name)` syntax and what a valid variable name is |
| `InstantiatedFunction` | an id and an ordered list of `UnboundEntryAction`s. That is the entire runnable representation |
| `FunctionInstantiationException` | carries a `Component`, which is why `/function` can render the failure and the tick loop can swallow it |
## 1 · Compile, at reload
**In:** the raw lines of one file. **Out:** a `CommandFunction`, or nothing
at all.
`CommandFunction.fromLines` walks the lines. A trailing backslash joins the
next one (`CommandFunction.shouldConcatenateNextLine`), and a continuation
at end of file is an error; blank lines and `#` comments are skipped; a
leading `/` is a hard error with two different messages depending on whether
you wrote one slash or two; a leading `$` is a macro line, kept as text; and
anything else goes through `CommandFunction.parseCommand`, which produces a
`BuildContexts.Unbound`. So **a compiled function line is literally a parsed
context chain plus its input string, waiting for a source.**
`CommandFunction.checkCommandLineLength` caps a line at two million
characters.
A syntax error on any line fails the *whole file*, which is logged at error
and then simply absent from the map. There is no partial function.
Two constraints make this stage unusual, and they are the same constraint
seen twice. `ServerFunctionLibrary` parses every file **in parallel on the
reload's background executor**, and it does so against a source built by
`Commands.createCompilationContext` with a **null level and a null server**.
Only the map swap happens on the main thread, and the maps are volatile
because the library object is built on a background thread and read from
several. An argument type that dereferenced the world during parsing would
break a reload — which is exactly why `FunctionArgument` reads an id and
defers the lookup. In 26.2 no argument type actually tests the constraint:
the four that parse against a source consult only its permissions, and those
come from the *function-permission-level* server property, gamemaster by
default.
## 2 · Instantiate, per call
**In:** a `CommandFunction` and an optional argument compound. **Out:** an
`InstantiatedFunction`, or a `FunctionInstantiationException`.
For a `PlainTextFunction` this step returns the very same object: nothing
is allocated, nothing is parsed, and the overwhelming majority of functions
in the world take this path.
A `MacroFunction` looks up each declared parameter in the argument compound,
stringifies it, and uses the **ordered list of strings** as a cache key over
an eight-entry LRU (`MacroFunction.MAX_CACHE_ENTRIES`). On a miss it
substitutes into every macro line and **re-parses** it, and a parse failure
there is the exception above. Note what the cache is keyed on: the values,
in parameter order — so nine distinct argument tuples cycling round will
miss every time.
Stringification is where macros surprise people, because it is not SNBT for
everything. `MacroFunction` has explicit cases for float and double (a
decimal format with up to fifteen fraction digits, so `1.0` becomes `1`),
for byte, short and long, and for strings (the **unquoted** value).
Everything else — integers, compounds, lists — falls through to SNBT.
Integers merely happen to render bare; byte, short and long need their own
cases precisely because SNBT would suffix them.
Three smaller rules complete the model. One `$` line anywhere makes the
whole *file* a macro function, though its non-macro lines keep their
already-compiled form and are never re-parsed. A `$` line containing no
substitution at all is a **load-time error**, not a plain command. And a
macro function's instantiated variants all share **one synthetic id**,
derived from the parameter *names* rather than the values.
## 3 · Queue
**In:** an `InstantiatedFunction` and a source. **Out:** entries on the
execution queue.
`CallFunction` spends one cost unit, opens a frame at the next depth, and
hands the function's line list to `ContinuationTask.schedule` — the same
call the fan-out over an entity selector makes. **A hundred-line function
and a hundred-player fork are therefore the same shape in the queue**, which
is what makes `/return` cheap: discarding one self-entry abandons every line
not yet materialised. Everything after this point is
[the execution engine](the-execution-engine.md).
## What calls a function, and when
`ServerFunctionManager.tick` is the **first** thing
`MinecraftServer.tickChildren` does — before the clocks, before the time
sync, before any level ticks, and therefore long before connections and
players tick, which in 26.2 happen *after* the levels
([the server tick](../server/server-tick.md)). It no-ops entirely when the
tick-rate manager is not running normally, so `/tick freeze` suspends data
packs. `#minecraft:load` runs once after a reload or start, and
`#minecraft:tick` runs every tick from a list **snapshotted at reload** and
never consulted again, so nothing can join or leave the tick loop between
reloads.
Each function in a tag gets its **own** `ExecutionContext`, so the budget is
per function rather than shared across the tag.
`/schedule` is the one way out of the current tick, and its gate is narrower
than it sounds. The callback goes into the server's timer queue — server-wide
saved data, not per level — and `ServerLevel.tickTime` advances that queue
immediately after setting the game time. That whole method sits behind the
level's own *tickTime* flag, **which only the overworld has**, so a
scheduled function fires once per tick rather than once per dimension.
`ScheduleCommand` also refuses two things outright: a macro function, and a
delay of zero.
Everything else that runs a function is a short and exhaustive list:
`/function` itself, `AdvancementRewards` for a reward function
([advancements](advancements.md)), the `RunFunction` enchantment effect
([enchantments](../items/enchantments.md)), and
`TestEnvironmentDefinition.Functions` for a game test's environment setup
([game tests](game-tests.md)).
## The two permission verbs, three lines apart
A function body is one of the places a command source's permission set is
deliberately rewritten, and the game reaches the same answer down two
differently-named routes.
`ServerFunctionManager.getGameLoopSender` takes the server's own source —
which is `LevelBasedPermissionSet.OWNER` — and calls
`CommandSourceStack.withPermission` with gamemaster. That is a flat
**replacement**: the tick and load tags run at gamemaster.
`FunctionCommand` and `DebugCommand` instead call
`CommandSourceStack.withMaximumPermission`, which is `PermissionSet.union`.
The name promises a widening, and for two sets that are *not* level-based it
delivers one — the default `PermissionSet.union` builds a `PermissionSetUnion` that ORs.
But `LevelBasedPermissionSet` overrides it, and the override returns the
**lower**-levelled set on both of its branches
([permissions](permissions.md)). So for the sets a player or the console
actually carries it is a *minimum*, and `CommandSourceStack.withMaximumPermission`
at gamemaster
over an owner's source yields gamemaster too. Both routes land on the same
rung: there is no way to reach a function body above gamemaster, and the
method named for a ceiling is the one that enforces it.
## Where to look
`CommandFunction` for the compiler, and `MacroFunction` for the only part of
it that runs per call. `ServerFunctionLibrary` for what a reload does off
the main thread, `ServerFunctionManager` for the tick hook and the one empty
catch, and `StringTemplate` for the substitution rules a pack author will
actually trip over.
---
*Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes `tools/verify_names.py`.*
---
# Advancements
> Verified against **Minecraft 26.2** · Part XIII · "Stone Age": a cobblestone lands in your inventory and one tick later the toast appears — delivered by a subscription table that only ever shrinks, over a packet that never says what the criterion was.
Mine a stone block. Nothing about advancements happens when the item is
picked up; nothing happens when it enters the inventory either. What happens
is that `AbstractContainerMenu.broadcastChanges` — the same diff that keeps
your client's inventory in sync — notices that a slot's contents differ from
its remembered copy, and reports the difference. Detection is a **diff, not
an event**, and the advancement system is a subscriber to it.
That is the first of two things this system does backwards from
expectation. The second is bigger: there is **no global list of who is
listening for what**. Each player carries their own subscription table, it
contains only the criteria that player has *not yet satisfied*, and it
shrinks as they play. A veteran player is cheaper to run than a new one, and
the cost of the whole system to a save file falls monotonically over its
life.
Which is also why advancements are the game's general-purpose *"tell me when
the player does X"* facility rather than only a goal list. The recipe book
is unlocked by advancements. `PlayerPredicate` reads advancement progress
back out as a loot condition. All fifty-eight registered triggers exist
because something in the game wanted a hook and this was the hook that
already existed.
## The cast
| class | what it decides | side |
|---|---|---|
| `Advancement` | the immutable definition — parent, `DisplayInfo`, rewards, criteria, requirements, and a **pre-rendered display name** built in the compact constructor, which is the `[Title]` every announcement quotes | both |
| `AdvancementHolder` | the id plus the advancement, with **id-only equality** — so a map keyed by holder survives a pack changing an advancement's contents | both |
| `AdvancementTree` | the parent/child graph, built by a fixed-point loop that refuses any advancement whose parent is not yet a node. An orphan is **discarded**, not re-rooted | both |
| `Criterion` / `CriterionTrigger` | a trigger plus a decoded `CriterionTriggerInstance`, and nothing else — a criterion has no name of its own, the name is the map key, and the **trigger object is stateless** | both |
| `SimpleCriterionTrigger` | the base class for all but one trigger, and the owner of the per-fire sweep | both |
| `AdvancementRequirements` | a list of lists of criterion names: an **AND of ORs**. `AdvancementRequirements.size` counts *clauses*, not criteria | both |
| `PlayerAdvancements` | the per-player subscription table and the dirty sets. The only class here with interesting state | server |
| `TreeNodePosition` | a full tidy-tree layout — three walks, threads, ancestors, shifts — run on the **server**, mutating `DisplayInfo`'s coordinates in place | server |
The shared model is `net/minecraft/advancements`, the triggers are in
`advancements/triggers` and the predicates in `advancements/predicates`
(with the entity half a level down). All 112 classes ship in both jars.
`CriteriaTriggers` registers **fifty-eight** triggers into
`BuiltInRegistries.TRIGGER_TYPES` over **forty-four** classes; the gap is
re-use, with `PlayerTrigger` alone accounting for six registrations.
## The trace: "Stone Age"
`minecraft:story/mine_stone` has one criterion, *get_stone*, whose trigger
is `minecraft:inventory_changed` and whose condition is a single
`ItemPredicate` over the `#minecraft:stone_tool_materials` tag. It has no
rewards at all.
```mermaid
sequenceDiagram
participant ACM as AbstractContainerMenu
participant SP as ServerPlayer
participant ICT as InventoryChangeTrigger
participant PA as PlayerAdvancements
participant AR as AdvancementRewards
participant CPL as ClientPacketListener
participant CAdv as ClientAdvancements
ACM->>ACM: broadcastChanges — this slot differs from lastSlots
ACM->>SP: ContainerListener.slotChanged — which slot, which stack
SP->>ICT: trigger(player, inventory, stack) — count the 43 slots FIRST
ICT->>ICT: SimpleCriterionTrigger.trigger — is this player listening for this trigger at all?
ICT->>ICT: TriggerInstance.matches — one predicate, so test only the changed stack
ICT->>PA: award(mine_stone, get_stone) — after the sweep, never during
PA->>PA: unregisterListeners — the criterion is done, stop watching
PA->>AR: grant(player) — EMPTY here. XP, loot, recipes and a function otherwise
PA->>PA: markForVisibilityUpdate — the ROOT, not the advancement
Note over SP,PA: still the same tick — flushDirty is the last statement of ServerPlayer.tick
SP->>PA: flushDirty
PA->>PA: updateTreeVisibility — re-walk the whole story tree
PA->>CPL: ClientboundUpdateAdvancementsPacket — added, removed, visible progress
CPL->>CAdv: update — rebuild the tree, reconcile the progress
CAdv->>CAdv: AdvancementToast — and silent unless it is a CHALLENGE
```
Each arrow is a decision.
**Counting comes before knowing whether anyone cares.**
`InventoryChangeTrigger.trigger` walks all forty-three slots — thirty-six
inventory plus seven equipment — to compute the occupied, full and empty
counts *before* it asks whether any criterion is listening. That is the
floor cost of every slot change of every player, forever, and it is the most
expensive trigger per fire. (The most *frequent* is `CriteriaTriggers.TICK`,
which fires unconditionally twenty times a second per player.)
**The sweep itself is the cheap part.** `SimpleCriterionTrigger.trigger`
fetches this trigger's map from `PlayerAdvancements.getTriggerMapForType` and
returns immediately if it is null — and it *is* null once every criterion
for that trigger is satisfied, because `PlayerAdvancements.removeListener`
deletes the per-trigger map when it empties. When there is work, it builds
**one** `LootContext` and reuses it for the whole sweep, evaluates the
caller's cheap matcher first and the *player* predicate only for matches,
and does not allocate the results list until the first hit. Note the scope:
one player. Nothing here is a broadcast.
**Matches are collected, then awarded**, because awarding calls
`PlayerAdvancements.unregisterListeners`, which mutates the very map being
iterated.
**Completion is checked against the requirements, not the criteria.**
`AdvancementRequirements.test` is the AND of ORs. Here it is one clause of
one name, so granting the criterion completes the advancement — which fires
the rewards, the chat announcement (built by
`AdvancementType.createAnnouncement`, broadcast to every player, gated on
`GameRules.SHOW_ADVANCEMENT_MESSAGES`) and the visibility dirty flag.
## Visibility is per root, and it gates the wire
`PlayerAdvancements.markForVisibilityUpdate` dirties the **root**, and the
flush re-runs `AdvancementVisibilityEvaluator` — the "how far past your
frontier can you see" rule, with a depth of two — over that root's entire
subtree. Finishing one advancement in a large tree re-evaluates the whole
tree, and only the nodes whose visibility actually *flipped* go on the wire.
`PlayerAdvancements.flushDirty` then sends progress **only for advancements
in `PlayerAdvancements.visible`**. Progress on something hidden, or beyond
your frontier, accumulates server-side and reaches the client the moment it
becomes visible.
Where the flush sits in the tick is worth pinning down, because it produces
a real one-tick delay that nobody expects. Inside `ServerPlayer.tick`,
`AbstractContainerMenu.broadcastChanges` is the fifth statement,
`CriteriaTriggers.TICK` fires mid-tick, and
`PlayerAdvancements.flushDirty` is the **last**. So everything awarded
between those points — a pickup, a kill, an `/advancement grant`, an item
granted by another advancement's reward — coalesces into one packet, and so
does everything that arrived in a packet, because
`MinecraftServer.processPacketsAndTick` drains the inbound queue before the
levels tick at all.
But `ServerPlayer.tick` is not the last thing that happens to a player.
`ServerGamePacketListenerImpl.tick` calls `ServerPlayer.doTick` during the
**connection** phase, which in 26.2 runs *after* the levels
([the server tick](../server/server-tick.md)) — i.e. after
`PlayerAdvancements.flushDirty` has already run. `CriteriaTriggers.LOCATION`, which fires there
every twenty ticks and is what most vanilla biome and structure
advancements hang on, therefore **always lands in the next tick's packet.**
## A criterion's conditions are loot conditions
`ContextAwarePredicate` wraps a list of `LootItemCondition` and evaluates it
against a `LootContext`, reached through `EntityPredicate.createContext`. So
a trigger's conditions are exactly the machinery of
[contexts and predicates](../items/contexts-and-predicates.md), and that is
where most descriptions of the system stop.
It is worth going one step further, because the predicate package is where
four shapes were invented that the whole data-driven half of the game now
reuses.
| shape | what it generalises | where else it turns up |
|---|---|---|
| `MinMaxBounds` | the numeric range, with both a codec **and** a `StringReader` grammar | `3..7` means the same in a predicate, an entity selector and `/random` |
| `CollectionPredicate` | one generic "N of these match", composing `CollectionContentsPredicate` and `CollectionCountsPredicate` | its only users are the six component predicates in `core/component/predicates` |
| `EntitySubPredicate` | a per-mob test as a **registry element** instead of a code branch | the twenty-odd small entity predicates are each a record, a codec and nothing else |
| `DataComponentMatchers` | testing a stack's components without knowing what any of them are | [data components](../foundations/data-components.md) |
Two details change behaviour rather than shape.
`EntityPredicate.ADVANCEMENT_CODEC` accepts *either* a condition list or a
bare entity predicate — though vanilla's own JSON takes the long form every
time, so the short one exists for pack authors rather than for the game.
And `EntityPredicate` declares an explicit type-check-first, NBT-last
ordering for its own sub-tests — a performance invariant hiding inside a
predicate class.
## Questions players ask
**Why does the client show "3/7" if it does not know what the criteria
are?** Because `AdvancementRequirements` *is* on the wire and
`AdvancementProgress.update` reconciles against it. `Advancement.read`
reconstructs the record with an **empty criteria map** and
`AdvancementRewards.EMPTY`, so a client cannot know what any criterion
tests, or that an advancement grants anything at all. And
`AdvancementProgress.getProgressText` returns nothing at all when there is
one clause, which is why a single-criterion advancement never shows "1/1".
(An `AdvancementProgress` with no requirement clauses is permanently
incompletable — `AdvancementRequirements.test` returns false for an empty
list rather than vacuously true — and the only way to hold one is to decode
it off the wire, which is exactly what the client does before its first
update.)
**Why does the tree look the same on every client?** Because it was laid out
on the server. `TreeNodePosition` runs inside `ServerAdvancementManager` and
mutates `DisplayInfo` in place; the coordinates ride the packet. A root with
no `DisplayInfo` is never laid out and never becomes a tab, and a
display-less node in the middle of a tree is transparent — the layout skips
it and adopts its children. One wrinkle in an otherwise deterministic
algorithm: `AdvancementNode.children` is an unordered hash set, so sibling
order inside a tidy-tree layout is hash-dependent.
**Does `/reload` roll back my progress?** No, and the order is the point.
`MinecraftServer.reloadResources` calls `PlayerList.saveAll` and *then*
`PlayerList.reloadResources`, so `PlayerAdvancements.reload` re-reads a file
written moments earlier. What is genuinely lost is progress for any
advancement the new pack has removed or renamed — logged once each, and
invisible to the player except as a full reset packet — plus the selected
tab, which is silently forgotten with no packet, so the client keeps a stale
one.
**When is progress written to disk?** Only when the player is saved. There is
no write on award: `PlayerAdvancements.save` runs from `PlayerList.save`, on
disconnect, on a save-all, or from the reload above. The definitions come
from `data//advancement/.json` through `Advancement.CODEC` — a
duplicate id aborts the reload outright — and per-player state is one JSON
file at `players/advancements/.json`
(`LevelResource.PLAYER_ADVANCEMENTS_DIR`), data-fixed on load through
`DataFixTypes.ADVANCEMENTS`.
**How does the recipe book fit in?** Every recipe advancement is generated
with a `RecipeUnlockedTrigger` criterion and an `AdvancementRewards` naming
the recipe, so earning it calls `ServerPlayer.awardRecipes`
([recipes](../items/recipes.md)). `RecipeUnlockedTrigger` then closes the
loop by letting *other* advancements observe an unlock — comparing the
recipe key by **reference identity**, which is safe only because
`ResourceKey`s are interned.
**Does the listener set ever grow?** Twice.
`PlayerAdvancements.registerListeners` subscribes only to criteria that are
not yet done in advancements that are not yet done, and every award
unsubscribes — *unless* somebody runs `/advancement revoke`, which
re-subscribes, or `/reload`, which re-subscribes everything unfinished in
the new pack.
**Why is `/advancement grant` usable as a conditional?** Because a no-op is
a hard failure: granting an advancement the player already has throws rather
than reporting zero. `AdvancementCommands.Mode` — *only*, *through*,
*from*, *until*, *everything* — is a graph traversal collecting parents or
children or both, and `/advancement grant … everything` calls
`PlayerAdvancements.flushDirty` before the loop with the packet's "show
advancements" flag **true** and after it with the flag **false**, which is
the only purpose that flag has: suppressing a toast storm from the batch it
just granted.
Three smaller surprises, for completeness. `ImpossibleTrigger` has no
trigger method at all — it is the one trigger implementing `CriterionTrigger`
directly, and it exists so a node can anchor a tree while being ungrantable
except by command, which vanilla uses for exactly one file: the invisible
root of every recipe advancement. (It has nothing to do with `/trigger`,
which is a scoreboard feature.) `DisplayInfo`'s announce-to-chat flag is
**write-only on the wire**: the serialiser packs three flags into an int and
omits it, and the reader hard-codes it false while the codec defaults it
true, so the client's copy is wrong for the common case rather than merely
unused. And `PlayerAdvancements.checkForAutomaticTriggers` is dead code in the
strictest sense: it walks every advancement on every player load, but its
whole body sits behind *this advancement has no criteria at all*, and
`Advancement`'s criteria codec rejects an empty map outright. No loaded
advancement can satisfy the guard, so the loop never does anything.
## The screen at the other end
The client's half is five classes in
`net/minecraft/client/gui/screens/advancements` plus `ClientAdvancements`
over in `client/multiplayer` — about 1,240 lines — and it is the payoff for
everything the server did. It does **no tree layout**: it is drawing
positions a data-pack reload decided.
`ClientAdvancements` consumes `AdvancementTree.Listener`, and
`AdvancementTree.setListener` replays every existing root and task at a new
listener immediately, which is how the screen catches up on open.
`AdvancementsScreen` owns the tab strip; `AdvancementTab` owns one root's
pan-and-scroll bounds, auto-centres on first render and clamps the drag;
`AdvancementWidget` scales the server-decided coordinates by a fixed factor,
draws the connector lines to its parent, and wraps its own tooltip text.
`AdvancementTabType` is the one with a hard limit in it: four header
geometries with room for eight tabs above, eight below and five on each
side. **A data pack's twenty-seventh root is silently unreachable.**
Two more things ride this boundary and go nowhere.
`ServerboundSeenAdvancementsPacket` has a "closed screen" action that is
serialised, deserialised and dropped. And `Advancement.sendsTelemetryEvent`
is consumed **only** on the client, by `WorldSessionTelemetryManager`, and
only for advancements in the *minecraft* namespace — which is the whole
reason a flag rides a wire form that drops the criteria and the rewards
([what this book skips](../anatomy/what-this-book-skips.md)).
## Where to look
`Advancement` and `AdvancementRequirements` for the model;
`PlayerAdvancements` for everything that actually happens;
`SimpleCriterionTrigger` and `InventoryChangeTrigger` for the hot path;
`MinMaxBounds` and `CollectionPredicate` for the predicate shapes that recur
everywhere else; and `AdvancementVisibilityEvaluator` for the one rule
nobody guesses right.
---
*Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes `tools/verify_names.py`.*
---
# Scores, teams and stored data
> Verified against **Minecraft 26.2** · Part XIII · `execute as @a store result score @s ticks_frozen run data get entity @s TicksFrozen`: one command writing a scoreboard through a callback the inner command has never heard of, and the two data models `execute store` exists to join.
Look at the sidebar on a well-built server and some of the names in it are
not players. *#total*, *constant*, *.timer* — rows belonging to nothing
alive. That is not a hack layered on top of the scoreboard; it is the
scoreboard working exactly as written, and one method override explains it.
`Entity.getScoreboardName` returns the entity's UUID string.
`Player.getScoreboardName` overrides it with the profile name. There is one
flat map from *string* to a row of scores, holding players by name, mobs by
UUID, and anything else you care to type. From that single override follows
the whole of scoreboard folk practice: **why fake players exist** (nothing
checks that a key belongs to an entity), **why a mob's score can never
appear in the tab list** (whose holder is built from a profile name), and
**why renaming a player orphans their scores**.
Three systems share this page because they share a command and an instinct.
The scoreboard is the game's general-purpose *number per thing*. The teams
live in the same package and are read by five subsystems that have nothing
to do with scores. Command storage and the NBT path language are the other
half — a place to put a tag belonging to no block and no entity, and a query
language for reaching into any tag at all. And `execute store` is the seam:
the only construct in the game that takes the result of an arbitrary command
and writes it somewhere. It has **three** sinks, and two of them are these
two models — a score, and a path into a block, an entity or a storage. (The
third is a boss bar's value or maximum, which shares its implementation with
the score sink and belongs to
[the HUD](../client/hud.md).)
The instinct they share is worth stating before the classes, because it
explains three otherwise-odd decisions: **the server is the only participant
that knows anything.** The client is sent scores it can draw and nothing
else — not an objective's criteria, not a score's lock bit, not which
objectives exist. There is **no serverbound packet in this whole system**.
Every write is a command.
## The cast
| class | what it decides | side |
|---|---|---|
| `Scoreboard` | six maps and nothing else, plus ten empty hooks that are the entire extension surface. A pure data structure, used unmodified by the client | both |
| `Objective` | criteria, display name, render type, number format, auto-update — and a **back-pointer to the scoreboard**, so every setter reports its own change | both |
| `Score` | four mutable fields: the value, a lock bit, a display component and a number format. `ReadOnlyScoreInfo`, `PlayerScoreEntry` and `ScoreAccess` are the other three faces of it | both |
| `ScoreHolder` | the interface `Entity` implements, with `ScoreHolder.forNameOnly` minting anonymous ones — the most consequential class on the page | both |
| `PlayerTeam` | the **only** subclass of `Team`: the mutable state, the setters, a precomputed display style, and friendly-fire plus see-invisibles packed into one wire byte | both |
| `ServerScoreboard` | three fields — the server, `ServerScoreboard.trackedObjectives`, and one dirty boolean — and thirteen overrides across ten hooks, each conditionally broadcasting, then marking dirty. It lives in `net/minecraft/server`, not beside `Scoreboard` | server |
| `NbtPathArgument` | 874 lines, the largest argument type in the game, and a whole query language: six node kinds and a depth limit of 512 | both |
| `DataCommands` | `/data`, over three `DataAccessor`s — `BlockDataAccessor`, `EntityDataAccessor` and `StorageDataAccessor` | server |
`net/minecraft/world/scores` is sixteen files and 1,442 lines — the whole
model — and every class in it ships in both jars. Beside it:
`CommandStorage`, a lazy façade over one `SavedData` *per namespace*, so
`minecraft:foo` and `mypack:foo` are different files; and
`net/minecraft/network/chat/numbers`, seven files and 167 lines, holding
`NumberFormat` with the three kinds `NumberFormatTypes` registers.
## The trace: a store through both models
```mermaid
sequenceDiagram
participant Cmds as Commands
participant BC as BuildContexts
participant ExecC as ExecuteCommand
participant DataC as DataCommands
participant SS as ServerScoreboard
participant SA as ScoreAccess
participant CPL as ClientPacketListener
Cmds->>BC: the parsed chain — @s and the objective name are still strings
BC->>BC: "as @a" forks: N sources, one per player
BC->>ExecC: "store result score" is a redirect, run once per source
ExecC->>ExecC: resolve @s and the objective NOW, and chain a callback onto the source
BC->>DataC: the leaf runs: getData(accessor, path)
DataC->>DataC: EntityDataAccessor.getData — the entire entity save, built fresh
DataC->>DataC: NbtPath.get, then collapse the tag to one int by four rules
DataC->>ExecC: the result reaches the SOURCE's callback, not the frame's
ExecC->>SS: getOrCreatePlayerScore — without forceWritable
SS->>SA: set(value)
SA->>SS: onScoreChanged — only if the objective is in a display slot
SS->>CPL: ClientboundSetScorePacket, broadcast to every player
CPL->>CPL: forNameOnly(owner) — the client only ever has the string
```
Each arrow is a decision.
**The store target is resolved before the inner command runs, not after.**
`ExecuteCommand.wrapStores` builds the store node as a **redirect with a
modifier**, not as a step after the leaf. The modifier resolves the score
holder and the objective against *this* source, then decorates the source
with a callback chained onto whatever was already there. So the store is a
property of the *source*, which is why several stores compose, and why each
forked player writes their own row without the leaf command knowing a
scoreboard exists.
**The result travels by callback, not by return value** — the source's own
callback, which is precisely why `execute store` works on a command typed in
chat whose *frame* callback is empty
([the execution engine](the-execution-engine.md)).
**`/data get` builds the whole entity to read one field.**
`EntityDataAccessor.getData` produces the entity's entire save tag, freshly,
and the path then walks it. That is the real cost of `/data get`, and it is
why a per-tick `/data get` on a busy entity is a measurable expense.
**Four rules collapse a tag to one integer.** A numeric tag floors its double
value; a collection and a compound both yield their *size*; a string yields
its length. Nothing yields the value you might have meant:
`data get entity @s Inventory` returns the number of stacks you are
carrying, not 36 — `Inventory.save` writes only the non-empty slots, so the
collection whose size you get is as short as your inventory is empty. A path matching more than one tag is an error; a path matching
none is a different error.
**The write can be a silent no-op, and may never reach the wire at all.**
`ScoreAccess.set` writes nothing and sends nothing when the value is
unchanged and the score is not new — unless the objective has auto-update
on, in which case it refreshes the display name first and a changed display
counts as a change on its own. And `ServerScoreboard.onScoreChanged` is
gated on the objective being in `ServerScoreboard.trackedObjectives`, whose
only entrance is occupying a display slot.
## Why a write is a handle
`Scoreboard.getOrCreatePlayerScore` does not return a `Score`. It returns a
`ScoreAccess` — an anonymous object closing over the score, the objective
and the holder, plus **two decisions computed once, at handle-creation
time**: may this be modified (did the caller ask for a force-writable
handle, or is the criteria not read-only), and was this score *newly
created* by the lookup that produced this handle.
That is the whole answer to "why not a setter". A setter would have to
re-derive the first fact on every call, and the second is unrecoverable
after the fact — once the `Score` exists, nothing can tell whether *this*
call created it. Newness is what decides whether an unchanged value still
needs a packet, so it has to survive from the lookup to the write. The
handle is also the one place that knows when to fire the change hook, which
is why every write path in the game funnels through
`ScoreAccess.set`, `ScoreAccess.add`, `ScoreAccess.increment`,
`ScoreAccess.reset`, `ScoreAccess.lock`, `ScoreAccess.unlock` and
`ScoreAccess.numberFormatOverride`.
Nothing here is ticked, either. `MinecraftServer` mentions the scoreboard
five times in total — the field, the constructor, the load, the getter, and
one call to `ServerScoreboard.storeToSaveDataIfDirty` inside
`MinecraftServer.saveAllChunks`. There is no periodic sweep and no
dirty-queue drain: every mutation broadcasts its own packet synchronously,
inside the call that made it, and there is **one scoreboard per server, not
per level**, so scores and teams are global across dimensions.
## What a criterion can be, which is nearly anything
`ObjectiveCriteria` looks like an enum of eleven values and is not.
Forty-three constants exist, thirty-two of them generated — sixteen
team-kill and sixteen killed-by-team criteria, one per team colour. Six are
read-only: health, food, air, armour, experience and level.
And then the tail. **`Stat` extends `ObjectiveCriteria`**, so every
statistic in the game *is* a criterion, and `ObjectiveCriteria.byName`
parses a colon-separated name by looking the left half up as a stat type and
the right half in that stat type's own registry.
`minecraft.mined:minecraft.stone` is not a special case; it is the
statistics registry addressed through a string. Nine stat types over the
block, item, entity-type and custom-stat registries make thousands of valid
criteria names, which is why `/scoreboard objectives add` accepts far
more than the forty-three bare criteria names — its suggestions offer those
*and* every stat name in every stat type's registry.
The identity-keyed reverse index is what makes that cheap.
`Scoreboard.objectivesByCriteria` is an **identity** map from criteria to
the objectives watching it, `ServerPlayer.awardStat` hands the `Stat` object
itself to `Scoreboard.forAllObjectives`, and object identity finds the
watchers — sound only because stat objects are interned in their registries.
Criteria-driven scores are the one part of this page with a schedule, and it
is narrower than it sounds. `Scoreboard.forAllObjectives` has **seven call
sites and every one is in `ServerPlayer`**: the six read-only criteria, the
death count, two kill counts, the two team-kill criteria and the two
statistics hooks. No *criterion* is driven from `Entity`, `LivingEntity` or
`Mob`, so a skeleton killing a zombie increments nobody's kill count —
though `LivingEntity` does reach the scoreboard once, calling
`Scoreboard.addPlayerToTeam` when it reads its own saved team back. The six read-only criteria are change-detection diffs — six
consecutive comparisons against remembered fields — living in
`ServerPlayer.doTick`, which runs in the **connection** phase, after the
levels have ticked ([the level tick](../server/server-level-tick.md)). So
damage taken during the level tick reaches the scoreboard, and the wire,
later in the same tick rather than during it.
## The path language, and the accessor that is coarser than it looks
Six node kinds — a named child, a match on an object, a match on the root
object, a match on a list element, all elements, and an index — with a depth
limit of 512.
The elegant part is **creation**. The parent-creating walk goes through the
nodes and, for each one, asks *the next node* what shape its parent has to
be: a named child wants a compound, an index wants a list. So a *set*
through `a.b[0].c` materialises a compound, a list and a compound without
any node knowing more than its own type. Removal is the same walk with a
plain lookup instead, so it never creates.
The three accessors, by contrast, are coarse. `DataAccessor` has two methods
that matter — read the whole tag, write the whole tag — and **no path-aware
write anywhere**. Every `/data modify` is *read everything, mutate in
memory, write everything back*, which is why a block-entity write reloads
the block entity and marks the chunk dirty, and why an entity write
round-trips the entity through its own load path and then restores the UUID
by hand, because loading would have overwritten it. What each accessor
*does* contribute is its own grammar subtree, which is how `DataCommands`
builds the target half and the source half of every subcommand from one list
of three providers applied twice.
## Teams, which five systems read and none of them are scores
`Team` declares everything a reader asks for and `PlayerTeam` is its only
subclass. What makes teams worth their own paragraph is who consults them,
because it is not the scoreboard: collision through
`EntitySelector.pushableBy`; nametag visibility through
`LivingEntityRenderer.shouldShowName`; invisibility through
`Entity.isInvisibleTo`; friendly fire through `Player.canHarmPlayer`; and
death-message routing through `ServerPlayer.die`. Only `ServerPlayer.die` and
`LivingEntityRenderer.shouldShowName` are reached from anywhere near one
place; `EntitySelector.pushableBy` and `Player.canHarmPlayer` have six call
sites each and `Entity.isInvisibleTo` two. The locator bar is a sixth
reader, reached the other way round: every team join, leave and modification
calls through to `ServerWaypointManager` to remake the connections and
re-colour the icons — the team system driving a waypoint
system.
Two team behaviours are worth pinning. `Team.isAlliedTo` is **reference
equality**, so two teams with byte-identical settings are never allied;
every "same team?" test in the game is really "same object?", safe only
because `Scoreboard.teamsByName` is the single owner of every instance. And
a team has two visibility settings of which only one ships: the wire
parameters carry nametag visibility, while death-message visibility has a
single reader in `ServerPlayer.die` and the client never learns the rule
because it does not need to. Nametag visibility also *widens*: when an
entity has a team, `LivingEntityRenderer.shouldShowName` returns from the
team switch directly and never reaches the checks that hide a name behind
F1, for the camera entity, or for a vehicle — so a mob on a team set to
*always* keeps its name through the HUD toggle.
## What the client is ever told
Five packets, all server → client, and no serverbound counterpart exists:
`ClientboundSetObjectivePacket`, `ClientboundSetDisplayObjectivePacket`,
`ClientboundSetScorePacket`, `ClientboundResetScorePacket` and
`ClientboundSetPlayerTeamPacket`. All five go through
`PlayerList.broadcastAll` — no distance filter, no dimension filter
([what the client is told](../networking/what-the-client-is-told.md)).
An objective in no display slot **does not exist on the network**.
`ServerScoreboard.onObjectiveAdded` sends nothing at all; the only path into
the tracked set is `ServerScoreboard.setDisplayObjective`. A scoreboard with
two hundred objectives and an empty sidebar costs zero bandwidth — and
putting one *into* a slot then ships every score it holds, to every player,
at once. The join burst is the same shape:
`PlayerList.updateEntireScoreboard` sends every team with its full member
list, then walks all nineteen display slots and ships each distinct
occupying objective with all of its scores.
What the client is told is also less than it looks. `ClientPacketListener`
constructs every objective it receives with `ObjectiveCriteria.DUMMY`, so a
client cannot tell a health objective from a dummy one — it only knows to
draw hearts — and the score packet carries no lock bit, which is why
`/trigger`'s suggestions have to be computed on the server.
There is a third route by which a score reaches a client, and it carries no
score packet at all: a `{"score":…}` or `{"nbt":…}` in a text component.
`ScoreContents` and `NbtContents` resolve on the **server**, against the
authoritative scoreboard, and put the *result* on the wire — never the
reference ([text components](../foundations/text-components.md)). A
`/tellraw` is a photograph, not a subscription.
Saving is one boolean for the entire scoreboard, cleared by re-packing the
whole thing, and it happens only when the world is saved — the autosave,
`/save-all`, or shutdown: **a score set and a crash a tick later is a score
lost.** `ScoreboardSaveData` sits under
`minecraft:scoreboard` beside the world, with one command-storage file per
namespace, both through the data fixer
([level data and rules](../../reference/level-data-and-rules.md)). The NBT
field names are the archaeology — *Objectives*, *PlayerScores*,
*DisplaySlots*, *Teams*, and inside them *Name*, *CriteriaName*,
*RenderType*, *Locked* — capitalised, pre-flattening conventions, preserved
by codec.
## Questions players ask
**Why does a `#` in front of a name hide the row?** Because `#` does two
unrelated things. In `ScoreHolderArgument` it skips entity resolution
entirely, so the token is taken as a literal name; and in the sidebar
`PlayerScoreEntry.isHidden` filters the row out. One character, two
mechanisms, and together they are the whole hidden-fake-player idiom. (The
argument type has four resolution branches in order — the wildcard, a `#`
name, a UUID searched across every level, an online player — and the last
three fall back to a bare name. The wildcard does not: with no tracked
holders at all it throws.)
**Why is my sidebar not `DisplaySlot.SIDEBAR`?** If the local player is on a
team *with a colour*, `Hud` uses that colour's own display slot and falls
back to the plain sidebar otherwise. The colour-to-slot mapping lives on
`TeamColor`, not on `DisplaySlot`, and it is what the sixteen team sidebars
are for. The sidebar shows fifteen rows and **hides before it cuts**: hidden
rows are filtered out, then the rest sorted by value descending and name
case-insensitively, and *then* truncated to fifteen.
**Why did `/trigger` say the objective is not enabled?** `Score`'s lock bit
starts **locked** and its codec defaults **unlocked**, so a score created by
`/scoreboard players set` is locked while a score loaded from a file that
omits the field is not. `/trigger` is the only command an unprivileged
player can use to write a score, and its gate is three-part — the criteria
must be the trigger criteria, the score must already exist, and it must be
unlocked — and the command re-locks it immediately, so each *enable* buys
exactly one use. It is also the only command in this area registered with no
permission requirement at all.
**Why did my `execute store` throw an internal error?** Because
`/scoreboard` refuses a read-only objective and `execute store` does not
check. The command resolves its write targets through
`ObjectiveArgument.getWritableObjective`; `ExecuteCommand.wrapStores` uses
the plain lookup and never asks for a force-writable handle. So
`execute store result score @s ` reaches
`ScoreAccess.set` with modification disallowed and raises a raw runtime
exception from inside a result callback rather than a command error. The
same hole is reachable through `/scoreboard players operation` with `><`,
the one operator that writes both sides.
**Why did my `execute store` into a data target do nothing at all?** Its
read-mutate-write is wrapped in a catch with an **empty body**: a malformed
target, an uncreatable path, a too-deep path, a block that stopped being a
block entity — no message, no failure, no write. The score sink has no such
catch.
**Why can I read a player's NBT but not write it?** The entity accessor's
write path rejects any `Player` before doing anything else, and the read
path has no such check. That one asymmetry is why every player-NBT technique
is read-only. Relatedly, a no-op is a **hard failure** in four places —
`/data merge`, `/data modify`, `/data remove` and
`/scoreboard players enable` all throw when they changed nothing, which
makes them usable as conditionals in a function, the same choice
`/advancement grant` makes.
**Do a mob's scores survive it despawning?** They survive *unloading* and
die with the mob: `Scoreboard.entityRemoved` runs from the level's
destruction callback and is gated on the entity being both non-player and
not alive.
Two smaller things. A number format can ignore the number entirely —
`FixedFormat` renders a constant component whatever the score is — and
resolution order is per-score override, then per-objective, then a per-site
default: red in the sidebar, yellow in the tab list, unstyled below the
name. And below-name numbers are computed in `Entity`, not in a renderer,
with their range as an **attribute**: `Attributes.BELOW_NAME_DISTANCE`,
syncable, default ten, maximum 512, so a server can change how far away a
player's below-name score is legible, per entity
([attributes](../entities/attributes.md)).
**A failing command under `store result` writes 0**, whichever kind it is.
For the custom-executor path the answer is in the game's own packages —
`CustomCommandExecutor.WithErrorHandling` reports failure through the
callback and a failure result is a zero, so a failing `/function` writes 0.
For an ordinary leaf the result consumer is driven by Brigadier, and
`ContextChain.runExecutable` catches the `CommandSyntaxException` and calls
the consumer with *success false, result 0* before rethrowing. The game
hands that consumer straight through from the source's own callback, so the
two paths agree: a store target written by a command that threw holds zero,
not its previous value.
## Where to look
`Scoreboard` for the six maps, `ScoreAccess` for why a write is a handle,
and `ServerScoreboard.trackedObjectives` for the one field that decides what
a client ever knows. `ObjectiveCriteria.byName` for the statistics bridge,
`NbtPathArgument.NbtPath` for the nicest ten lines in the area, and
`ExecuteCommand.wrapStores` for the one that makes `execute store` stop
feeling like magic.
---
*Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes `tools/verify_names.py`.*
---
# Dialogs
> Verified against **Minecraft 26.2** · Part XIII · You click a server in the multiplayer list and, before the world has loaded — before you are in a world at all — a form appears with text boxes on it, and it is not part of Minecraft.
A dialog is a data pack's form: a title, some body text, some inputs and
some buttons, decoded from JSON and put on your screen. Nothing about that
is surprising until you notice which protocol phase it works in.
`ClientboundShowDialogPacket` is registered in **both** the play and the
configuration protocols ([protocol phases](../networking/protocol-phases.md)),
so a server can interrupt the join handshake to ask you something. Vanilla
only ever does it from a dev-flag-gated command — but the machinery is
there, complete, in the shipped jar.
And the reason it works there is not a special case bolted on; it is a
second codec, and it explains itself. The configuration buffer is a plain
byte buffer with **no registry access**, so the packet cannot carry a holder
id. `Dialog.CONTEXT_FREE_STREAM_CODEC` therefore sends the whole dialog
inline. What is "context-free" is the *buffer*, not the payload.
A dialog is also one of the two clearest instances of a move Mojang has been
making everywhere — take something that used to be a Java class and make it
a registry element loaded from a data pack. That argument is made once, for
all its instances, in
[the data-driven type pattern](../foundations/data-driven-types.md); this
page assumes it. Four of the pattern's registries are dialog registries.
## The cast
| class | what it decides | side |
|---|---|---|
| `Dialog` | the registry element. `Dialog.DIRECT_CODEC` dispatches on `BuiltInRegistries.DIALOG_TYPE`; there are two stream codecs, and which one is used decides the whole page | server |
| `CommonDialogData` | what every dialog embeds: titles, whether escape closes it, whether it **pauses the game**, the after-action, the body elements and the inputs. Its `MapCodec` is where the pause validation lives | server |
| `DialogAction` | close, none, or wait-for-response — and `DialogAction.willUnpause` is what that validation tests | server |
| `InputControl` | `TextInput`, `SingleOptionInput`, `BooleanInput`, `NumberRangeInput`. An `Input` is a key plus a control, and the key must be a valid **macro** variable name | server |
| `Action` | produces an optional `ClickEvent` from the *live* input values, through `Action.ValueGetter` | server |
| `ClickEvent` | extended with `ClickEvent.ShowDialog` and `ClickEvent.Custom`, which is how anything clickable can open a dialog | both |
| `DialogScreens` | the codec-to-screen-factory map, with `DialogScreen` as the base and `DialogControlSet` owning the live getters | client |
| `DialogConnectionAccess` | the phase-specific way back to the server — and the configuration-phase one refuses to run commands | client |
`net/minecraft/server/dialog` is thirty-one classes across four packages,
all in the server jar; the screens that render them are client-only in
`net/minecraft/client/gui/screens/dialog`. The five kinds
`DialogTypes.bootstrap` registers are `NoticeDialog` and `ConfirmationDialog`
(both `SimpleDialog`) and `MultiActionDialog`, `DialogListDialog` and
`ServerLinksDialog` (all `ButtonListDialog`) — and both of those supertypes
are interfaces, not classes.
## The trace: a data pack puts a form on the screen
```mermaid
sequenceDiagram
participant RDL as RegistryDataLoader
participant DlgC as DialogCommand
participant SP as ServerPlayer
participant CComPL as ClientCommonPacketListenerImpl
participant DlgS as DialogScreen
participant MS as MinecraftServer
RDL->>RDL: Registries.DIALOG from data/ns/dialog — and synced at configuration
DlgC->>SP: openDialog(holder) — /dialog show, or a ClickEvent.ShowDialog anywhere
SP->>CComPL: ClientboundShowDialogPacket — a holder id, or the whole dialog inline
CComPL->>DlgS: DialogScreens.createFromData — pick the screen for the codec
DlgS->>DlgS: DialogControlSet.addInput — each input registers an Action.ValueGetter
DlgS->>DlgS: click — Action.createAction reads the getters NOW, not earlier
DlgS->>MS: ServerboundCustomClickActionPacket — an id plus the inputs as NBT
MS->>MS: handleCustomClickAction — vanilla logs it at debug and stops
```
The trace turns on one decision: **when are the input values read?** Not at
packet time and not at screen construction. `DialogControlSet` keeps a map
of live `Action.ValueGetter`s and `Action.createAction` calls them at the
moment of the click — which is why the same `Action` object produces a
different command each time, and why `CommandTemplate` can be a template
rather than a string. `ActionTypes` registers nine kinds: the seven
click-event kinds a server is allowed to send, plus `CommandTemplate` and
`CustomAll`, which packs every input value into an NBT compound.
That set of nine is derived from the click-event enum **at class-init**, so
every click-event kind a server may send is automatically a dialog action of
the same name — and the one kind that is not allowed, opening a local file,
can never be one.
Nothing on the server side of this ever ticks. A dialog is a packet send
from whatever ran the command or handled the click; the reply hops off the
Netty thread onto the server's `PacketProcessor` before
`MinecraftServer.handleCustomClickAction` sees it, and on the client
`ClientCommonPacketListenerImpl.handleShowDialog` hops to the client's
processor before touching the screen stack. Exactly one thing in this system
ticks: `WaitingForResponseScreen`, counting ticks to un-grey its escape
button.
## Four ways a dialog opens, and one of them is not a click
`ServerPlayer.openDialog` is the server-side entry point, and `/dialog show`
is the obvious caller. The interesting ones are the click events, because
"a component with a click event" is not the same as "a component whose click
events are dispatched". There are three places on the client where they
actually are — chat, a book, and `DialogScreen` itself, which dispatches its
own buttons and body text — and one route that is not a click dispatch at
all: `SignBlockEntity` reads the event
**server-side** and calls `ServerPlayer.openDialog` directly. An item's name
or lore is tooltip text and dispatches nothing.
Two tags round it out. `DialogTags.PAUSE_SCREEN_ADDITIONS` and
`DialogTags.QUICK_ACTIONS` let a data pack add buttons to the pause menu and
to a hotkey, so a dialog need not be pushed by the server at all — and
`Dialogs` holds the three the jar ships. Closing one from the server is
`ClientboundClearDialogPacket`, registered in both phases like the other
two.
Inside a dialog, the parts dispatch on registries of their own the same way
the dialog does: `DialogBody` over `BuiltInRegistries.DIALOG_BODY_TYPE`
(`PlainMessage` and `ItemBody`), `InputControl` over
`BuiltInRegistries.INPUT_CONTROL_TYPE`, and `ActionButton` carrying a
`CommonButtonData` of label, tooltip and width. `DialogBodyHandlers` and
`InputControlHandlers` are the client-side factory maps that mirror them.
An input's key is validated by `ParsedTemplate` against
`StringTemplate.isValidVariableName` — the same rule a macro function's
parameters obey, which is the seam into
[functions and macros](functions-and-macros.md), and why `CommandTemplate`
can substitute a dialog's inputs into a command at all.
## What a data pack cannot do
Three defences are built into the model rather than into any particular
dialog, and each one exists because the feature would otherwise be a way to
trap a player.
**The exit is not optional.** `DialogScreen`'s initialisation is final: it
*always* adds a warning button that opens a nested confirm screen offering
to disconnect, and repositions it if a layout would push it off-screen. An
action that waits for a response swaps in `WaitingForResponseScreen`, which
reveals a Back button after a second and enables it after five.
**Pausing is validated by the codec, wherever it decodes.** A dialog that
pauses the game with an after-action that never unpauses is rejected,
because it would strand the player in a paused world. The check sits on
`CommonDialogData`'s codec rather than on the loader, and `MapCodec.validate` is
applied to both directions of the codec — so it runs on the server as it
encodes *and* on the client as it decodes, which is what covers a dialog sent
inline in the configuration phase.
**A button that runs a command is not simply a chat command.** It goes
through `ClientPacketListener.sendUnattendedCommand`, which parses the string
once — and a second time, against a no-permission source, only if that first
parse succeeded and needs no signature — and pops a confirmation screen if
the command fails to parse, needs a signature, or needs a permission the
client believes it lacks ([permissions](permissions.md)). A command with
none of those problems is sent with no screen at all. And the configuration-phase
`DialogConnectionAccess` refuses to run commands at all, logging a warning
instead.
## The extension point vanilla does not use
`MinecraftServer.handleCustomClickAction` is one line, logging at debug.
The entire custom-action mechanism — an arbitrary id plus an arbitrary NBT
payload, sent by a screen the server described — exists for data packs and
server software to build on. The game itself only defines the transport, and
defends it with a 32 KB NBT accounter and a 64 KB cap on the payload's own length prefix.
The same is true one level up: the only vanilla sender of a
configuration-phase dialog is `DebugConfigCommand`, which is gated on
`SharedConstants.DEBUG_DEV_COMMANDS` *or* `SharedConstants.IS_RUNNING_IN_IDE`,
**and** dedicated-server-only. A server
really can put a form in front of you before you are in the world. Vanilla
never does.
## Where to look
`Dialog` and `CommonDialogData` for the model, then `Action` — the
value-getter indirection is the only subtle thing in the whole system.
`DialogScreens` for how a codec becomes a screen, and
`MinecraftServer.handleCustomClickAction` for the one line that is the
extension point.
---
*Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes `tools/verify_names.py`.*
---
# Game tests
> Verified against **Minecraft 26.2** · Part XIII · Run `/test run *` on a vanilla server and one test runs, and passes. The suite is not in the game — a test is a data-pack file, the Java body is a value the JSON points at, and the shipped jar declares exactly one of each.
Game tests are how Mojang checks that a piston still pushes and a hopper
still pulls: a small structure is pasted into a spare corner of a world, a
test body runs against it for a bounded number of ticks, and a block beside
it turns green or red. That much has been true for years. What changed is
where a test *lives*.
There is no *GameTest* annotation any more, and no test registry class. A
test is a **registry element** loaded from `data//test_instance/`, and
the Java body — when there is one — is a value in a second registry,
`Registries.TEST_FUNCTION`, that the JSON points at. The Java half is a
payload, not the declaration. Which means the shipped jar contains
`GameTestInstances`' single always-pass instance, `BuiltinTestFunctions`'
body for it, and `GameTestEnvironments`' default environment — an empty
`TestEnvironmentDefinition.AllOf` — and the real suite lives in Mojang's
test sources, not in the game you downloaded.
This is [the data-driven type pattern](../foundations/data-driven-types.md)
again, and game tests are its most complete instance: two data-pack
registries and two built-in type registries between them.
## The cast
| class | what it decides |
|---|---|
| `GameTestInstance` | the registry element. `GameTestInstance.run` takes a `GameTestHelper` and is the body — `BlockBasedTestInstance` needs no Java at all, `FunctionGameTestInstance` invokes a `Registries.TEST_FUNCTION` entry |
| `TestData` | the declaration record every instance delegates to: environment, structure, tick budgets, required, rotation, manual-only, the two retry counts, sky access, padding |
| `TestEnvironmentDefinition` | the seven ways to bend the world for a test, shaped as an **undo log** |
| `GameTestBatch` | a group of tests keyed by their environment holder. A batch *is* an environment |
| `GameTestRunner` | owns the batches and the structure spawner, and re-queues a failure when the retry options say so |
| `GameTestInfo` | one *run* of one test: its position, its timeout, its sequences, its outcome |
| `GameTestHelper` | 1,353 lines, and the entire surface a test body sees — coordinate translation, world edits, spawning, assertions, outcomes |
| `TestInstanceBlockEntity` | 551 lines: the block that owns a test's bounding box, status and beacon beam, and does the real work of placing, saving and encasing the structure |
`net/minecraft/gametest/framework` is forty-four classes, all server-side,
with `net/minecraft/gametest/Main` as the headless entry point beside it.
## The objects, and how they nest
```mermaid
flowchart TB
subgraph D["THE DECLARATION — data pack files"]
TI["GameTestInstance — data/ns/test_instance"]
TD["TestData — environment, structure, tick budgets, retries"]
TE["TestEnvironmentDefinition — data/ns/test_environment"]
ST["a structure — data/ns/structure"]
TI --> TD
TD --> TE
TD --> ST
end
subgraph R["THE RUN — one object per attempt"]
GB["GameTestBatch — every test sharing ONE environment holder, split into runs of fifty"]
GI2["GameTestInfo — one run of one test: position, timeout, sequences, outcome"]
GH["GameTestHelper — test-local coordinates, edits, assertions"]
GB --> GI2
GI2 --> GH
end
subgraph W["THE WORLD — what a test costs"]
TIB2["TestInstanceBlockEntity — bounding box, status, beacon beam, barrier shell, forced chunk"]
TB2["TestBlock — start, log, fail and accept, for a test written with no Java"]
end
D --> R
R --> W
```
**A batch is not a name and not a class: it is an environment.**
`GameTestBatchFactory` groups tests by their `TestEnvironmentDefinition`
holder, because that is what `GameTestBatch` is keyed by, and each group is
split into runs of fifty (a default the builder can change, not a cap). One
environment is active at a time on the runner, and moving between batches
tears the old one down and stands the new one up.
**The environment interface is an undo log.**
`TestEnvironmentDefinition.setup` returns a value that
`TestEnvironmentDefinition.teardown` is handed back. Five of the seven kinds
return the *previous* state and restore it;
`TestEnvironmentDefinition.Functions` returns nothing and runs a *different*
data-pack function on the way out; and `TestEnvironmentDefinition.AllOf`
returns its children's activations and unwinds them in reverse.
## The trace: one test runs
```mermaid
sequenceDiagram
participant TC as TestCommand
participant GTR as GameTestRunner
participant TIB as TestInstanceBlockEntity
participant GTT as GameTestTicker
participant GI as GameTestInfo
participant RGL as ReportGameListener
TC->>GTR: build one GameTestInfo per test, batched by environment
GTR->>GI: spawn each info — prepareTestStructure
GI->>TIB: placeStructure, then encaseStructure — a barrier shell round the test
GTR->>GTR: TestEnvironmentDefinition.setup — returns the undo log
GTR->>GTT: add every info whose structure was placed to the ticker
GTT->>GI: tick — counting up from NEGATIVE: the setup ticks run before tick zero
GI->>GI: GameTestInstance.run(helper) at tick zero, and sequences tick after
GI->>RGL: succeed, or a GameTestException — a timeout is just another one
RGL->>TIB: setSuccess or setErrorMessage — the beam turns green, red or orange
RGL->>RGL: say to chat, and GlobalTestReporter to the log or to JUnit XML
```
Game tests tick on the server thread, from `MinecraftServer.tickChildren` in
a profiler section named after the subsystem — after connections and players
and the debug subscribers, before the server GUI refresh and chunk sending —
and only when the tick-rate manager reports the game running normally, so
`/tick freeze` suspends them.
**Setup ticks run before tick zero.** `GameTestInfo.startExecution` starts
its counter *negative* — by the declared setup ticks, plus the spawner's own
tick delay, plus one — so the body runs when the count reaches zero. `GameTestSequence` is the "do this, wait, then assert that"
chain — `GameTestSequence.thenExecuteAfter`,
`GameTestSequence.thenWaitUntil`, `GameTestSequence.thenSucceed` — and it
uses an exception as ordinary control flow, at most one thrown and swallowed
per sequence per tick, and only for an assertion failure. A timeout is not
caught there.
**Reporting is a listener chain, and it writes to four places.**
`ReportGameListener` is what says something in chat and what writes the
outcome back to the `TestInstanceBlockEntity` that owns the beam;
`MultipleTestTracker` is the progress bar, with five states including a
space for *not started*; and `GlobalTestReporter` dispatches to
`LogTestReporter` or `JUnitLikeTestReporter`.
## A test with no Java in it
`BlockBasedTestInstance` runs a test built entirely from `TestBlock`s inside
the structure. `TestBlockMode` has four values — start, log, fail and accept
— and the rules are as simple as they sound: exactly one start block emits
redstone to begin, an accept block being triggered is a pass, and a fail
block being triggered is a failure carrying its stored message. That is a
unit test authored in-game with a redstone circuit and shipped as a
structure plus a JSON.
The client half the framework's package list hides is what makes that
practical: `TestInstanceBlockEditScreen` and `TestBlockEditScreen` are how a
test is authored in game, `TestInstanceRenderer` draws the bounding box, and
`GameTestBlockHighlightRenderer` is the sole consumer of
`ClientboundGameTestHighlightPosPacket`. Both serverbound test packets are
sent *by* the client, from those screens: this is the one system in the part
whose *declaration* a client edits, though far from the only one whose
client talks back — commands, suggestions, dialog clicks and advancement tab
switches are all serverbound too.
## Two things a running server should know
**`/test` exists on every server**, not only in a development environment.
Only the export subcommands are gated on running from an IDE. The command
sits at `Commands.LEVEL_GAMEMASTERS` like every other data-pack command in
this part.
**Test instance blocks are points of interest.** Locating every test within
a 250-block radius is a POI query, not a block scan
([points of interest](../world/points-of-interest.md)), which is what makes
`/test`'s radius subcommands cheap — and what makes a world full of saved
tests carry them in its POI storage.
Underneath both, `StructureUtils` and `StructureGridSpawner` are the layer
that clears the space, lays tests out in a grid, transforms the far corner
and finds every test block by position
([jigsaw and templates](../worldgen/jigsaw-and-templates.md) owns the
template machinery they call). `GameTestServer` is a whole `MinecraftServer`
subclass for headless runs, driven by `GameTestMainUtil`, and it overrides
`GameTestServer.waitUntilNextTick` to drain tasks instead of sleeping: the
headless test server runs flat out, and installs a no-op gizmo collector so
debug drawing costs nothing
([what this book skips](../anatomy/what-this-book-skips.md)).
## Where to look
`GameTestInstance` and `TestData` for what a test *is*, then `GameTestInfo`
for what actually happens on a tick, then `TestInstanceBlockEntity` for what
a test costs the world, and `GameTestHelper` when you want to write one.
---
*Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes `tools/verify_names.py`.*
---
# Reference
> Verified against **Minecraft 26.2** · Reference · The shelf behind the lectures: everything a viewer would pause the video to read, kept where no lecture has to stop for it.
A lecture explains one thing at a time, and it cannot stop to list the 43
entity-data serializers or the ten bits of a flag word without losing the
room. The rule for this tier is the one pass 3 set: *would a viewer pause
the video to read this?* If yes, it lives here and the page links to it.
Twenty pages on the shelf and this one in front of them, and the useful way
to tell the twenty apart is not by subject but by **how each one is kept** — because a catalogue is only as good as
the version it was read from, and the first question to ask of any page on
this shelf is *what regenerates it.*
## The shelf
```mermaid
flowchart LR
D["the 26.2 decompile"]
P["the system pages"]
T["TEMPLATE.md, the lane key"]
subgraph G["read off the decompile by gen_reference.py, rewritten on every deploy"]
G1["packets, registries, data components, game rules"]
G2["attributes, entity data serializers, enchantment hooks, loot context parameter sets"]
end
subgraph I["read off the corpus by the checkers, rewritten on every deploy"]
I1["class index, from verify_names.py"]
I2["diagram lanes, from check_lanes.py"]
end
subgraph H["hand-kept by the part sessions, name-verified, re-read every pass"]
H1["block update flags, damage outside LivingEntity, what the HUD draws, submit phases, density-function nodes"]
H2["threads, math and primitives, level data and rules"]
H3["naming drift, glossary"]
end
D --> G
P --> I1
T --> I2
D -. "a session reads one class at a time" .-> H
```
## How each page is kept, and who leans on it
| page | what it lists | kept by | the parts whose landing pages point at it |
|---|---|---|---|
| [Packets](packets.md) | every packet, by protocol group and direction | generated | III, V, VI, VII, VIII, IX, X, XIII |
| [Registries](registries.md) | every registry key: built-in, data-pack, synced | generated | II, V, VI, VII, IX, XII, XIII |
| [Data components](components.md) | every `DataComponentType`, persistent and synced | generated | II, V, VII, VIII, IX |
| [Game rules](gamerules.md) | every rule, type, category, default | generated | III, IV, V, VI, VIII |
| [Attributes](attributes.md) | every attribute: default, range, sentiment, syncable | generated | VI, VIII |
| [Entity data serializers](entity-data-serializers.md) | all 43, in registration order, which is the wire id | generated | VI |
| [Enchantment hooks](enchantment-hooks.md) | every public `EnchantmentHelper` entry point and its callers | generated | VII |
| [Loot context parameter sets](loot-context-params.md) | all twenty-six, with required and optional keys | generated | VII, XIII |
| [Block update flags](block-update-flags.md) | the ten bits of `Level.setBlock`'s flag word | hand-kept | IV, V |
| [Damage outside `LivingEntity`](non-living-damage.md) | what each of the twenty-one non-living classes does when hit | hand-kept | VI |
| [What the HUD draws, and when](hud-elements.md) | every HUD element and the condition it is behind | hand-kept | X |
| [Submit phases and feature renderers](submit-phases.md) | the fifteen phases and the thirteen renderers, in declaration order | hand-kept | XI |
| [Density-function nodes](density-function-nodes.md) | the thirty-four node types and what the rewrite installs for each | hand-kept | XII |
| [Threads](threads.md) | every thread, who makes it, what may run on it | hand-kept | I, III, IV, IX, X, XI |
| [Math and primitives](math-and-primitives.md) | the coordinate spaces, packings, shapes and random sources | hand-kept | II, IV, V, VI |
| [Level data and rules](level-data-and-rules.md) | who owns the seed, spawn, rules and border, and which file each is in | hand-kept | IV, VIII, XII |
| [Naming drift](naming-drift.md) | every 1.21-era name a reader will reach for, and what 26.2 calls it | hand-kept | I, II, XI, XII |
| [Glossary](glossary.md) | one sentence per term, and the page that owns it | hand-kept | X, XI, XII, XIII |
| [Diagram lanes](lanes.md) | every lane abbreviation and the class it means, and the nine that mean a thread, a process or a boundary instead | generated from the lane key | every part |
| [Class index](class-index.md) | every class backticked on a page, and the pages that name it | generated from the pages | — |
*Generated* means `python tools/gen_reference.py all` rewrites the file
from the decompile's declaration lines, so a version bump re-derives it
rather than re-reading it; the two indexes are rewritten by
`python tools/verify_names.py --index` and `python tools/check_lanes.py
--index`. *Hand-kept* means a part session read the classes and wrote the
rows, `tools/verify_names.py` checks every name on the page, and the
second fact-check re-reads the rows — declaration orders drift on a version
bump, and two of these pages (submit phases, density-function nodes) are
nothing but declaration order.
For agents: the whole site is also served as one file at
[/llms-full.txt](https://minecraftdocs.dev/llms-full.txt).
---
*Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes `tools/verify_names.py`.*
---
# Packets
> Generated from the **26.2** decompile by `tools/gen_reference.py`. Do not edit by hand.
Every packet the game defines, by the `PacketTypes` class that declares it. `common`, `cookie` and `ping` packets are shared by more than one protocol phase; the exact phase→packet bindings are in the `*Protocols` classes next to each `PacketTypes` class (`GameProtocols`, `ConfigurationProtocols`, `LoginProtocols`, `StatusProtocols`, `HandshakeProtocols`). See [Packets and stream codecs](../systems/networking/packets-and-stream-codecs.md).
| group | clientbound | serverbound |
|---|---:|---:|
| `common` (`CommonPacketTypes`) | 13 | 6 |
| `configuration` (`ConfigurationPacketTypes`) | 6 | 3 |
| `cookie` (`CookiePacketTypes`) | 1 | 1 |
| `game` (`GamePacketTypes`) | 127 | 61 |
| `handshake` (`HandshakePacketTypes`) | 0 | 1 |
| `login` (`LoginPacketTypes`) | 5 | 4 |
| `ping` (`PingPacketTypes`) | 1 | 1 |
| `status` (`StatusPacketTypes`) | 1 | 1 |
| **total** | **154** | **78** |
## `common` — `CommonPacketTypes` — shared across phases
| id | direction | class |
|---|---|---|
| `clear_dialog` | clientbound | `ClientboundClearDialogPacket` |
| `custom_payload` | clientbound | `ClientboundCustomPayloadPacket` |
| `custom_report_details` | clientbound | `ClientboundCustomReportDetailsPacket` |
| `disconnect` | clientbound | `ClientboundDisconnectPacket` |
| `keep_alive` | clientbound | `ClientboundKeepAlivePacket` |
| `ping` | clientbound | `ClientboundPingPacket` |
| `resource_pack_pop` | clientbound | `ClientboundResourcePackPopPacket` |
| `resource_pack_push` | clientbound | `ClientboundResourcePackPushPacket` |
| `server_links` | clientbound | `ClientboundServerLinksPacket` |
| `show_dialog` | clientbound | `ClientboundShowDialogPacket` |
| `store_cookie` | clientbound | `ClientboundStoreCookiePacket` |
| `transfer` | clientbound | `ClientboundTransferPacket` |
| `update_tags` | clientbound | `ClientboundUpdateTagsPacket` |
| `client_information` | serverbound | `ServerboundClientInformationPacket` |
| `custom_click_action` | serverbound | `ServerboundCustomClickActionPacket` |
| `custom_payload` | serverbound | `ServerboundCustomPayloadPacket` |
| `keep_alive` | serverbound | `ServerboundKeepAlivePacket` |
| `pong` | serverbound | `ServerboundPongPacket` |
| `resource_pack` | serverbound | `ServerboundResourcePackPacket` |
## `configuration` — `ConfigurationPacketTypes`
| id | direction | class |
|---|---|---|
| `code_of_conduct` | clientbound | `ClientboundCodeOfConductPacket` |
| `finish_configuration` | clientbound | `ClientboundFinishConfigurationPacket` |
| `registry_data` | clientbound | `ClientboundRegistryDataPacket` |
| `reset_chat` | clientbound | `ClientboundResetChatPacket` |
| `select_known_packs` | clientbound | `ClientboundSelectKnownPacks` |
| `update_enabled_features` | clientbound | `ClientboundUpdateEnabledFeaturesPacket` |
| `accept_code_of_conduct` | serverbound | `ServerboundAcceptCodeOfConductPacket` |
| `finish_configuration` | serverbound | `ServerboundFinishConfigurationPacket` |
| `select_known_packs` | serverbound | `ServerboundSelectKnownPacks` |
## `cookie` — `CookiePacketTypes` — shared across phases
| id | direction | class |
|---|---|---|
| `cookie_request` | clientbound | `ClientboundCookieRequestPacket` |
| `cookie_response` | serverbound | `ServerboundCookieResponsePacket` |
## `game` — `GamePacketTypes`
| id | direction | class |
|---|---|---|
| `add_entity` | clientbound | `ClientboundAddEntityPacket` |
| `animate` | clientbound | `ClientboundAnimatePacket` |
| `award_stats` | clientbound | `ClientboundAwardStatsPacket` |
| `block_changed_ack` | clientbound | `ClientboundBlockChangedAckPacket` |
| `block_destruction` | clientbound | `ClientboundBlockDestructionPacket` |
| `block_entity_data` | clientbound | `ClientboundBlockEntityDataPacket` |
| `block_event` | clientbound | `ClientboundBlockEventPacket` |
| `block_update` | clientbound | `ClientboundBlockUpdatePacket` |
| `boss_event` | clientbound | `ClientboundBossEventPacket` |
| `bundle` | clientbound | `ClientboundBundlePacket` |
| `bundle_delimiter` | clientbound | `ClientboundBundleDelimiterPacket` |
| `change_difficulty` | clientbound | `ClientboundChangeDifficultyPacket` |
| `chunk_batch_finished` | clientbound | `ClientboundChunkBatchFinishedPacket` |
| `chunk_batch_start` | clientbound | `ClientboundChunkBatchStartPacket` |
| `chunks_biomes` | clientbound | `ClientboundChunksBiomesPacket` |
| `clear_titles` | clientbound | `ClientboundClearTitlesPacket` |
| `command_suggestions` | clientbound | `ClientboundCommandSuggestionsPacket` |
| `commands` | clientbound | `ClientboundCommandsPacket` |
| `container_close` | clientbound | `ClientboundContainerClosePacket` |
| `container_set_content` | clientbound | `ClientboundContainerSetContentPacket` |
| `container_set_data` | clientbound | `ClientboundContainerSetDataPacket` |
| `container_set_slot` | clientbound | `ClientboundContainerSetSlotPacket` |
| `cooldown` | clientbound | `ClientboundCooldownPacket` |
| `custom_chat_completions` | clientbound | `ClientboundCustomChatCompletionsPacket` |
| `damage_event` | clientbound | `ClientboundDamageEventPacket` |
| `debug/block_value` | clientbound | `ClientboundDebugBlockValuePacket` |
| `debug/chunk_value` | clientbound | `ClientboundDebugChunkValuePacket` |
| `debug/entity_value` | clientbound | `ClientboundDebugEntityValuePacket` |
| `debug/event` | clientbound | `ClientboundDebugEventPacket` |
| `debug_sample` | clientbound | `ClientboundDebugSamplePacket` |
| `delete_chat` | clientbound | `ClientboundDeleteChatPacket` |
| `disguised_chat` | clientbound | `ClientboundDisguisedChatPacket` |
| `entity_event` | clientbound | `ClientboundEntityEventPacket` |
| `entity_position_sync` | clientbound | `ClientboundEntityPositionSyncPacket` |
| `explode` | clientbound | `ClientboundExplodePacket` |
| `forget_level_chunk` | clientbound | `ClientboundForgetLevelChunkPacket` |
| `game_event` | clientbound | `ClientboundGameEventPacket` |
| `game_rule_values` | clientbound | `ClientboundGameRuleValuesPacket` |
| `game_test_highlight_pos` | clientbound | `ClientboundGameTestHighlightPosPacket` |
| `hurt_animation` | clientbound | `ClientboundHurtAnimationPacket` |
| `initialize_border` | clientbound | `ClientboundInitializeBorderPacket` |
| `level_chunk_with_light` | clientbound | `ClientboundLevelChunkWithLightPacket` |
| `level_event` | clientbound | `ClientboundLevelEventPacket` |
| `level_particles` | clientbound | `ClientboundLevelParticlesPacket` |
| `light_update` | clientbound | `ClientboundLightUpdatePacket` |
| `login` | clientbound | `ClientboundLoginPacket` |
| `low_disk_space_warning` | clientbound | `ClientboundLowDiskSpaceWarningPacket` |
| `map_item_data` | clientbound | `ClientboundMapItemDataPacket` |
| `merchant_offers` | clientbound | `ClientboundMerchantOffersPacket` |
| `mount_screen_open` | clientbound | `ClientboundMountScreenOpenPacket` |
| `move_entity_pos` | clientbound | `ClientboundMoveEntityPacket.Pos` |
| `move_entity_pos_rot` | clientbound | `ClientboundMoveEntityPacket.PosRot` |
| `move_entity_rot` | clientbound | `ClientboundMoveEntityPacket.Rot` |
| `move_minecart_along_track` | clientbound | `ClientboundMoveMinecartPacket` |
| `move_vehicle` | clientbound | `ClientboundMoveVehiclePacket` |
| `open_book` | clientbound | `ClientboundOpenBookPacket` |
| `open_screen` | clientbound | `ClientboundOpenScreenPacket` |
| `open_sign_editor` | clientbound | `ClientboundOpenSignEditorPacket` |
| `place_ghost_recipe` | clientbound | `ClientboundPlaceGhostRecipePacket` |
| `player_abilities` | clientbound | `ClientboundPlayerAbilitiesPacket` |
| `player_chat` | clientbound | `ClientboundPlayerChatPacket` |
| `player_combat_end` | clientbound | `ClientboundPlayerCombatEndPacket` |
| `player_combat_enter` | clientbound | `ClientboundPlayerCombatEnterPacket` |
| `player_combat_kill` | clientbound | `ClientboundPlayerCombatKillPacket` |
| `player_info_remove` | clientbound | `ClientboundPlayerInfoRemovePacket` |
| `player_info_update` | clientbound | `ClientboundPlayerInfoUpdatePacket` |
| `player_look_at` | clientbound | `ClientboundPlayerLookAtPacket` |
| `player_position` | clientbound | `ClientboundPlayerPositionPacket` |
| `player_rotation` | clientbound | `ClientboundPlayerRotationPacket` |
| `projectile_power` | clientbound | `ClientboundProjectilePowerPacket` |
| `recipe_book_add` | clientbound | `ClientboundRecipeBookAddPacket` |
| `recipe_book_remove` | clientbound | `ClientboundRecipeBookRemovePacket` |
| `recipe_book_settings` | clientbound | `ClientboundRecipeBookSettingsPacket` |
| `remove_entities` | clientbound | `ClientboundRemoveEntitiesPacket` |
| `remove_mob_effect` | clientbound | `ClientboundRemoveMobEffectPacket` |
| `reset_score` | clientbound | `ClientboundResetScorePacket` |
| `respawn` | clientbound | `ClientboundRespawnPacket` |
| `rotate_head` | clientbound | `ClientboundRotateHeadPacket` |
| `section_blocks_update` | clientbound | `ClientboundSectionBlocksUpdatePacket` |
| `select_advancements_tab` | clientbound | `ClientboundSelectAdvancementsTabPacket` |
| `server_data` | clientbound | `ClientboundServerDataPacket` |
| `set_action_bar_text` | clientbound | `ClientboundSetActionBarTextPacket` |
| `set_border_center` | clientbound | `ClientboundSetBorderCenterPacket` |
| `set_border_lerp_size` | clientbound | `ClientboundSetBorderLerpSizePacket` |
| `set_border_size` | clientbound | `ClientboundSetBorderSizePacket` |
| `set_border_warning_delay` | clientbound | `ClientboundSetBorderWarningDelayPacket` |
| `set_border_warning_distance` | clientbound | `ClientboundSetBorderWarningDistancePacket` |
| `set_camera` | clientbound | `ClientboundSetCameraPacket` |
| `set_chunk_cache_center` | clientbound | `ClientboundSetChunkCacheCenterPacket` |
| `set_chunk_cache_radius` | clientbound | `ClientboundSetChunkCacheRadiusPacket` |
| `set_cursor_item` | clientbound | `ClientboundSetCursorItemPacket` |
| `set_default_spawn_position` | clientbound | `ClientboundSetDefaultSpawnPositionPacket` |
| `set_display_objective` | clientbound | `ClientboundSetDisplayObjectivePacket` |
| `set_entity_data` | clientbound | `ClientboundSetEntityDataPacket` |
| `set_entity_link` | clientbound | `ClientboundSetEntityLinkPacket` |
| `set_entity_motion` | clientbound | `ClientboundSetEntityMotionPacket` |
| `set_equipment` | clientbound | `ClientboundSetEquipmentPacket` |
| `set_experience` | clientbound | `ClientboundSetExperiencePacket` |
| `set_health` | clientbound | `ClientboundSetHealthPacket` |
| `set_held_slot` | clientbound | `ClientboundSetHeldSlotPacket` |
| `set_objective` | clientbound | `ClientboundSetObjectivePacket` |
| `set_passengers` | clientbound | `ClientboundSetPassengersPacket` |
| `set_player_inventory` | clientbound | `ClientboundSetPlayerInventoryPacket` |
| `set_player_team` | clientbound | `ClientboundSetPlayerTeamPacket` |
| `set_score` | clientbound | `ClientboundSetScorePacket` |
| `set_simulation_distance` | clientbound | `ClientboundSetSimulationDistancePacket` |
| `set_subtitle_text` | clientbound | `ClientboundSetSubtitleTextPacket` |
| `set_time` | clientbound | `ClientboundSetTimePacket` |
| `set_title_text` | clientbound | `ClientboundSetTitleTextPacket` |
| `set_titles_animation` | clientbound | `ClientboundSetTitlesAnimationPacket` |
| `sound` | clientbound | `ClientboundSoundPacket` |
| `sound_entity` | clientbound | `ClientboundSoundEntityPacket` |
| `start_configuration` | clientbound | `ClientboundStartConfigurationPacket` |
| `stop_sound` | clientbound | `ClientboundStopSoundPacket` |
| `system_chat` | clientbound | `ClientboundSystemChatPacket` |
| `tab_list` | clientbound | `ClientboundTabListPacket` |
| `tag_query` | clientbound | `ClientboundTagQueryPacket` |
| `take_item_entity` | clientbound | `ClientboundTakeItemEntityPacket` |
| `teleport_entity` | clientbound | `ClientboundTeleportEntityPacket` |
| `test_instance_block_status` | clientbound | `ClientboundTestInstanceBlockStatus` |
| `ticking_state` | clientbound | `ClientboundTickingStatePacket` |
| `ticking_step` | clientbound | `ClientboundTickingStepPacket` |
| `update_advancements` | clientbound | `ClientboundUpdateAdvancementsPacket` |
| `update_attributes` | clientbound | `ClientboundUpdateAttributesPacket` |
| `update_mob_effect` | clientbound | `ClientboundUpdateMobEffectPacket` |
| `update_recipes` | clientbound | `ClientboundUpdateRecipesPacket` |
| `waypoint` | clientbound | `ClientboundTrackedWaypointPacket` |
| `accept_teleportation` | serverbound | `ServerboundAcceptTeleportationPacket` |
| `attack` | serverbound | `ServerboundAttackPacket` |
| `block_entity_tag_query` | serverbound | `ServerboundBlockEntityTagQueryPacket` |
| `bundle_item_selected` | serverbound | `ServerboundSelectBundleItemPacket` |
| `change_difficulty` | serverbound | `ServerboundChangeDifficultyPacket` |
| `change_game_mode` | serverbound | `ServerboundChangeGameModePacket` |
| `chat` | serverbound | `ServerboundChatPacket` |
| `chat_ack` | serverbound | `ServerboundChatAckPacket` |
| `chat_command` | serverbound | `ServerboundChatCommandPacket` |
| `chat_command_signed` | serverbound | `ServerboundChatCommandSignedPacket` |
| `chat_session_update` | serverbound | `ServerboundChatSessionUpdatePacket` |
| `chunk_batch_received` | serverbound | `ServerboundChunkBatchReceivedPacket` |
| `client_command` | serverbound | `ServerboundClientCommandPacket` |
| `client_tick_end` | serverbound | `ServerboundClientTickEndPacket` |
| `command_suggestion` | serverbound | `ServerboundCommandSuggestionPacket` |
| `configuration_acknowledged` | serverbound | `ServerboundConfigurationAcknowledgedPacket` |
| `container_button_click` | serverbound | `ServerboundContainerButtonClickPacket` |
| `container_click` | serverbound | `ServerboundContainerClickPacket` |
| `container_close` | serverbound | `ServerboundContainerClosePacket` |
| `container_slot_state_changed` | serverbound | `ServerboundContainerSlotStateChangedPacket` |
| `debug_subscription_request` | serverbound | `ServerboundDebugSubscriptionRequestPacket` |
| `edit_book` | serverbound | `ServerboundEditBookPacket` |
| `entity_tag_query` | serverbound | `ServerboundEntityTagQueryPacket` |
| `interact` | serverbound | `ServerboundInteractPacket` |
| `jigsaw_generate` | serverbound | `ServerboundJigsawGeneratePacket` |
| `lock_difficulty` | serverbound | `ServerboundLockDifficultyPacket` |
| `move_player_pos` | serverbound | `ServerboundMovePlayerPacket.Pos` |
| `move_player_pos_rot` | serverbound | `ServerboundMovePlayerPacket.PosRot` |
| `move_player_rot` | serverbound | `ServerboundMovePlayerPacket.Rot` |
| `move_player_status_only` | serverbound | `ServerboundMovePlayerPacket.StatusOnly` |
| `move_vehicle` | serverbound | `ServerboundMoveVehiclePacket` |
| `paddle_boat` | serverbound | `ServerboundPaddleBoatPacket` |
| `pick_item_from_block` | serverbound | `ServerboundPickItemFromBlockPacket` |
| `pick_item_from_entity` | serverbound | `ServerboundPickItemFromEntityPacket` |
| `place_recipe` | serverbound | `ServerboundPlaceRecipePacket` |
| `player_abilities` | serverbound | `ServerboundPlayerAbilitiesPacket` |
| `player_action` | serverbound | `ServerboundPlayerActionPacket` |
| `player_command` | serverbound | `ServerboundPlayerCommandPacket` |
| `player_input` | serverbound | `ServerboundPlayerInputPacket` |
| `player_loaded` | serverbound | `ServerboundPlayerLoadedPacket` |
| `recipe_book_change_settings` | serverbound | `ServerboundRecipeBookChangeSettingsPacket` |
| `recipe_book_seen_recipe` | serverbound | `ServerboundRecipeBookSeenRecipePacket` |
| `rename_item` | serverbound | `ServerboundRenameItemPacket` |
| `seen_advancements` | serverbound | `ServerboundSeenAdvancementsPacket` |
| `select_trade` | serverbound | `ServerboundSelectTradePacket` |
| `set_beacon` | serverbound | `ServerboundSetBeaconPacket` |
| `set_carried_item` | serverbound | `ServerboundSetCarriedItemPacket` |
| `set_command_block` | serverbound | `ServerboundSetCommandBlockPacket` |
| `set_command_minecart` | serverbound | `ServerboundSetCommandMinecartPacket` |
| `set_creative_mode_slot` | serverbound | `ServerboundSetCreativeModeSlotPacket` |
| `set_game_rule` | serverbound | `ServerboundSetGameRulePacket` |
| `set_jigsaw_block` | serverbound | `ServerboundSetJigsawBlockPacket` |
| `set_structure_block` | serverbound | `ServerboundSetStructureBlockPacket` |
| `set_test_block` | serverbound | `ServerboundSetTestBlockPacket` |
| `sign_update` | serverbound | `ServerboundSignUpdatePacket` |
| `spectator_action` | serverbound | `ServerboundSpectatorActionPacket` |
| `swing` | serverbound | `ServerboundSwingPacket` |
| `teleport_to_entity` | serverbound | `ServerboundTeleportToEntityPacket` |
| `test_instance_block_action` | serverbound | `ServerboundTestInstanceBlockActionPacket` |
| `use_item` | serverbound | `ServerboundUseItemPacket` |
| `use_item_on` | serverbound | `ServerboundUseItemOnPacket` |
## `handshake` — `HandshakePacketTypes`
| id | direction | class |
|---|---|---|
| `intention` | serverbound | `ClientIntentionPacket` |
## `login` — `LoginPacketTypes`
| id | direction | class |
|---|---|---|
| `custom_query` | clientbound | `ClientboundCustomQueryPacket` |
| `hello` | clientbound | `ClientboundHelloPacket` |
| `login_compression` | clientbound | `ClientboundLoginCompressionPacket` |
| `login_disconnect` | clientbound | `ClientboundLoginDisconnectPacket` |
| `login_finished` | clientbound | `ClientboundLoginFinishedPacket` |
| `custom_query_answer` | serverbound | `ServerboundCustomQueryAnswerPacket` |
| `hello` | serverbound | `ServerboundHelloPacket` |
| `key` | serverbound | `ServerboundKeyPacket` |
| `login_acknowledged` | serverbound | `ServerboundLoginAcknowledgedPacket` |
## `ping` — `PingPacketTypes` — shared across phases
| id | direction | class |
|---|---|---|
| `pong_response` | clientbound | `ClientboundPongResponsePacket` |
| `ping_request` | serverbound | `ServerboundPingRequestPacket` |
## `status` — `StatusPacketTypes`
| id | direction | class |
|---|---|---|
| `status_response` | clientbound | `ClientboundStatusResponsePacket` |
| `status_request` | serverbound | `ServerboundStatusRequestPacket` |
---
# Registries
> Generated from the **26.2** decompile by `tools/gen_reference.py`. Do not edit by hand.
Every registry key declared in `Registries`. **Built-in** registries are populated from static code in `BuiltInRegistries` at class-load time and frozen; **data-pack** registries are loaded per world by `RegistryDataLoader` from JSON (`WORLDGEN_REGISTRIES`, or `DIMENSION_REGISTRIES` for level stems); **synced** ones are sent to the client in the configuration phase (`SYNCHRONIZED_REGISTRIES`). A key that is none of these is a registry *type* the game reasons about without a global instance (e.g. per-world or client-side). See [Identifiers and registries](../systems/foundations/identifiers-and-registries.md).
153 keys · 95 built-in · 47 data-pack · 29 synced
| key | element type | kind | synced |
|---|---|---|---|
| `activity` (`Registries.ACTIVITY`) | `Activity` | built-in | |
| `advancement` (`Registries.ADVANCEMENT`) | `Advancement` | — | |
| `attribute` (`Registries.ATTRIBUTE`) | `Attribute` | built-in | |
| `attribute_type` (`Registries.ATTRIBUTE_TYPE`) | `AttributeType<…>` | built-in | |
| `banner_pattern` (`Registries.BANNER_PATTERN`) | `BannerPattern` | data-pack | yes |
| `block` (`Registries.BLOCK`) | `Block` | built-in | |
| `block_entity_type` (`Registries.BLOCK_ENTITY_TYPE`) | `BlockEntityType<…>` | built-in | |
| `block_predicate_type` (`Registries.BLOCK_PREDICATE_TYPE`) | `BlockPredicateType<…>` | built-in | |
| `block_type` (`Registries.BLOCK_TYPE`) | `MapCodec<…>` | built-in | |
| `cat_sound_variant` (`Registries.CAT_SOUND_VARIANT`) | `CatSoundVariant` | data-pack | yes |
| `cat_variant` (`Registries.CAT_VARIANT`) | `CatVariant` | data-pack | yes |
| `chat_type` (`Registries.CHAT_TYPE`) | `ChatType` | data-pack | yes |
| `chicken_sound_variant` (`Registries.CHICKEN_SOUND_VARIANT`) | `ChickenSoundVariant` | data-pack | yes |
| `chicken_variant` (`Registries.CHICKEN_VARIANT`) | `ChickenVariant` | data-pack | yes |
| `chunk_status` (`Registries.CHUNK_STATUS`) | `ChunkStatus` | built-in | |
| `clock_time_marker` (`ClockTimeMarkers.ROOT_ID`) | `ClockTimeMarker` | — | |
| `command_argument_type` (`Registries.COMMAND_ARGUMENT_TYPE`) | `ArgumentTypeInfo<…>` | built-in | |
| `consume_effect_type` (`Registries.CONSUME_EFFECT_TYPE`) | `ConsumeEffect.Type<…>` | built-in | |
| `cow_sound_variant` (`Registries.COW_SOUND_VARIANT`) | `CowSoundVariant` | data-pack | yes |
| `cow_variant` (`Registries.COW_VARIANT`) | `CowVariant` | data-pack | yes |
| `creative_mode_tab` (`Registries.CREATIVE_MODE_TAB`) | `CreativeModeTab` | built-in | |
| `custom_stat` (`Registries.CUSTOM_STAT`) | `Identifier` | built-in | |
| `damage_type` (`Registries.DAMAGE_TYPE`) | `DamageType` | data-pack | yes |
| `data_component_predicate_type` (`Registries.DATA_COMPONENT_PREDICATE_TYPE`) | `DataComponentPredicate.Type<…>` | built-in | |
| `data_component_type` (`Registries.DATA_COMPONENT_TYPE`) | `DataComponentType<…>` | built-in | |
| `debug_subscription` (`Registries.DEBUG_SUBSCRIPTION`) | `DebugSubscription<…>` | built-in | |
| `decorated_pot_pattern` (`Registries.DECORATED_POT_PATTERN`) | `DecoratedPotPattern` | built-in | |
| `dialog` (`Registries.DIALOG`) | `Dialog` | data-pack | yes |
| `dialog_action_type` (`Registries.DIALOG_ACTION_TYPE`) | `MapCodec<…>` | built-in | |
| `dialog_body_type` (`Registries.DIALOG_BODY_TYPE`) | `MapCodec<…>` | built-in | |
| `dialog_type` (`Registries.DIALOG_TYPE`) | `MapCodec<…>` | built-in | |
| `dimension` (`Registries.DIMENSION`) | `Level` | — | |
| `dimension` (`Registries.LEVEL_STEM`) | `LevelStem` | data-pack (dimension) | |
| `dimension_type` (`Registries.DIMENSION_TYPE`) | `DimensionType` | data-pack | yes |
| `enchantment` (`Registries.ENCHANTMENT`) | `Enchantment` | data-pack | yes |
| `enchantment_effect_component_type` (`Registries.ENCHANTMENT_EFFECT_COMPONENT_TYPE`) | `DataComponentType<…>` | built-in | |
| `enchantment_entity_effect_type` (`Registries.ENCHANTMENT_ENTITY_EFFECT_TYPE`) | `MapCodec<…>` | built-in | |
| `enchantment_level_based_value_type` (`Registries.ENCHANTMENT_LEVEL_BASED_VALUE_TYPE`) | `MapCodec<…>` | built-in | |
| `enchantment_location_based_effect_type` (`Registries.ENCHANTMENT_LOCATION_BASED_EFFECT_TYPE`) | `MapCodec<…>` | built-in | |
| `enchantment_provider` (`Registries.ENCHANTMENT_PROVIDER`) | `EnchantmentProvider` | data-pack | |
| `enchantment_provider_type` (`Registries.ENCHANTMENT_PROVIDER_TYPE`) | `MapCodec<…>` | built-in | |
| `enchantment_value_effect_type` (`Registries.ENCHANTMENT_VALUE_EFFECT_TYPE`) | `MapCodec<…>` | built-in | |
| `entity_sub_predicate_type` (`Registries.ENTITY_SUB_PREDICATE_TYPE`) | `Codec<…>` | built-in | |
| `entity_type` (`Registries.ENTITY_TYPE`) | `EntityType<…>` | built-in | |
| `environment_attribute` (`Registries.ENVIRONMENT_ATTRIBUTE`) | `EnvironmentAttribute<…>` | built-in | |
| `equipment_asset` (`EquipmentAssets.ROOT_ID`) | `EquipmentAsset` | — | |
| `float_provider_type` (`Registries.FLOAT_PROVIDER_TYPE`) | `MapCodec<…>` | built-in | |
| `fluid` (`Registries.FLUID`) | `Fluid` | built-in | |
| `frog_variant` (`Registries.FROG_VARIANT`) | `FrogVariant` | data-pack | yes |
| `function` (`ServerFunctionLibrary.TYPE_KEY`) | `CommandFunction<…>` | — | |
| `game_event` (`Registries.GAME_EVENT`) | `GameEvent` | built-in | |
| `game_rule` (`Registries.GAME_RULE`) | `GameRule<…>` | built-in | |
| `height_provider_type` (`Registries.HEIGHT_PROVIDER_TYPE`) | `HeightProviderType<…>` | built-in | |
| `incoming_rpc_methods` (`Registries.INCOMING_RPC_METHOD`) | `IncomingRpcMethod<…>` | built-in | |
| `input_control_type` (`Registries.INPUT_CONTROL_TYPE`) | `MapCodec<…>` | built-in | |
| `instrument` (`Registries.INSTRUMENT`) | `Instrument` | data-pack | yes |
| `int_provider_type` (`Registries.INT_PROVIDER_TYPE`) | `MapCodec<…>` | built-in | |
| `item` (`Registries.ITEM`) | `Item` | built-in | |
| `item_modifier` (`Registries.ITEM_MODIFIER`) | `LootItemFunction` | — | |
| `jukebox_song` (`Registries.JUKEBOX_SONG`) | `JukeboxSong` | data-pack | yes |
| `loot_condition_type` (`Registries.LOOT_CONDITION_TYPE`) | `MapCodec<…>` | built-in | |
| `loot_function_type` (`Registries.LOOT_FUNCTION_TYPE`) | `MapCodec<…>` | built-in | |
| `loot_nbt_provider_type` (`Registries.LOOT_NBT_PROVIDER_TYPE`) | `MapCodec<…>` | built-in | |
| `loot_number_provider_type` (`Registries.LOOT_NUMBER_PROVIDER_TYPE`) | `MapCodec<…>` | built-in | |
| `loot_pool_entry_type` (`Registries.LOOT_POOL_ENTRY_TYPE`) | `MapCodec<…>` | built-in | |
| `loot_score_provider_type` (`Registries.LOOT_SCORE_PROVIDER_TYPE`) | `MapCodec<…>` | built-in | |
| `loot_table` (`Registries.LOOT_TABLE`) | `LootTable` | — | |
| `map_decoration_type` (`Registries.MAP_DECORATION_TYPE`) | `MapDecorationType` | built-in | |
| `memory_module_type` (`Registries.MEMORY_MODULE_TYPE`) | `MemoryModuleType<…>` | built-in | |
| `menu` (`Registries.MENU`) | `MenuType<…>` | built-in | |
| `mob_effect` (`Registries.MOB_EFFECT`) | `MobEffect` | built-in | |
| `number_format_type` (`Registries.NUMBER_FORMAT_TYPE`) | `NumberFormatType<…>` | built-in | |
| `outgoing_rpc_methods` (`Registries.OUTGOING_RPC_METHOD`) | `OutgoingRpcMethod<…>` | built-in | |
| `painting_variant` (`Registries.PAINTING_VARIANT`) | `PaintingVariant` | data-pack | yes |
| `particle_type` (`Registries.PARTICLE_TYPE`) | `ParticleType<…>` | built-in | |
| `permission_check_type` (`Registries.PERMISSION_CHECK_TYPE`) | `MapCodec<…>` | built-in | |
| `permission_type` (`Registries.PERMISSION_TYPE`) | `MapCodec<…>` | built-in | |
| `pig_sound_variant` (`Registries.PIG_SOUND_VARIANT`) | `PigSoundVariant` | data-pack | yes |
| `pig_variant` (`Registries.PIG_VARIANT`) | `PigVariant` | data-pack | yes |
| `point_of_interest_type` (`Registries.POINT_OF_INTEREST_TYPE`) | `PoiType` | built-in | |
| `pos_rule_test` (`Registries.POS_RULE_TEST`) | `PosRuleTestType<…>` | built-in | |
| `position_source_type` (`Registries.POSITION_SOURCE_TYPE`) | `PositionSourceType<…>` | built-in | |
| `potion` (`Registries.POTION`) | `Potion` | built-in | |
| `predicate` (`Registries.PREDICATE`) | `LootItemCondition` | — | |
| `recipe` (`Registries.RECIPE`) | `Recipe<…>` | — | |
| `recipe_book_category` (`Registries.RECIPE_BOOK_CATEGORY`) | `RecipeBookCategory` | built-in | |
| `recipe_display` (`Registries.RECIPE_DISPLAY`) | `RecipeDisplay.Type<…>` | built-in | |
| `recipe_property_set` (`RecipePropertySet.TYPE_KEY`) | `RecipePropertySet` | — | |
| `recipe_serializer` (`Registries.RECIPE_SERIALIZER`) | `RecipeSerializer<…>` | built-in | |
| `recipe_type` (`Registries.RECIPE_TYPE`) | `RecipeType<…>` | built-in | |
| `rule_block_entity_modifier` (`Registries.RULE_BLOCK_ENTITY_MODIFIER`) | `RuleBlockEntityModifierType<…>` | built-in | |
| `rule_test` (`Registries.RULE_TEST`) | `RuleTestType<…>` | built-in | |
| `sensor_type` (`Registries.SENSOR_TYPE`) | `SensorType<…>` | built-in | |
| `slot_display` (`Registries.SLOT_DISPLAY`) | `SlotDisplay.Type<…>` | built-in | |
| `slot_source_type` (`Registries.SLOT_SOURCE_TYPE`) | `MapCodec<…>` | built-in | |
| `sound_event` (`Registries.SOUND_EVENT`) | `SoundEvent` | built-in | |
| `spawn_condition_type` (`Registries.SPAWN_CONDITION_TYPE`) | `MapCodec<…>` | built-in | |
| `stat_type` (`Registries.STAT_TYPE`) | `StatType<…>` | built-in | |
| `sulfur_cube_archetype` (`Registries.SULFUR_CUBE_ARCHETYPE`) | `SulfurCubeArchetype` | data-pack | yes |
| `test_environment` (`Registries.TEST_ENVIRONMENT`) | `TestEnvironmentDefinition<…>` | data-pack | yes |
| `test_environment_definition_type` (`Registries.TEST_ENVIRONMENT_DEFINITION_TYPE`) | `MapCodec<…>` | built-in | |
| `test_function` (`Registries.TEST_FUNCTION`) | `Consumer<…>` | built-in | |
| `test_instance` (`Registries.TEST_INSTANCE`) | `GameTestInstance` | data-pack | yes |
| `test_instance_type` (`Registries.TEST_INSTANCE_TYPE`) | `MapCodec<…>` | built-in | |
| `ticket_type` (`Registries.TICKET_TYPE`) | `TicketType` | built-in | |
| `timeline` (`Registries.TIMELINE`) | `Timeline` | data-pack | yes |
| `trade_set` (`Registries.TRADE_SET`) | `TradeSet` | data-pack | |
| `trial_spawner` (`Registries.TRIAL_SPAWNER_CONFIG`) | `TrialSpawnerConfig` | data-pack | |
| `trigger_type` (`Registries.TRIGGER_TYPE`) | `CriterionTrigger<…>` | built-in | |
| `trim_material` (`Registries.TRIM_MATERIAL`) | `TrimMaterial` | data-pack | yes |
| `trim_pattern` (`Registries.TRIM_PATTERN`) | `TrimPattern` | data-pack | yes |
| `villager_profession` (`Registries.VILLAGER_PROFESSION`) | `VillagerProfession` | built-in | |
| `villager_trade` (`Registries.VILLAGER_TRADE`) | `VillagerTrade` | data-pack | |
| `villager_type` (`Registries.VILLAGER_TYPE`) | `VillagerType` | built-in | |
| `waypoint_style_asset` (`WaypointStyleAssets.ROOT_ID`) | `WaypointStyleAsset` | — | |
| `wolf_sound_variant` (`Registries.WOLF_SOUND_VARIANT`) | `WolfSoundVariant` | data-pack | yes |
| `wolf_variant` (`Registries.WOLF_VARIANT`) | `WolfVariant` | data-pack | yes |
| `world_clock` (`Registries.WORLD_CLOCK`) | `WorldClock` | data-pack | yes |
| `worldgen/biome` (`Registries.BIOME`) | `Biome` | data-pack | yes |
| `worldgen/biome_source` (`Registries.BIOME_SOURCE`) | `MapCodec<…>` | built-in | |
| `worldgen/block_state_provider_type` (`Registries.BLOCK_STATE_PROVIDER_TYPE`) | `BlockStateProviderType<…>` | built-in | |
| `worldgen/carver` (`Registries.CARVER`) | `WorldCarver<…>` | built-in | |
| `worldgen/chunk_generator` (`Registries.CHUNK_GENERATOR`) | `MapCodec<…>` | built-in | |
| `worldgen/configured_carver` (`Registries.CONFIGURED_CARVER`) | `ConfiguredWorldCarver<…>` | data-pack | |
| `worldgen/configured_feature` (`Registries.CONFIGURED_FEATURE`) | `ConfiguredFeature<…>` | data-pack | |
| `worldgen/density_function` (`Registries.DENSITY_FUNCTION`) | `DensityFunction` | data-pack | |
| `worldgen/density_function_type` (`Registries.DENSITY_FUNCTION_TYPE`) | `MapCodec<…>` | built-in | |
| `worldgen/feature` (`Registries.FEATURE`) | `Feature<…>` | built-in | |
| `worldgen/feature_size_type` (`Registries.FEATURE_SIZE_TYPE`) | `FeatureSizeType<…>` | built-in | |
| `worldgen/flat_level_generator_preset` (`Registries.FLAT_LEVEL_GENERATOR_PRESET`) | `FlatLevelGeneratorPreset` | data-pack | |
| `worldgen/foliage_placer_type` (`Registries.FOLIAGE_PLACER_TYPE`) | `FoliagePlacerType<…>` | built-in | |
| `worldgen/material_condition` (`Registries.MATERIAL_CONDITION`) | `MapCodec<…>` | built-in | |
| `worldgen/material_rule` (`Registries.MATERIAL_RULE`) | `MapCodec<…>` | built-in | |
| `worldgen/multi_noise_biome_source_parameter_list` (`Registries.MULTI_NOISE_BIOME_SOURCE_PARAMETER_LIST`) | `MultiNoiseBiomeSourceParameterList` | data-pack | |
| `worldgen/noise` (`Registries.NOISE`) | `NormalNoise.NoiseParameters` | data-pack | |
| `worldgen/noise_settings` (`Registries.NOISE_SETTINGS`) | `NoiseGeneratorSettings` | data-pack | |
| `worldgen/placed_feature` (`Registries.PLACED_FEATURE`) | `PlacedFeature` | data-pack | |
| `worldgen/placement_modifier_type` (`Registries.PLACEMENT_MODIFIER_TYPE`) | `PlacementModifierType<…>` | built-in | |
| `worldgen/pool_alias_binding` (`Registries.POOL_ALIAS_BINDING`) | `MapCodec<…>` | built-in | |
| `worldgen/processor_list` (`Registries.PROCESSOR_LIST`) | `StructureProcessorList` | data-pack | |
| `worldgen/root_placer_type` (`Registries.ROOT_PLACER_TYPE`) | `RootPlacerType<…>` | built-in | |
| `worldgen/structure` (`Registries.STRUCTURE`) | `Structure` | data-pack | |
| `worldgen/structure_piece` (`Registries.STRUCTURE_PIECE`) | `StructurePieceType` | built-in | |
| `worldgen/structure_placement` (`Registries.STRUCTURE_PLACEMENT`) | `StructurePlacementType<…>` | built-in | |
| `worldgen/structure_pool_element` (`Registries.STRUCTURE_POOL_ELEMENT`) | `StructurePoolElementType<…>` | built-in | |
| `worldgen/structure_processor` (`Registries.STRUCTURE_PROCESSOR`) | `MapCodec<…>` | built-in | |
| `worldgen/structure_set` (`Registries.STRUCTURE_SET`) | `StructureSet` | data-pack | |
| `worldgen/structure_type` (`Registries.STRUCTURE_TYPE`) | `StructureType<…>` | built-in | |
| `worldgen/template_pool` (`Registries.TEMPLATE_POOL`) | `StructureTemplatePool` | data-pack | |
| `worldgen/tree_decorator_type` (`Registries.TREE_DECORATOR_TYPE`) | `TreeDecoratorType<…>` | built-in | |
| `worldgen/trunk_placer_type` (`Registries.TRUNK_PLACER_TYPE`) | `TrunkPlacerType<…>` | built-in | |
| `worldgen/world_preset` (`Registries.WORLD_PRESET`) | `WorldPreset` | data-pack | |
| `zombie_nautilus_variant` (`Registries.ZOMBIE_NAUTILUS_VARIANT`) | `ZombieNautilusVariant` | data-pack | yes |
---
# Data components
> Generated from the **26.2** decompile by `tools/gen_reference.py`. Do not edit by hand.
Every `DataComponentType` registered in `DataComponents`. *Persistent* components have a `Codec` and are written to disk; *synced* ones have a `StreamCodec` and are sent to the client; *cache-encoded* ones use the shared `EncoderCache`. A type that is neither persistent nor synced is transient and lives only in memory. See [Data components](../systems/foundations/data-components.md).
111 components
| id | value type | persistent | synced |
|---|---|---|---|
| `custom_data` (`DataComponents.CUSTOM_DATA`) | `CustomData` | yes | |
| `max_stack_size` (`DataComponents.MAX_STACK_SIZE`) | `Integer` | yes | yes |
| `max_damage` (`DataComponents.MAX_DAMAGE`) | `Integer` | yes | yes |
| `damage` (`DataComponents.DAMAGE`) | `Integer` | yes | yes |
| `unbreakable` (`DataComponents.UNBREAKABLE`) | `Unit` | yes | yes |
| `use_effects` (`DataComponents.USE_EFFECTS`) | `UseEffects` | yes | yes |
| `custom_name` (`DataComponents.CUSTOM_NAME`) | `Component` | yes (cached) | yes |
| `minimum_attack_charge` (`DataComponents.MINIMUM_ATTACK_CHARGE`) | `Float` | yes | yes |
| `damage_type` (`DataComponents.DAMAGE_TYPE`) | `Holder<…>` | yes | yes |
| `item_name` (`DataComponents.ITEM_NAME`) | `Component` | yes (cached) | yes |
| `item_model` (`DataComponents.ITEM_MODEL`) | `Identifier` | yes (cached) | yes |
| `lore` (`DataComponents.LORE`) | `ItemLore` | yes (cached) | yes |
| `rarity` (`DataComponents.RARITY`) | `Rarity` | yes | yes |
| `enchantments` (`DataComponents.ENCHANTMENTS`) | `ItemEnchantments` | yes (cached) | yes |
| `can_place_on` (`DataComponents.CAN_PLACE_ON`) | `AdventureModePredicate` | yes (cached) | yes |
| `can_break` (`DataComponents.CAN_BREAK`) | `AdventureModePredicate` | yes (cached) | yes |
| `attribute_modifiers` (`DataComponents.ATTRIBUTE_MODIFIERS`) | `ItemAttributeModifiers` | yes (cached) | yes |
| `custom_model_data` (`DataComponents.CUSTOM_MODEL_DATA`) | `CustomModelData` | yes | yes |
| `tooltip_display` (`DataComponents.TOOLTIP_DISPLAY`) | `TooltipDisplay` | yes (cached) | yes |
| `repair_cost` (`DataComponents.REPAIR_COST`) | `Integer` | yes | yes |
| `creative_slot_lock` (`DataComponents.CREATIVE_SLOT_LOCK`) | `Unit` | | yes |
| `enchantment_glint_override` (`DataComponents.ENCHANTMENT_GLINT_OVERRIDE`) | `Boolean` | yes | yes |
| `intangible_projectile` (`DataComponents.INTANGIBLE_PROJECTILE`) | `Unit` | yes | |
| `food` (`DataComponents.FOOD`) | `FoodProperties` | yes (cached) | yes |
| `consumable` (`DataComponents.CONSUMABLE`) | `Consumable` | yes (cached) | yes |
| `use_remainder` (`DataComponents.USE_REMAINDER`) | `UseRemainder` | yes (cached) | yes |
| `use_cooldown` (`DataComponents.USE_COOLDOWN`) | `UseCooldown` | yes (cached) | yes |
| `damage_resistant` (`DataComponents.DAMAGE_RESISTANT`) | `DamageResistant` | yes (cached) | yes |
| `tool` (`DataComponents.TOOL`) | `Tool` | yes (cached) | yes |
| `weapon` (`DataComponents.WEAPON`) | `Weapon` | yes (cached) | yes |
| `attack_range` (`DataComponents.ATTACK_RANGE`) | `AttackRange` | yes (cached) | yes |
| `enchantable` (`DataComponents.ENCHANTABLE`) | `Enchantable` | yes (cached) | yes |
| `equippable` (`DataComponents.EQUIPPABLE`) | `Equippable` | yes (cached) | yes |
| `repairable` (`DataComponents.REPAIRABLE`) | `Repairable` | yes (cached) | yes |
| `glider` (`DataComponents.GLIDER`) | `Unit` | yes | yes |
| `tooltip_style` (`DataComponents.TOOLTIP_STYLE`) | `Identifier` | yes (cached) | yes |
| `death_protection` (`DataComponents.DEATH_PROTECTION`) | `DeathProtection` | yes (cached) | yes |
| `blocks_attacks` (`DataComponents.BLOCKS_ATTACKS`) | `BlocksAttacks` | yes (cached) | yes |
| `piercing_weapon` (`DataComponents.PIERCING_WEAPON`) | `PiercingWeapon` | yes (cached) | yes |
| `kinetic_weapon` (`DataComponents.KINETIC_WEAPON`) | `KineticWeapon` | yes (cached) | yes |
| `swing_animation` (`DataComponents.SWING_ANIMATION`) | `SwingAnimation` | yes | yes |
| `additional_trade_cost` (`DataComponents.ADDITIONAL_TRADE_COST`) | `Integer` | | yes |
| `stored_enchantments` (`DataComponents.STORED_ENCHANTMENTS`) | `ItemEnchantments` | yes (cached) | yes |
| `dye` (`DataComponents.DYE`) | `DyeColor` | yes | yes |
| `dyed_color` (`DataComponents.DYED_COLOR`) | `DyedItemColor` | yes | yes |
| `map_color` (`DataComponents.MAP_COLOR`) | `MapItemColor` | yes | yes |
| `map_id` (`DataComponents.MAP_ID`) | `MapId` | yes | yes |
| `map_decorations` (`DataComponents.MAP_DECORATIONS`) | `MapDecorations` | yes (cached) | |
| `map_post_processing` (`DataComponents.MAP_POST_PROCESSING`) | `MapPostProcessing` | | yes |
| `charged_projectiles` (`DataComponents.CHARGED_PROJECTILES`) | `ChargedProjectiles` | yes (cached) | yes |
| `bundle_contents` (`DataComponents.BUNDLE_CONTENTS`) | `BundleContents` | yes (cached) | yes |
| `potion_contents` (`DataComponents.POTION_CONTENTS`) | `PotionContents` | yes (cached) | yes |
| `potion_duration_scale` (`DataComponents.POTION_DURATION_SCALE`) | `Float` | yes (cached) | yes |
| `suspicious_stew_effects` (`DataComponents.SUSPICIOUS_STEW_EFFECTS`) | `SuspiciousStewEffects` | yes (cached) | yes |
| `writable_book_content` (`DataComponents.WRITABLE_BOOK_CONTENT`) | `WritableBookContent` | yes (cached) | yes |
| `written_book_content` (`DataComponents.WRITTEN_BOOK_CONTENT`) | `WrittenBookContent` | yes (cached) | yes |
| `trim` (`DataComponents.TRIM`) | `ArmorTrim` | yes (cached) | yes |
| `debug_stick_state` (`DataComponents.DEBUG_STICK_STATE`) | `DebugStickState` | yes (cached) | |
| `entity_data` (`DataComponents.ENTITY_DATA`) | `TypedEntityData<…>` | yes | yes |
| `bucket_entity_data` (`DataComponents.BUCKET_ENTITY_DATA`) | `CustomData` | yes | yes |
| `block_entity_data` (`DataComponents.BLOCK_ENTITY_DATA`) | `TypedEntityData<…>` | yes | yes |
| `instrument` (`DataComponents.INSTRUMENT`) | `InstrumentComponent` | yes (cached) | yes |
| `provides_trim_material` (`DataComponents.PROVIDES_TRIM_MATERIAL`) | `Holder<…>` | yes (cached) | yes |
| `ominous_bottle_amplifier` (`DataComponents.OMINOUS_BOTTLE_AMPLIFIER`) | `OminousBottleAmplifier` | yes | yes |
| `jukebox_playable` (`DataComponents.JUKEBOX_PLAYABLE`) | `JukeboxPlayable` | yes | yes |
| `provides_banner_patterns` (`DataComponents.PROVIDES_BANNER_PATTERNS`) | `HolderSet<…>` | yes (cached) | yes |
| `recipes` (`DataComponents.RECIPES`) | `List<…>` | yes (cached) | |
| `lodestone_tracker` (`DataComponents.LODESTONE_TRACKER`) | `LodestoneTracker` | yes (cached) | yes |
| `firework_explosion` (`DataComponents.FIREWORK_EXPLOSION`) | `FireworkExplosion` | yes (cached) | yes |
| `fireworks` (`DataComponents.FIREWORKS`) | `Fireworks` | yes (cached) | yes |
| `profile` (`DataComponents.PROFILE`) | `ResolvableProfile` | yes (cached) | yes |
| `note_block_sound` (`DataComponents.NOTE_BLOCK_SOUND`) | `Identifier` | yes | yes |
| `banner_patterns` (`DataComponents.BANNER_PATTERNS`) | `BannerPatternLayers` | yes (cached) | yes |
| `base_color` (`DataComponents.BASE_COLOR`) | `DyeColor` | yes | yes |
| `pot_decorations` (`DataComponents.POT_DECORATIONS`) | `PotDecorations` | yes (cached) | yes |
| `container` (`DataComponents.CONTAINER`) | `ItemContainerContents` | yes (cached) | yes |
| `block_state` (`DataComponents.BLOCK_STATE`) | `BlockItemStateProperties` | yes (cached) | yes |
| `bees` (`DataComponents.BEES`) | `Bees` | yes (cached) | yes |
| `sulfur_cube_content` (`DataComponents.SULFUR_CUBE_CONTENT`) | `SulfurCubeContent` | yes (cached) | yes |
| `lock` (`DataComponents.LOCK`) | `LockCode` | yes | |
| `container_loot` (`DataComponents.CONTAINER_LOOT`) | `SeededContainerLoot` | yes | |
| `break_sound` (`DataComponents.BREAK_SOUND`) | `Holder<…>` | yes (cached) | yes |
| `villager/variant` (`DataComponents.VILLAGER_VARIANT`) | `Holder<…>` | yes | yes |
| `wolf/variant` (`DataComponents.WOLF_VARIANT`) | `Holder<…>` | yes | yes |
| `wolf/sound_variant` (`DataComponents.WOLF_SOUND_VARIANT`) | `Holder<…>` | yes | yes |
| `wolf/collar` (`DataComponents.WOLF_COLLAR`) | `DyeColor` | yes | yes |
| `fox/variant` (`DataComponents.FOX_VARIANT`) | `Fox.Variant` | yes | yes |
| `salmon/size` (`DataComponents.SALMON_SIZE`) | `Salmon.Variant` | yes | yes |
| `parrot/variant` (`DataComponents.PARROT_VARIANT`) | `Parrot.Variant` | yes | yes |
| `tropical_fish/pattern` (`DataComponents.TROPICAL_FISH_PATTERN`) | `TropicalFish.Pattern` | yes | yes |
| `tropical_fish/base_color` (`DataComponents.TROPICAL_FISH_BASE_COLOR`) | `DyeColor` | yes | yes |
| `tropical_fish/pattern_color` (`DataComponents.TROPICAL_FISH_PATTERN_COLOR`) | `DyeColor` | yes | yes |
| `mooshroom/variant` (`DataComponents.MOOSHROOM_VARIANT`) | `MushroomCow.Variant` | yes | yes |
| `rabbit/variant` (`DataComponents.RABBIT_VARIANT`) | `Rabbit.Variant` | yes | yes |
| `pig/variant` (`DataComponents.PIG_VARIANT`) | `Holder<…>` | yes | yes |
| `pig/sound_variant` (`DataComponents.PIG_SOUND_VARIANT`) | `Holder<…>` | yes | yes |
| `cow/variant` (`DataComponents.COW_VARIANT`) | `Holder<…>` | yes | yes |
| `cow/sound_variant` (`DataComponents.COW_SOUND_VARIANT`) | `Holder<…>` | yes | yes |
| `chicken/variant` (`DataComponents.CHICKEN_VARIANT`) | `Holder<…>` | yes | yes |
| `chicken/sound_variant` (`DataComponents.CHICKEN_SOUND_VARIANT`) | `Holder<…>` | yes | yes |
| `zombie_nautilus/variant` (`DataComponents.ZOMBIE_NAUTILUS_VARIANT`) | `Holder<…>` | yes | yes |
| `frog/variant` (`DataComponents.FROG_VARIANT`) | `Holder<…>` | yes | yes |
| `horse/variant` (`DataComponents.HORSE_VARIANT`) | `Variant` | yes | yes |
| `painting/variant` (`DataComponents.PAINTING_VARIANT`) | `Holder<…>` | yes | yes |
| `llama/variant` (`DataComponents.LLAMA_VARIANT`) | `Llama.Variant` | yes | yes |
| `axolotl/variant` (`DataComponents.AXOLOTL_VARIANT`) | `Axolotl.Variant` | yes | yes |
| `cat/variant` (`DataComponents.CAT_VARIANT`) | `Holder<…>` | yes | yes |
| `cat/sound_variant` (`DataComponents.CAT_SOUND_VARIANT`) | `Holder<…>` | yes | yes |
| `cat/collar` (`DataComponents.CAT_COLLAR`) | `DyeColor` | yes | yes |
| `sheep/color` (`DataComponents.SHEEP_COLOR`) | `DyeColor` | yes | yes |
| `shulker/color` (`DataComponents.SHULKER_COLOR`) | `DyeColor` | yes | yes |
---
# Game rules
> Generated from the **26.2** decompile by `tools/gen_reference.py`. Do not edit by hand.
Every rule declared in `GameRules`, with its category (`GameRuleCategory`) and default. Integer rules list their bounds and any feature gate after the default. Values live in a `GameRuleMap` — a `SavedData` at *data/minecraft/game_rules.dat*, one set for the whole server rather than one per level. See [Level data and rules](level-data-and-rules.md).
59 rules
| rule | type | category | default |
|---|---|---|---|
| `command_block_output` (`GameRules.COMMAND_BLOCK_OUTPUT`) | Boolean | chat | `true` |
| `log_admin_commands` (`GameRules.LOG_ADMIN_COMMANDS`) | Boolean | chat | `true` |
| `send_command_feedback` (`GameRules.SEND_COMMAND_FEEDBACK`) | Boolean | chat | `true` |
| `show_advancement_messages` (`GameRules.SHOW_ADVANCEMENT_MESSAGES`) | Boolean | chat | `true` |
| `show_death_messages` (`GameRules.SHOW_DEATH_MESSAGES`) | Boolean | chat | `true` |
| `block_drops` (`GameRules.BLOCK_DROPS`) | Boolean | drops | `true` |
| `block_explosion_drop_decay` (`GameRules.BLOCK_EXPLOSION_DROP_DECAY`) | Boolean | drops | `true` |
| `entity_drops` (`GameRules.ENTITY_DROPS`) | Boolean | drops | `true` |
| `mob_drops` (`GameRules.MOB_DROPS`) | Boolean | drops | `true` |
| `mob_explosion_drop_decay` (`GameRules.MOB_EXPLOSION_DROP_DECAY`) | Boolean | drops | `true` |
| `projectiles_can_break_blocks` (`GameRules.PROJECTILES_CAN_BREAK_BLOCKS`) | Boolean | drops | `true` |
| `tnt_explosion_drop_decay` (`GameRules.TNT_EXPLOSION_DROP_DECAY`) | Boolean | drops | `false` |
| `allow_entering_nether_using_portals` (`GameRules.ALLOW_ENTERING_NETHER_USING_PORTALS`) | Boolean | misc | `true` |
| `command_blocks_work` (`GameRules.COMMAND_BLOCKS_WORK`) | Boolean | misc | `true` |
| `global_sound_events` (`GameRules.GLOBAL_SOUND_EVENTS`) | Boolean | misc | `true` |
| `max_block_modifications` (`GameRules.MAX_BLOCK_MODIFICATIONS`) | Integer | misc | `32768 (min 1)` |
| `max_command_forks` (`GameRules.MAX_COMMAND_FORKS`) | Integer | misc | `65536 (min 0)` |
| `max_command_sequence_length` (`GameRules.MAX_COMMAND_SEQUENCE_LENGTH`) | Integer | misc | `65536 (min 0)` |
| `max_minecart_speed` (`GameRules.MAX_MINECART_SPEED`) | Integer | misc | `8 (min 1, max 1000, requires FeatureFlags.MINECART_IMPROVEMENTS)` |
| `reduced_debug_info` (`GameRules.REDUCED_DEBUG_INFO`) | Boolean | misc | `false` |
| `spawner_blocks_work` (`GameRules.SPAWNER_BLOCKS_WORK`) | Boolean | misc | `true` |
| `tnt_explodes` (`GameRules.TNT_EXPLODES`) | Boolean | misc | `true` |
| `forgive_dead_players` (`GameRules.FORGIVE_DEAD_PLAYERS`) | Boolean | mobs | `true` |
| `max_entity_cramming` (`GameRules.MAX_ENTITY_CRAMMING`) | Integer | mobs | `24 (min 0)` |
| `mob_griefing` (`GameRules.MOB_GRIEFING`) | Boolean | mobs | `true` |
| `raids` (`GameRules.RAIDS`) | Boolean | mobs | `true` |
| `universal_anger` (`GameRules.UNIVERSAL_ANGER`) | Boolean | mobs | `false` |
| `drowning_damage` (`GameRules.DROWNING_DAMAGE`) | Boolean | player | `true` |
| `elytra_movement_check` (`GameRules.ELYTRA_MOVEMENT_CHECK`) | Boolean | player | `true` |
| `ender_pearls_vanish_on_death` (`GameRules.ENDER_PEARLS_VANISH_ON_DEATH`) | Boolean | player | `true` |
| `fall_damage` (`GameRules.FALL_DAMAGE`) | Boolean | player | `true` |
| `fire_damage` (`GameRules.FIRE_DAMAGE`) | Boolean | player | `true` |
| `freeze_damage` (`GameRules.FREEZE_DAMAGE`) | Boolean | player | `true` |
| `immediate_respawn` (`GameRules.IMMEDIATE_RESPAWN`) | Boolean | player | `false` |
| `keep_inventory` (`GameRules.KEEP_INVENTORY`) | Boolean | player | `false` |
| `limited_crafting` (`GameRules.LIMITED_CRAFTING`) | Boolean | player | `false` |
| `locator_bar` (`GameRules.LOCATOR_BAR`) | Boolean | player | `true` |
| `natural_health_regeneration` (`GameRules.NATURAL_HEALTH_REGENERATION`) | Boolean | player | `true` |
| `player_movement_check` (`GameRules.PLAYER_MOVEMENT_CHECK`) | Boolean | player | `true` |
| `players_nether_portal_creative_delay` (`GameRules.PLAYERS_NETHER_PORTAL_CREATIVE_DELAY`) | Integer | player | `0 (min 0)` |
| `players_nether_portal_default_delay` (`GameRules.PLAYERS_NETHER_PORTAL_DEFAULT_DELAY`) | Integer | player | `80 (min 0)` |
| `players_sleeping_percentage` (`GameRules.PLAYERS_SLEEPING_PERCENTAGE`) | Integer | player | `100 (min 0)` |
| `pvp` (`GameRules.PVP`) | Boolean | player | `true` |
| `respawn_radius` (`GameRules.RESPAWN_RADIUS`) | Integer | player | `10 (min 0)` |
| `spectators_generate_chunks` (`GameRules.SPECTATORS_GENERATE_CHUNKS`) | Boolean | player | `true` |
| `spawn_mobs` (`GameRules.SPAWN_MOBS`) | Boolean | spawning | `true` |
| `spawn_monsters` (`GameRules.SPAWN_MONSTERS`) | Boolean | spawning | `true` |
| `spawn_patrols` (`GameRules.SPAWN_PATROLS`) | Boolean | spawning | `true` |
| `spawn_phantoms` (`GameRules.SPAWN_PHANTOMS`) | Boolean | spawning | `true` |
| `spawn_wandering_traders` (`GameRules.SPAWN_WANDERING_TRADERS`) | Boolean | spawning | `true` |
| `spawn_wardens` (`GameRules.SPAWN_WARDENS`) | Boolean | spawning | `true` |
| `advance_time` (`GameRules.ADVANCE_TIME`) | Boolean | updates | `true` |
| `advance_weather` (`GameRules.ADVANCE_WEATHER`) | Boolean | updates | `true` |
| `fire_spread_radius_around_player` (`GameRules.FIRE_SPREAD_RADIUS_AROUND_PLAYER`) | Integer | updates | `128 (min -1)` |
| `lava_source_conversion` (`GameRules.LAVA_SOURCE_CONVERSION`) | Boolean | updates | `false` |
| `max_snow_accumulation_height` (`GameRules.MAX_SNOW_ACCUMULATION_HEIGHT`) | Integer | updates | `1 (min 0, max 8)` |
| `random_tick_speed` (`GameRules.RANDOM_TICK_SPEED`) | Integer | updates | `3 (min 0)` |
| `spread_vines` (`GameRules.SPREAD_VINES`) | Boolean | updates | `true` |
| `water_source_conversion` (`GameRules.WATER_SOURCE_CONVERSION`) | Boolean | updates | `true` |
---
# Attributes
> Generated from the **26.2** decompile by `tools/gen_reference.py`. Do not edit by hand.
Every attribute registered in `Attributes`. All of them are `RangedAttribute`s, so every one clamps to its range once, at the end of `AttributeInstance.calculateValue`. **Syncable** attributes are the only ones `ClientboundUpdateAttributesPacket` ever carries: a mutation to one of the others changes the server's number and never reaches the client at all. The sentiment decides tooltip colour and nothing else. Defaults here are the registry's, and most entity types override them in their own `AttributeSupplier`. See [Attributes](../systems/entities/attributes.md).
40 attributes, 32 syncable and 8 not
| id | constant | default | min | max | syncable | sentiment |
|---|---|---:|---:|---:|---|---|
| `air_drag_modifier` | `Attributes.AIR_DRAG_MODIFIER` | 1 | 0 | 2048 | yes | positive |
| `armor` | `Attributes.ARMOR` | 0 | 0 | 30 | yes | positive |
| `armor_toughness` | `Attributes.ARMOR_TOUGHNESS` | 0 | 0 | 20 | yes | positive |
| `attack_damage` | `Attributes.ATTACK_DAMAGE` | 2 | 0 | 2048 | | positive |
| `attack_knockback` | `Attributes.ATTACK_KNOCKBACK` | 0 | 0 | 5 | | positive |
| `attack_speed` | `Attributes.ATTACK_SPEED` | 4 | 0 | 1024 | yes | positive |
| `below_name_distance` | `Attributes.BELOW_NAME_DISTANCE` | 10 | 0 | 512 | yes | positive |
| `block_break_speed` | `Attributes.BLOCK_BREAK_SPEED` | 1 | 0 | 1024 | yes | positive |
| `block_interaction_range` | `Attributes.BLOCK_INTERACTION_RANGE` | 4.5 | 0 | 64 | yes | positive |
| `bounciness` | `Attributes.BOUNCINESS` | 0 | 0 | 1 | yes | positive |
| `burning_time` | `Attributes.BURNING_TIME` | 1 | 0 | 1024 | yes | negative |
| `camera_distance` | `Attributes.CAMERA_DISTANCE` | 4 | 0 | 32 | yes | positive |
| `entity_interaction_range` | `Attributes.ENTITY_INTERACTION_RANGE` | 3 | 0 | 64 | yes | positive |
| `explosion_knockback_resistance` | `Attributes.EXPLOSION_KNOCKBACK_RESISTANCE` | 0 | 0 | 1 | yes | positive |
| `fall_damage_multiplier` | `Attributes.FALL_DAMAGE_MULTIPLIER` | 1 | 0 | 100 | yes | negative |
| `flying_speed` | `Attributes.FLYING_SPEED` | 0.4 | 0 | 1024 | yes | positive |
| `follow_range` | `Attributes.FOLLOW_RANGE` | 32 | 0 | 2048 | | positive |
| `friction_modifier` | `Attributes.FRICTION_MODIFIER` | 1 | 0 | 2048 | yes | positive |
| `gravity` | `Attributes.GRAVITY` | 0.08 | -1 | 1 | yes | neutral |
| `jump_strength` | `Attributes.JUMP_STRENGTH` | 0.42 | 0 | 32 | yes | positive |
| `knockback_resistance` | `Attributes.KNOCKBACK_RESISTANCE` | 0 | -2 | 1 | | positive |
| `luck` | `Attributes.LUCK` | 0 | -1024 | 1024 | yes | positive |
| `max_absorption` | `Attributes.MAX_ABSORPTION` | 0 | 0 | 2048 | yes | positive |
| `max_health` | `Attributes.MAX_HEALTH` | 20 | 1 | 1024 | yes | positive |
| `mining_efficiency` | `Attributes.MINING_EFFICIENCY` | 0 | 0 | 1024 | yes | positive |
| `movement_efficiency` | `Attributes.MOVEMENT_EFFICIENCY` | 0 | 0 | 1 | yes | positive |
| `movement_speed` | `Attributes.MOVEMENT_SPEED` | 0.7 | 0 | 1024 | yes | positive |
| `name_tag_distance` | `Attributes.NAME_TAG_DISTANCE` | 64 | 0 | 512 | yes | positive |
| `oxygen_bonus` | `Attributes.OXYGEN_BONUS` | 0 | 0 | 1024 | yes | positive |
| `safe_fall_distance` | `Attributes.SAFE_FALL_DISTANCE` | 3 | -1024 | 1024 | yes | positive |
| `scale` | `Attributes.SCALE` | 1 | 0.0625 | 16 | yes | neutral |
| `sneaking_speed` | `Attributes.SNEAKING_SPEED` | 0.3 | 0 | 1 | yes | positive |
| `spawn_reinforcements` | `Attributes.SPAWN_REINFORCEMENTS_CHANCE` | 0 | 0 | 1 | | positive |
| `step_height` | `Attributes.STEP_HEIGHT` | 0.6 | 0 | 10 | yes | positive |
| `submerged_mining_speed` | `Attributes.SUBMERGED_MINING_SPEED` | 0.2 | 0 | 20 | yes | positive |
| `sweeping_damage_ratio` | `Attributes.SWEEPING_DAMAGE_RATIO` | 0 | 0 | 1 | yes | positive |
| `tempt_range` | `Attributes.TEMPT_RANGE` | 10 | 0 | 2048 | | positive |
| `water_movement_efficiency` | `Attributes.WATER_MOVEMENT_EFFICIENCY` | 0 | 0 | 1 | yes | positive |
| `waypoint_receive_range` | `Attributes.WAYPOINT_RECEIVE_RANGE` | 0 | 0 | 60000000 | | neutral |
| `waypoint_transmit_range` | `Attributes.WAYPOINT_TRANSMIT_RANGE` | 0 | 0 | 60000000 | | neutral |
---
# Entity data serializers
> Generated from the **26.2** decompile by `tools/gen_reference.py`. Do not edit by hand.
Every `EntityDataSerializer` in `EntityDataSerializers`, in **registration order, which is the wire id** — `EntityDataSerializers.registerSerializer` pushes each one into a `CrudeIncrementalIntIdentityHashBiMap` that hands out the next int. A `SynchedEntityData.DataValue` on the wire is an unsigned byte accessor id, this var-int, and the encoded value. *For value type* marks the ones built by `EntityDataSerializer.forValueType`, the immutable case where `EntityDataSerializer.copy` is identity. See [Synched entity data](../systems/entities/synched-entity-data.md).
43 serializers, wire ids 0 to 42
| id | constant | value type | built by |
|---:|---|---|---|
| 0 | `EntityDataSerializers.BYTE` | `Byte` | for value type |
| 1 | `EntityDataSerializers.INT` | `Integer` | for value type |
| 2 | `EntityDataSerializers.LONG` | `Long` | for value type |
| 3 | `EntityDataSerializers.FLOAT` | `Float` | for value type |
| 4 | `EntityDataSerializers.STRING` | `String` | for value type |
| 5 | `EntityDataSerializers.COMPONENT` | `Component` | for value type |
| 6 | `EntityDataSerializers.OPTIONAL_COMPONENT` | `Optional` | for value type |
| 7 | `EntityDataSerializers.ITEM_STACK` | `ItemStack` | an anonymous subclass |
| 8 | `EntityDataSerializers.BOOLEAN` | `Boolean` | for value type |
| 9 | `EntityDataSerializers.ROTATIONS` | `Rotations` | for value type |
| 10 | `EntityDataSerializers.BLOCK_POS` | `BlockPos` | for value type |
| 11 | `EntityDataSerializers.OPTIONAL_BLOCK_POS` | `Optional` | for value type |
| 12 | `EntityDataSerializers.DIRECTION` | `Direction` | for value type |
| 13 | `EntityDataSerializers.OPTIONAL_LIVING_ENTITY_REFERENCE` | `Optional>` | for value type |
| 14 | `EntityDataSerializers.BLOCK_STATE` | `BlockState` | for value type |
| 15 | `EntityDataSerializers.OPTIONAL_BLOCK_STATE` | `Optional` | for value type |
| 16 | `EntityDataSerializers.PARTICLE` | `ParticleOptions` | for value type |
| 17 | `EntityDataSerializers.PARTICLES` | `List` | for value type |
| 18 | `EntityDataSerializers.VILLAGER_DATA` | `VillagerData` | for value type |
| 19 | `EntityDataSerializers.OPTIONAL_UNSIGNED_INT` | `OptionalInt` | for value type |
| 20 | `EntityDataSerializers.POSE` | `Pose` | for value type |
| 21 | `EntityDataSerializers.CAT_VARIANT` | `Holder` | for value type |
| 22 | `EntityDataSerializers.CAT_SOUND_VARIANT` | `Holder` | for value type |
| 23 | `EntityDataSerializers.COW_VARIANT` | `Holder` | for value type |
| 24 | `EntityDataSerializers.COW_SOUND_VARIANT` | `Holder` | for value type |
| 25 | `EntityDataSerializers.WOLF_VARIANT` | `Holder` | for value type |
| 26 | `EntityDataSerializers.WOLF_SOUND_VARIANT` | `Holder` | for value type |
| 27 | `EntityDataSerializers.FROG_VARIANT` | `Holder` | for value type |
| 28 | `EntityDataSerializers.PIG_VARIANT` | `Holder` | for value type |
| 29 | `EntityDataSerializers.PIG_SOUND_VARIANT` | `Holder` | for value type |
| 30 | `EntityDataSerializers.CHICKEN_VARIANT` | `Holder` | for value type |
| 31 | `EntityDataSerializers.CHICKEN_SOUND_VARIANT` | `Holder` | for value type |
| 32 | `EntityDataSerializers.ZOMBIE_NAUTILUS_VARIANT` | `Holder` | for value type |
| 33 | `EntityDataSerializers.OPTIONAL_GLOBAL_POS` | `Optional` | for value type |
| 34 | `EntityDataSerializers.PAINTING_VARIANT` | `Holder` | for value type |
| 35 | `EntityDataSerializers.SNIFFER_STATE` | `Sniffer.State` | for value type |
| 36 | `EntityDataSerializers.ARMADILLO_STATE` | `Armadillo.ArmadilloState` | for value type |
| 37 | `EntityDataSerializers.COPPER_GOLEM_STATE` | `CopperGolemState` | for value type |
| 38 | `EntityDataSerializers.WEATHERING_COPPER_STATE` | `WeatheringCopper.WeatherState` | for value type |
| 39 | `EntityDataSerializers.VECTOR3` | `Vector3fc` | for value type |
| 40 | `EntityDataSerializers.QUATERNION` | `Quaternionfc` | for value type |
| 41 | `EntityDataSerializers.RESOLVABLE_PROFILE` | `ResolvableProfile` | for value type |
| 42 | `EntityDataSerializers.HUMANOID_ARM` | `HumanoidArm` | for value type |
---
# Enchantment hooks
> Generated from the **26.2** decompile by `tools/gen_reference.py`. Do not edit by hand.
Every public entry point of `EnchantmentHelper`, with the classes that call it. The enchantment package barely calls anything and everything calls it, so this table is the system's real interface: each row is a moment at which some other system asks whether an enchantment wants to change what happens. Callers are the declaring files, one per class, excluding `EnchantmentHelper` itself. See [Enchantments](../systems/items/enchantments.md) for what an enchantment is and [Enchanting](../systems/items/enchanting.md) for the selection half.
50 entry points, 47 of them called from outside the class
| entry point | overloads | called from |
|---|---:|---|
| `EnchantmentHelper.canStoreEnchantments` | 1 | `AnvilMenu` |
| `EnchantmentHelper.createBook` | 1 | `CreativeModeTabs` |
| `EnchantmentHelper.doPostAttackEffects` | 1 | `AbstractCubeMob`, `AbstractWindCharge`, `Bee`, `ChargeAttack`, `EnderDragon`, `EvokerFangs`, `HoglinBase`, `IronGolem`, `LargeFireball`, `LivingEntity`, `LlamaSpit`, `Mob`, `Player`, `RamTarget`, `ShulkerBullet`, `SmallFireball`, `WitherSkull` |
| `EnchantmentHelper.doPostAttackEffectsWithItemSource` | 1 | `AbstractArrow`, `Player` |
| `EnchantmentHelper.doPostAttackEffectsWithItemSourceOnBreak` | 1 | `ThrownTrident` |
| `EnchantmentHelper.doPostPiercingAttackEffects` | 1 | `LivingEntity` |
| `EnchantmentHelper.enchantItem` | 2 | `EnchantWithLevelsFunction` |
| `EnchantmentHelper.enchantItemFromProvider` | 1 | `EnderMan`, `Mob`, `Pillager`, `SkeletonTrapGoal`, `Vindicator` |
| `EnchantmentHelper.filterCompatibleEnchantments` | 1 | *nothing outside the class* |
| `EnchantmentHelper.forEachModifier` | 2 | `ItemStack` |
| `EnchantmentHelper.getAvailableEnchantmentResults` | 1 | *nothing outside the class* |
| `EnchantmentHelper.getDamageProtection` | 1 | `LivingEntity` |
| `EnchantmentHelper.getEnchantmentCost` | 1 | `EnchantmentMenu` |
| `EnchantmentHelper.getEnchantmentLevel` | 1 | `EnchantedCountIncreaseFunction`, `LootItemRandomChanceWithEnchantedBonusCondition` |
| `EnchantmentHelper.getEnchantmentsForCrafting` | 1 | `AnvilMenu`, `EnchantCommand`, `GrindstoneMenu`, `RepairItemRecipe` |
| `EnchantmentHelper.getFishingLuckBonus` | 1 | `FishingRodItem` |
| `EnchantmentHelper.getFishingTimeReduction` | 1 | `FishingRodItem` |
| `EnchantmentHelper.getHighestLevel` | 1 | *nothing outside the class* |
| `EnchantmentHelper.getItemEnchantmentLevel` | 1 | `ApplyBonusCount`, `BonusLevelTableCondition` |
| `EnchantmentHelper.getPiercingCount` | 1 | `AbstractArrow` |
| `EnchantmentHelper.getRandomItemWith` | 1 | `ExperienceOrb` |
| `EnchantmentHelper.getTridentReturnToOwnerAcceleration` | 1 | `ThrownTrident` |
| `EnchantmentHelper.getTridentSpinAttackStrength` | 1 | `TridentItem` |
| `EnchantmentHelper.has` | 1 | `AbstractHorse`, `Allay`, `ArmorSlot`, `ArmorStand`, `Equippable`, `Mob`, `Piglin`, `Player`, `ZombieVillager` |
| `EnchantmentHelper.hasAnyEnchantments` | 1 | `GrindstoneMenu` |
| `EnchantmentHelper.hasTag` | 1 | `BeehiveBlock`, `DecoratedPotBlock`, `IceBlock`, `InfestedBlock` |
| `EnchantmentHelper.isEnchantmentCompatible` | 1 | `EnchantCommand` |
| `EnchantmentHelper.isImmuneToDamage` | 1 | `LivingEntity` |
| `EnchantmentHelper.modifyArmorEffectiveness` | 1 | `CombatRules` |
| `EnchantmentHelper.modifyCrossbowChargingTime` | 1 | `CrossbowItem` |
| `EnchantmentHelper.modifyDamage` | 1 | `AbstractArrow`, `LivingEntity`, `Mob`, `ServerPlayer`, `ThrownTrident` |
| `EnchantmentHelper.modifyDurabilityToRepairFromXp` | 1 | `ExperienceOrb` |
| `EnchantmentHelper.modifyFallBasedDamage` | 1 | `MaceItem` |
| `EnchantmentHelper.modifyKnockback` | 1 | `AbstractArrow`, `LivingEntity` |
| `EnchantmentHelper.onHitBlock` | 1 | `AbstractArrow`, `ServerPlayerGameMode`, `ThrownTrident` |
| `EnchantmentHelper.onProjectileSpawned` | 1 | `Projectile` |
| `EnchantmentHelper.pickHighestLevel` | 1 | `CrossbowItem`, `TridentItem` |
| `EnchantmentHelper.processAmmoUse` | 1 | `ProjectileWeaponItem` |
| `EnchantmentHelper.processBlockExperience` | 1 | `Block` |
| `EnchantmentHelper.processDurabilityChange` | 1 | `ItemStack` |
| `EnchantmentHelper.processEquipmentDropChance` | 1 | `Mob` |
| `EnchantmentHelper.processMobExperience` | 1 | `LivingEntity` |
| `EnchantmentHelper.processProjectileCount` | 1 | `ProjectileWeaponItem` |
| `EnchantmentHelper.processProjectileSpread` | 1 | `ProjectileWeaponItem` |
| `EnchantmentHelper.runLocationChangedEffects` | 2 | `LivingEntity`, `ServerPlayer` |
| `EnchantmentHelper.selectEnchantment` | 1 | `EnchantmentMenu`, `EnchantmentsByCost`, `EnchantmentsByCostWithDifficulty` |
| `EnchantmentHelper.setEnchantments` | 1 | `AnvilMenu` |
| `EnchantmentHelper.stopLocationBasedEffects` | 2 | `LivingEntity`, `ServerPlayer` |
| `EnchantmentHelper.tickEffects` | 1 | `LivingEntity` |
| `EnchantmentHelper.updateEnchantments` | 1 | `GrindstoneMenu`, `ItemStack`, `RepairItemRecipe`, `SetEnchantmentsFunction` |
---
# Loot context parameter sets
> Generated from the **26.2** decompile by `tools/gen_reference.py`. Do not edit by hand.
Every `ContextKeySet` registered in `LootContextParamSets`, with the keys its `ContextKeySet.Builder` declared. The set belongs to the **caller**, not to the loot table: `ContextMap.Builder.create` throws both on a required key that is absent and on a key the set does not declare at all, so this table is the contract each call site has to satisfy. A required key can be read with `LootContext.getParameter`, an optional one only with `LootContext.getOptionalParameter`. Twelve of these twenty-six sets never roll a `LootTable` at all — the engine is older and wider than the loot package. See [Contexts and predicates](../systems/items/contexts-and-predicates.md).
26 parameter sets
| set | id | required | optional |
|---|---|---|---|
| `LootContextParamSets.ADVANCEMENT_ENTITY` | *advancement_entity* | `LootContextParams.THIS_ENTITY`, `LootContextParams.ORIGIN` | — |
| `LootContextParamSets.ADVANCEMENT_LOCATION` | *advancement_location* | `LootContextParams.THIS_ENTITY`, `LootContextParams.ORIGIN`, `LootContextParams.TOOL`, `LootContextParams.BLOCK_STATE` | — |
| `LootContextParamSets.ADVANCEMENT_REWARD` | *advancement_reward* | `LootContextParams.THIS_ENTITY`, `LootContextParams.ORIGIN` | — |
| `LootContextParamSets.ARCHAEOLOGY` | *archaeology* | `LootContextParams.ORIGIN`, `LootContextParams.THIS_ENTITY`, `LootContextParams.TOOL` | — |
| `LootContextParamSets.PIGLIN_BARTER` | *barter* | `LootContextParams.THIS_ENTITY` | — |
| `LootContextParamSets.BLOCK` | *block* | `LootContextParams.BLOCK_STATE`, `LootContextParams.ORIGIN`, `LootContextParams.TOOL` | `LootContextParams.THIS_ENTITY`, `LootContextParams.BLOCK_ENTITY`, `LootContextParams.EXPLOSION_RADIUS` |
| `LootContextParamSets.BLOCK_INTERACT` | *block_interact* | `LootContextParams.BLOCK_STATE` | `LootContextParams.BLOCK_ENTITY`, `LootContextParams.INTERACTING_ENTITY`, `LootContextParams.TOOL` |
| `LootContextParamSets.BLOCK_USE` | *block_use* | `LootContextParams.THIS_ENTITY`, `LootContextParams.ORIGIN`, `LootContextParams.BLOCK_STATE` | — |
| `LootContextParamSets.CHEST` | *chest* | `LootContextParams.ORIGIN` | `LootContextParams.THIS_ENTITY` |
| `LootContextParamSets.COMMAND` | *command* | `LootContextParams.ORIGIN` | `LootContextParams.THIS_ENTITY` |
| `LootContextParamSets.EMPTY` | *empty* | — | — |
| `LootContextParamSets.ENCHANTED_DAMAGE` | *enchanted_damage* | `LootContextParams.THIS_ENTITY`, `LootContextParams.ENCHANTMENT_LEVEL`, `LootContextParams.ORIGIN`, `LootContextParams.DAMAGE_SOURCE` | `LootContextParams.DIRECT_ATTACKING_ENTITY`, `LootContextParams.ATTACKING_ENTITY` |
| `LootContextParamSets.ENCHANTED_ENTITY` | *enchanted_entity* | `LootContextParams.THIS_ENTITY`, `LootContextParams.ENCHANTMENT_LEVEL`, `LootContextParams.ORIGIN` | — |
| `LootContextParamSets.ENCHANTED_ITEM` | *enchanted_item* | `LootContextParams.TOOL`, `LootContextParams.ENCHANTMENT_LEVEL` | — |
| `LootContextParamSets.ENCHANTED_LOCATION` | *enchanted_location* | `LootContextParams.THIS_ENTITY`, `LootContextParams.ENCHANTMENT_LEVEL`, `LootContextParams.ORIGIN`, `LootContextParams.ENCHANTMENT_ACTIVE` | — |
| `LootContextParamSets.ENTITY` | *entity* | `LootContextParams.THIS_ENTITY`, `LootContextParams.ORIGIN`, `LootContextParams.DAMAGE_SOURCE` | `LootContextParams.ATTACKING_ENTITY`, `LootContextParams.DIRECT_ATTACKING_ENTITY`, `LootContextParams.LAST_DAMAGE_PLAYER` |
| `LootContextParamSets.ENTITY_INTERACT` | *entity_interact* | `LootContextParams.TARGET_ENTITY`, `LootContextParams.TOOL` | `LootContextParams.INTERACTING_ENTITY` |
| `LootContextParamSets.EQUIPMENT` | *equipment* | `LootContextParams.ORIGIN`, `LootContextParams.THIS_ENTITY` | — |
| `LootContextParamSets.FISHING` | *fishing* | `LootContextParams.ORIGIN`, `LootContextParams.TOOL` | `LootContextParams.THIS_ENTITY` |
| `LootContextParamSets.ALL_PARAMS` | *generic* | `LootContextParams.THIS_ENTITY`, `LootContextParams.LAST_DAMAGE_PLAYER`, `LootContextParams.DAMAGE_SOURCE`, `LootContextParams.ATTACKING_ENTITY`, `LootContextParams.DIRECT_ATTACKING_ENTITY`, `LootContextParams.ORIGIN`, `LootContextParams.BLOCK_STATE`, `LootContextParams.BLOCK_ENTITY`, `LootContextParams.TOOL`, `LootContextParams.EXPLOSION_RADIUS`, `LootContextParams.ADDITIONAL_COST_COMPONENT_ALLOWED` | — |
| `LootContextParamSets.GIFT` | *gift* | `LootContextParams.ORIGIN`, `LootContextParams.THIS_ENTITY` | — |
| `LootContextParamSets.HIT_BLOCK` | *hit_block* | `LootContextParams.THIS_ENTITY`, `LootContextParams.ENCHANTMENT_LEVEL`, `LootContextParams.ORIGIN`, `LootContextParams.BLOCK_STATE` | — |
| `LootContextParamSets.SELECTOR` | *selector* | `LootContextParams.ORIGIN`, `LootContextParams.THIS_ENTITY` | — |
| `LootContextParamSets.SHEARING` | *shearing* | `LootContextParams.ORIGIN`, `LootContextParams.THIS_ENTITY`, `LootContextParams.TOOL` | — |
| `LootContextParamSets.VAULT` | *vault* | `LootContextParams.ORIGIN` | `LootContextParams.THIS_ENTITY`, `LootContextParams.TOOL` |
| `LootContextParamSets.VILLAGER_TRADE` | *villager_trade* | `LootContextParams.ORIGIN`, `LootContextParams.THIS_ENTITY`, `LootContextParams.ADDITIONAL_COST_COMPONENT_ALLOWED` | — |
---
# Block update flags
> Verified against **Minecraft 26.2** · Reference · Hand-kept from `Block`'s
> *UPDATE_* constants: the ten bits of `Level.setBlock`'s flag word, what
> reads each, and the named combinations.
The flag word is `Level.setBlock`'s third argument — the last one on the
three-argument overload, and followed by an *update limit* on the four-argument
one. It is a bit set, tagged in signatures by `Block.UpdateFlags`, an
annotation that carries no values of its own.
[Blocks and states](../systems/blocks/blocks-and-states.md) draws the tail
of `Level.setBlock` as a flowchart whose gates name these bits by number;
this is the table behind the numbers, and every other page that passes a flag
word — [fluids](../systems/world/fluids.md)' bucket and [pistons and block
events](../systems/blocks/pistons-and-block-events.md)' moves among them —
means the same bits.
| bit | constant | what reads it |
|---:|---|---|
| 1 | `Block.UPDATE_NEIGHBORS` | the neighbour fan-out in the tail, and the gate on `BlockBehaviour.BlockStateBase.affectNeighborsAfterRemoval` in the chunk write |
| 2 | `Block.UPDATE_CLIENTS` | `Level.sendBlockUpdated` — the broadcast on the server, a re-mesh on the client, where it is `LevelExtractor.blockChanged` |
| 4 | `Block.UPDATE_INVISIBLE` | suppresses whichever of those the side does |
| 8 | `Block.UPDATE_IMMEDIATE` | one place in the game: `LevelExtractor.blockChanged`, which marks the re-mesh as player-caused |
| 16 | `Block.UPDATE_KNOWN_SHAPE` | three readers: `Level.setBlock`, where it suppresses all three shape passes; `BlockInput.place`, where it decides whether the state is fixed up against its neighbours before the write; and `WorldGenRegion.setBlock`, where it suppresses the post-processing mark |
| 32 | `Block.UPDATE_SUPPRESS_DROPS` | `Block.updateOrDestroy`, whose destroy branch drops resources unless it is set. A one-level flag: it is masked out of the word as it propagates, both to the neighbours and into the recursive write |
| 64 | `Block.UPDATE_MOVE_BY_PISTON` | passed on as *movedByPiston*, and lets `BlockBehaviour.BlockStateBase.affectNeighborsAfterRemoval` run without bit 1 |
| 128 | `Block.UPDATE_SKIP_SHAPE_UPDATE_ON_WIRE` | `NeighborUpdater.executeShapeUpdate`, which then skips redstone wire |
| 256 | `Block.UPDATE_SKIP_BLOCK_ENTITY_SIDEEFFECTS` | suppresses `BlockEntity.preRemoveSideEffects` |
| 512 | `Block.UPDATE_SKIP_ON_PLACE` | suppresses `BlockBehaviour.BlockStateBase.onPlace` |
The named combinations are `Block.UPDATE_ALL` (3),
`Block.UPDATE_ALL_IMMEDIATE` (11, what placement uses),
`Block.UPDATE_NONE` (260) and `Block.UPDATE_SKIP_ALL_SIDEEFFECTS` (816).
`Block.UPDATE_LIMIT` is also 512, and is not a bit at all — it is the default
recursion budget for the shape cascade.
---
*Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes `tools/verify_names.py`.*
---
# Damage outside `LivingEntity`
> Verified against **Minecraft 26.2** · Reference · Hand-kept from
> `net/minecraft/world/entity/**`.
`Entity.hurtServer` is **abstract**, so every branch has to answer for itself.
(`Entity.hurtClient`, the other half of the pair, does have a default: it
returns false, and twelve of the rows below inherit it unchanged.) The
twenty-one classes are every non-`LivingEntity` class that declares
`Entity.hurtServer`, and they answer with rules that share nothing with
`LivingEntity`'s — no armour, no invulnerability window, no `CombatTracker`,
no death sequence. The lecture that frames them is [damage and
death](../systems/entities/damage-and-death.md); this is the per-class table.
Two things happen before any of them. `Player.cannotAttack` asks
`Entity.isAttackable` and then `Entity.skipAttackInteraction`, either of which
can end the swing before the entity is asked anything — `Interaction` uses that
hook to record who hit it and `BlockAttachedEntity` to re-enter through
`Entity.hurtOrSimulate` with zero damage. `Entity.hurtOrSimulate` is what
`Player.attack` calls, and it picks `Entity.hurtServer` or `Entity.hurtClient`
off the level;
its answer is not "did the hit land" but "was anything damaged", and it is what
gates the knockback, the sweep, the durability loss and the hit particles.
| class | what it checks first | what it does | returns |
|---|---|---|---|
| `AreaEffectCloud` | — | nothing | false |
| `Display` | — | nothing | false |
| `Interaction` | — | nothing; the attacker was already recorded in `Entity.skipAttackInteraction` | false |
| `LightningBolt` | — | nothing | false |
| `Marker` | — | nothing | false |
| `OminousItemSpawner` | — | nothing | false |
| `PrimedTnt` | — | nothing: a lit TNT block cannot be shot out of the air | false |
| `EvokerFangs` | — | nothing | false |
| `EyeOfEnder` | — | nothing | false |
| `AbstractHurtingProjectile` | — | nothing — but a fireball or a wind charge is *deflected* before this is reached, since `Player.attack` calls `Player.deflectProjectile` first for anything in `EntityTypeTags.REDIRECTABLE_PROJECTILE`; the rest are unhittable by `Entity.isPickable` | false |
| `Projectile` | `Entity.isInvulnerableToBase` | `Entity.markHurt` only, so the client sees a flinch and nothing changes | false |
| `FallingBlockEntity` | `Entity.isInvulnerableToBase` | `Entity.markHurt` only | false |
| `ExperienceOrb` | `Entity.isInvulnerableToBase` | subtracts the damage from an int of health, `Entity.discard` at zero | true |
| `ItemEntity` | `Entity.isInvulnerableToBase`, then a `Mob` source under `GameRules.MOB_GRIEFING`, then `ItemStack.canBeHurtBy` | same int of health, plus `GameEvent.ENTITY_DAMAGE`, and `ItemStack.onDestroyed` before the discard | true |
| `BlockAttachedEntity` | `Entity.isInvulnerableToBase`, then a `Mob` source under `GameRules.MOB_GRIEFING` | `Entity.kill`, `Entity.markHurt`, and drops its item — one hit, whatever the amount | true |
| `ItemFrame` | `ItemFrame.fixed` gates everything: a fixed frame is hurt only by `DamageTypeTags.BYPASSES_INVULNERABILITY` or a creative player | a non-explosion hit on a frame **holding** something pops the item and stops there; otherwise it falls through to `BlockAttachedEntity` and the frame breaks | true |
| `EndCrystal` | `Entity.isInvulnerableToBase`, then **is the source an `EnderDragon`** | removes itself with `Entity.RemovalReason.KILLED` and explodes with power 6 — unless the source was already an explosion — then `EndCrystal.onDestroyedBy` | true |
| `ShulkerBullet` | — | plays `SoundEvents.SHULKER_BULLET_HURT`, spawns fifteen `ParticleTypes.CRIT`, destroys itself | true |
| `EnderDragonPart` | `Entity.isInvulnerableToBase` | forwards the whole call to `EnderDragon.hurt` with itself as the part that was hit | the parent's answer |
| `VehicleEntity` | already removed, then `Entity.isInvulnerableToBase` | flips `VehicleEntity.getHurtDir`, sets ten ticks of hurt time, adds *damage × 10* to `VehicleEntity.getDamage`, and destroys past 40 — a creative player skips to `Entity.discard` | true |
| `MinecartTNT` | a **burning** `AbstractArrow` as the direct entity explodes it, scaled by the arrow's speed | on that path, nothing else: the explosion calls `Entity.discard`, so `VehicleEntity.hurtServer` returns at its already-removed test. Any other source falls through to everything `VehicleEntity` does | true |
Six patterns account for all of it: *nothing happens* (ten classes), *a
flinch and nothing else* (two), *an int of health with no armour and no
window* (two), *one hit destroys* (four), *an accumulator* (two), and
*forward it to something else* (one, the dragon part). The classes that read
the damage **amount** at all are the pair with an int of health, the
accumulator pair, and `EnderDragonPart`, which hands the number to the dragon;
everything else is a yes-or-no.
---
*Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes `tools/verify_names.py`.*
---
# What the HUD draws, and when
> Verified against **Minecraft 26.2** · Reference · Hand-kept from
> `Hud.extractRenderState` and `Gui.extractRenderState`.
The HUD is one ordered method, and almost every element in it is behind a
condition — the contextual bar is the exception, recorded unconditionally and
made to draw nothing by an empty state object instead.
The lecture that frames this is [the HUD](../systems/client/hud.md); this is
the table it is built on, in **record order** — which is also the order
things appear in front of each other, since [the GUI render
tree](../systems/client/the-gui-render-tree.md) infers layering from call
order and bounding boxes.
Three gates sit above everything below and none of them belongs to `Hud`:
`GameRenderer.extract` computes them — resources loaded, the frame advancing
game time, a level existing — and `Gui.extractRenderState` is what applies
them, calling into `Hud` only when they hold. `Hud.extractRenderState` then
short-circuits entirely while a `LevelLoadingScreen` is up — but it publishes
`GuiRenderState.isHudHidden` **before** that check, so the flag is always
current even when nothing is recorded.
*Hidden* in the last column means `Hud.isHidden`, which `Options.keyToggleGui`
— F1 — flips.
| # | element | recorded by | hidden by F1? | its own condition |
|---:|---|---|---|---|
| 1 | vignette | `Hud.extractVignette` | yes | `Options.vignette` is on |
| 2 | spyglass overlay | `Hud.extractSpyglassOverlay` | yes | first-person camera **and** the player is scoping |
| 3 | equipment camera overlay | `Hud.extractTextureOverlay` | yes | first-person, not scoping, and some equipped item's `Equippable` declares a camera overlay for the slot it is in |
| 4 | powder-snow outline | `Hud.extractTextureOverlay` | yes | `Entity.getTicksFrozen` above zero |
| 5 | portal overlay | `Hud.extractPortalOverlay` | yes | the interpolated portal intensity is above zero |
| 6 | nausea overlay | `Hud.extractConfusionOverlay` | yes | *else*: no portal effect, a nausea blend above zero, and `Options.screenEffectScale` below one |
| 7 | crosshair | `Hud.extractCrosshair` | yes | first-person; either not a spectator or a hit result a spectator may see; **and** the F3 three-dimensional-crosshair entry is off |
| — | *new stratum* | `GuiGraphicsExtractor.nextStratum` | — | a hard layering barrier — everything below is above everything above. `Hud` calls it ten times in all; this is the one that separates the overlays from the rest, and there is another inside the crosshair |
| 8 | hotbar | `Hud.extractItemHotbar` | yes | a camera player exists — replaced by `SpectatorGui.extractHotbar` in spectator mode |
| 9 | armour | `Hud.extractArmor` | yes | inside the health block, and `LivingEntity.getArmorValue` above zero |
| 10 | hearts | `Hud.extractHearts` | yes | inside the health block |
| 11 | food | `Hud.extractFood` | yes | inside the health block, **and** the vehicle contributes no hearts |
| 12 | air bubbles | `Hud.extractAirBubbles` | yes | inside the health block, and the player's eyes are in water or the air supply is below its maximum |
| 13 | mount health | `Hud.extractVehicleHealth` | yes | a ridden `LivingEntity` with a non-zero heart count — **outside** the health block, so creative shows it |
| 14 | contextual bar, background | `ContextualBar.extractBackground` | yes | always recorded, but which of four states it is in is re-decided every frame by `Hud.nextContextualInfoState` |
| 15 | experience level | `ContextualBar.extractExperienceLevel` | yes | the game mode has experience **and** the level is above zero — recorded between the bar's two passes, so it survives whichever bar wins |
| 16 | contextual bar, foreground | `ContextualBar.extractRenderState` | yes | always recorded; empty in `ContextualBar.EMPTY`, `ExperienceBar` and `JumpableVehicleBar`, so `LocatorBar` is the only one of the four states that draws anything here |
| 17 | selected item name | `Hud.extractSelectedItemName` | yes | not a spectator, `Hud.toolHighlightTimer` above zero, and the stack is not empty |
| 18 | status effects | `Hud.extractEffects` | yes | the player has effects, no screen is showing them itself, and the instance sets `MobEffectInstance.showIcon` |
| 19 | boss bars | `BossHealthOverlay` | yes | the overlay has events |
| 20 | **sleep fade** | `Hud.extractSleepOverlay` | **no** | `Player.getSleepTimer` above zero — the one element between the two hidden-gated blocks |
| 21 | demo text | `Hud.extractDemoOverlay` | yes | `Minecraft.isDemo` |
| 22 | scoreboard sidebar | `Hud.displayScoreboardSidebar` | yes | a display objective for the team's colour slot, else for `DisplaySlot.SIDEBAR` |
| 23 | action bar | `Hud.extractOverlayMessage` | yes | `Hud.overlayMessageString` is set and its timer has not run out |
| 24 | title and subtitle | `Hud.extractTitle` | yes | `Hud.title` is set and `Hud.titleTime` is above zero |
| 25 | chat | `ChatComponent.extractRenderState` | yes | a player exists and the chat *screen* is not focused |
| 26 | tab list | `PlayerTabOverlay.extractRenderState` | yes | `Options.keyPlayerList` is down, and either this is not a local server, or more than one player is listed, or a `DisplaySlot.LIST` objective exists |
| 27 | subtitles | `SubtitleOverlay` | `Options.showSubtitles` is on and something audible is playing | deferred when there is no screen or the screen declares itself in-game UI — and recorded even while hidden, if a screen declaring itself in-game UI is up |
The four elements a reader expects at the end of that list are not on
`Hud.extractRenderState`'s list at all — three of them are still `Hud` methods,
and only the toasts are outside `Hud`. `Gui.extractRenderState` records them,
after the overlay or screen:
| # | element | its own condition | hidden by F1? |
|---:|---|---|---|
| 28 | saving indicator | `Options.showAutosaveIndicator` is on, the frame is drawing a level, and a save is still animating | **no** |
| 29 | toasts | resources are loaded | checks the flag itself |
| 30 | debug overlay | the current screen is not `DebugOptionsScreen` | checks the flag itself |
| 31 | deferred subtitles | row 27 deferred them — but only when no screen is up: a screen draws them itself from `Screen.extractBackground`, below its own widgets, and this call then finds nothing left | — |
Two consequences worth carrying away. Toasts and the debug overlay are always
**above** a screen, because `Gui` records them after it. And the deferred
subtitles are called from a screen's *background* pass, so they land under the
screen's widgets rather than over them.
---
*Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes `tools/verify_names.py`.*
---
# Submit phases and feature renderers
> Verified against **Minecraft 26.2** · Reference · Hand-kept from
> `SubmitNodeCollection` and the `FeatureRenderDispatcher` constructor.
Everything an entity, block entity, particle or debug renderer draws in a
level arrives as a *submit node* — the sky, the clouds, the weather and the
world border are drawn by their own renderers in their own frame-graph passes,
and terrain by its chunk sections.
`SubmitNodeCollection` sorts those nodes into fifteen named phases as they
come in, and thirteen feature renderers turn them into vertices. The lecture
that frames both is [entity rendering](../systems/rendering/entity-rendering.md),
which names four of the phases and one of the renderers. Three more renderers
are named elsewhere — `TextFeatureRenderer` and `NameTagFeatureRenderer` on
[text and fonts](../systems/client/text-and-fonts.md), `QuadParticleFeatureRenderer`
on [particles](../systems/rendering/particles.md) — and everything else here is
only here.
## The fifteen phases
In **declaration order**, which is also the order of
`SubmitNodeCollection.allPhases`. It is *not* the order they are drawn in, and
neither is the last column: `FeatureRenderDispatcher.PreparedFrame.executeTranslucent`
alone makes three separate sweeps over every order bucket, so the phases it
drains are numbered by sweep — but *within* a sweep the order is the one the
sweep's own statements run in, not the order of the rows. Sweep 1 drains
shadows, then translucent models, then see-through name tags, then name tags,
then texts, then translucent custom geometry — so a see-through name tag is
drawn **before** the opaque one, which is the row order reversed.
Three phases are a `TranslucentFeatureRenderPhase` and the other twelve a
`SimpleFeatureRenderPhase`. The simple phase groups its nodes by feature type
and then by batch key — which only two of the thirteen submit kinds have, a
model and a piece of custom geometry, everything else grouping by adjacency —
and leaves `RenderTypeFeatureRenderer.Group` free to fold a node's geometry
into **any** earlier draw of the same render type, not only the adjacent one.
The translucent phase keeps every node, sorts them back to front by squared
distance to the camera, and marks the group strictly ordered. That does not
stop merging: consecutive submits of one render type still share a draw, on a
test that never consults the flag. What it stops is the fold into a
*non-adjacent* earlier draw — the one merge that would move geometry ahead of
the draws between it and its target, and so undo the sort. A render type whose
primitives are chained opts out of both, through
`RenderType.canConsolidateConsecutiveGeometry`.
| # | phase | what lands in it | drained by |
|---:|---|---|---|
| 1 | `SubmitNodeCollection.solid` | the opaque default: models and block models whose `RenderType` does not blend, moving blocks whose model does not declare the translucent material flag, items with no translucent quad, custom geometry that neither blends nor outlines, the opaque half of every quad-particle group, plus every flame and every leash | `.executeSolid` |
| 2 | `SubmitNodeCollection.shadows` | one node per `SubmitNodeCollection.submitShadow`, carrying the radius and the `EntityRenderState.ShadowPiece` list sampled at extract | `.executeTranslucent`, sweep 1 |
| 3 | `SubmitNodeCollection.nameTags` | every name tag gets a node here, see-through or not — a see-through one lands with an emission bump on its light, opaque white, and no background | `.executeTranslucent`, sweep 1 |
| 4 | `SubmitNodeCollection.seeThroughNameTags` | the *second* node a see-through name tag emits, in `Font.DisplayMode.SEE_THROUGH` with the background restored | `.executeTranslucent`, sweep 1 |
| 5 | `SubmitNodeCollection.texts` | world-space text that is not a name tag, from `SubmitNodeCollection.submitText` | `.executeTranslucent`, sweep 1 |
| 6 | `SubmitNodeCollection.shapeOutlines` | `VoxelShape` edge outlines submitted **without** the after-terrain flag | `.executeTranslucent`, sweep 2 |
| 7 | `SubmitNodeCollection.translucentBlocksAndItems` | items with a translucent quad, block models whose render type blends, and moving blocks whose model declares the translucent material flag | `.executeTranslucent`, sweep 3 |
| 8 | `SubmitNodeCollection.translucentModels` | entity models whose `RenderType` blends | `.executeTranslucent`, sweep 1 |
| 9 | `SubmitNodeCollection.translucentCustomGeometry` | custom geometry whose `RenderType` blends — a simple phase in spite of the name, so it is not distance-sorted | `.executeTranslucent`, sweep 1 |
| 10 | `SubmitNodeCollection.gizmos` | debug primitive groups submitted **without** the on-top flag | `.executeTranslucent`, sweep 2 |
| 11 | `SubmitNodeCollection.breakingOverlay` | the crumbling decal: a model submitted with a `ModelFeatureRenderer.CrumblingOverlay` whose render type admits one, and every `SubmitNodeCollection.submitBreakingBlockModel` | `.executeTranslucent`, sweep 3 |
| 12 | `SubmitNodeCollection.waterMask` | models submitted with the water-mask render type | `.executeTranslucent`, sweep 3 |
| 13 | `SubmitNodeCollection.afterTerrain` | outlines flagged after-terrain, plus the translucent half of every quad-particle group | `.executeTranslucentAfterTerrain` |
| 14 | `SubmitNodeCollection.alwaysOnTop` | gizmo groups flagged on top | `.executeAlwaysOnTop` |
| 15 | `SubmitNodeCollection.outline` | a second copy of a model, block model, moving block or item whose outline colour was non-zero — a model or block model only where its render type has an outline variant, and a moving block or item with its ordinary type, re-typed inside the feature renderer or dropped there — plus custom geometry, which is not a second copy at all: an outline render type routes the *only* copy here | `.executeOutline`, which `FeatureRenderDispatcher.renderAllFeatures` never calls — `LevelRenderer` does, into its own target |
Two rows are worth reading twice. A quad-particle group is submitted **once**
and lands in two phases at once, *solid* and *afterTerrain*, with a flag that
picks which of its layers each half draws. And *outline* is not simply *solid*
submitted twice: the glow is a second submission for a model, a block model, a
moving block or
an item, but flames, leashes and quad particles never reach it at all, a
blending model pairs its outline copy with *translucentModels* rather than
*solid*, and custom geometry goes to one phase or the other and never both.
## The thirteen feature renderers
In the order `FeatureRenderDispatcher`'s constructor registers them. All but
one extend `RenderTypeFeatureRenderer`, which owns the shared
`StagedVertexBuffer` draw and the merging of consecutive same-render-type
geometry.
| feature renderer | what it writes |
|---|---|
| `ShadowFeatureRenderer` | four vertices per `EntityRenderState.ShadowPiece` onto one shared shadow render type, with the piece's alpha as the colour and UVs derived from its bounds and the shadow radius |
| `FlameFeatureRenderer` | a stack of fire quads scaled to the entity's bounding box, alternating two block-atlas sprites and flipping their U every other pair, at full block light |
| `ModelFeatureRenderer` | the entity models: `Model.setupAnim` and then `Model.renderToBuffer` over the `ModelPart` tree, into a buffer optionally wrapped for a sheeted decal or a single sprite |
| `NameTagFeatureRenderer` | the glyph quads and background of a name tag, prepared through `Font` at the submitted pose and display mode |
| `TextFeatureRenderer` | arbitrary world-space text, with an eight-direction outline pass and a polygon-offset second pass when an outline colour is set |
| `LeashFeatureRenderer` | a ribbon between the two ends of an `EntityRenderState.LeashState`, walked twice — twenty-four steps out and twenty-four back for its two faces, a hundred vertices in all — with its light interpolated between the endpoints and its colour alternating per step |
| `ItemFeatureRenderer` | item quads, in two passes over the same submits — geometry first, then the enchantment foil for every submit that has one |
| `CustomFeatureRenderer` | nothing of its own: it hands the caller's `SubmitNodeCollector.CustomGeometryRenderer` a vertex consumer for the requested render type |
| `BlockModelFeatureRenderer` | the quads of a `BlockStateModelPart` list, in `Direction` order, at one fixed light and overlay coordinate for the whole submit |
| `MovingBlockFeatureRenderer` | a whole block state re-tesselated through `ModelBlockRenderer` — pistons and falling blocks — honouring the ambient-occlusion and cutout-leaves options |
| `QuadParticleFeatureRenderer` | the only one outside `RenderTypeFeatureRenderer`: it appends particle draws to the `StagedVertexBuffer` per `SingleQuadParticle.Layer` and opens its own `RenderPass` to issue them |
| `ShapeOutlineFeatureRenderer` | two line vertices per edge of a `VoxelShape`, each carrying a per-vertex line width |
| `GizmoFeatureRenderer` | the debug vocabulary: quads, triangle fans, lines, texts and points out of a `DrawableGizmoPrimitives.Group`, camera-relative |
That list is the answer to *what can be drawn in a level*. Anything a mod or a
renderer wants that is not one of the other twelve has to go through
`CustomFeatureRenderer`.
---
*Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes `tools/verify_names.py`.*
---
# Density-function nodes
> Verified against **Minecraft 26.2** · Reference · the thirty-four node types a
> *worldgen/density_function* file may name, what each one takes, and what the
> per-chunk rewrite turns it into.
[Density functions](../systems/worldgen/density-functions.md) is the lecture:
three forms of one graph, two rewrites, and the six caches. This is the
catalogue behind it — the table you would pause the video to read.
`DensityFunctions.bootstrap` registers every entry below into
`BuiltInRegistries.DENSITY_FUNCTION_TYPE`, in this order, under the
*minecraft* namespace. That registry is **built-in and frozen at startup**,
which is why adding a new *kind* of node takes code while adding a new graph
takes a JSON file.
**34** — registered node types (`DensityFunctions.bootstrap`): four by name,
then six markers, nine more by name, seven mapped transforms, four
arithmetic, and four last.
## The table
*children* counts the density-function slots; each one accepts an id string,
an inline object or a bare number, because every child slot is typed
`DensityFunction.CODEC`.
| id | class | children | other fields | what it computes |
|---|---|---:|---|---|
| *blend_alpha* | `DensityFunctions.BlendAlpha` | 0 | — | constant 1.0 as data — a placeholder the chunk swaps out |
| *blend_offset* | `DensityFunctions.BlendOffset` | 0 | — | constant 0.0 as data — likewise a placeholder |
| *beardifier* | `DensityFunctions.BeardifierMarker` | 0 | — | constant 0.0 as data — the structure-terrain placeholder |
| *old_blended_noise* | `BlendedNoise` | 0 | *xz_scale*, *y_scale*, *xz_factor*, *y_factor*, *smear_scale_multiplier* | the pre-1.18 terrain noise, decoded unseeded |
| *interpolated* | `DensityFunctions.Marker` | 1 | — | delegates — requests cell-corner interpolation |
| *flat_cache* | `DensityFunctions.Marker` | 1 | — | delegates — requests a quart-resolution 2-D cache |
| *cache_2d* | `DensityFunctions.Marker` | 1 | — | delegates — requests a one-entry XZ memo |
| *cache_once* | `DensityFunctions.Marker` | 1 | — | delegates — requests reuse within one interpolation step |
| *cache_all_in_cell* | `DensityFunctions.Marker` | 1 | — | delegates — requests a whole-cell block cache |
| *blend_density* | `DensityFunctions.Marker` | 1 | — | delegates — requests old-terrain density blending |
| *noise* | `DensityFunctions.Noise` | 0 | *noise*, *xz_scale*, *y_scale* | samples a `NormalNoise` at the scaled position |
| *end_islands* | `DensityFunctions.EndIslandDensityFunction` | 0 | — | the End's simplex island field, as a density |
| *shifted_noise* | `DensityFunctions.ShiftedNoise` | 3 | *noise*, *xz_scale*, *y_scale* | samples noise at position × scale plus three offsets |
| *range_choice* | `DensityFunctions.RangeChoice` | 3 | *min_inclusive*, *max_exclusive* | one of two branches, by whether the input is in range |
| *interval_select* | `DensityFunctions.IntervalSelect` | 1 + a list | *thresholds* | the branch whose ascending threshold the input first falls below |
| *shift_a* | `DensityFunctions.ShiftA` | 0 | *argument* (a noise) | domain warp read at x, 0, z |
| *shift_b* | `DensityFunctions.ShiftB` | 0 | *argument* (a noise) | domain warp read at z, x, 0 |
| *shift* | `DensityFunctions.Shift` | 0 | *argument* (a noise) | domain warp read at x, y, z |
| *clamp* | `DensityFunctions.Clamp` | 1 | *min*, *max* | the child, clamped |
| *abs* | `DensityFunctions.Mapped` | 1 | — | absolute value |
| *square* | `DensityFunctions.Mapped` | 1 | — | the child squared |
| *cube* | `DensityFunctions.Mapped` | 1 | — | the child cubed |
| *half_negative* | `DensityFunctions.Mapped` | 1 | — | identity above zero, halved below |
| *quarter_negative* | `DensityFunctions.Mapped` | 1 | — | identity above zero, quartered below |
| *invert* | `DensityFunctions.Mapped` | 1 | — | the reciprocal |
| *squeeze* | `DensityFunctions.Mapped` | 1 | — | clamp to ±1, then a soft odd cubic |
| *add* | `DensityFunctions.Ap2` or `DensityFunctions.MulOrAdd` | 2 | — | the sum |
| *mul* | `DensityFunctions.Ap2` or `DensityFunctions.MulOrAdd` | 2 | — | the product, short-circuiting on an exact zero |
| *min* | `DensityFunctions.Ap2` | 2 | — | the minimum, skipping the second child when the first is already below its bound |
| *max* | `DensityFunctions.Ap2` | 2 | — | the maximum, with the symmetric skip |
| *spline* | `DensityFunctions.Spline` | inside the spline | *spline* | a `CubicSpline` whose coordinates are themselves density functions |
| *constant* | `DensityFunctions.Constant` | 0 | *argument* | a fixed value |
| *y_clamped_gradient* | `DensityFunctions.YClampedGradient` | 0 | *from_y*, *to_y*, *from_value*, *to_value* | block Y mapped onto a value range |
| *find_top_surface* | `DensityFunctions.FindTopSurface` | 2 | *lower_bound*, *cell_height* | steps down in strides until the density goes positive, and returns that **Y** |
**Where one class serves several ids.** The six markers are all
`DensityFunctions.Marker`, a record of a `DensityFunctions.Marker.Type` and a
wrapped function; the seven transforms are all `DensityFunctions.Mapped`; the
four arithmetic ids share `DensityFunctions.TwoArgumentSimpleFunction`. In
each case the *enum constant* carries its own codec, and the node's *codec()*
returns its type's — which is how a re-serialised graph comes back with the
right id. `DensityFunctions.MulOrAdd` is the specialisation
`DensityFunctions.TwoArgumentSimpleFunction.create` picks when the id is
*add* or *mul* and one argument folded to a `DensityFunctions.Constant`, so
*add* in the JSON may come back as either class.
## What the caches become
`NoiseChunk.wrapNew` is the per-chunk rewrite. A marker is a *request*; this
is what is installed instead. All six replacements implement
`DensityFunctions.MarkerOrMarked`, so they still report their marker type and
would re-serialise unchanged.
| marker type | installed | keyed on |
|---|---|---|
| `DensityFunctions.Marker.Type.Interpolated` | `NoiseChunk.NoiseInterpolator` | nothing — two slices of cell-corner values, and eight corners loaded per cell. Serves a foreign context by delegating to the wrapped function; only a sample whose context *is* the `NoiseChunk` throws outside the loop |
| `DensityFunctions.Marker.Type.FlatCache` | `NoiseChunk.FlatCache` | **position**, at quart resolution: one array entry per 4×4 block column group, filled at construction |
| `DensityFunctions.Marker.Type.Cache2D` | `NoiseChunk.Cache2D` | **position**, one entry — the packed XZ of the last sample |
| `DensityFunctions.Marker.Type.CacheOnce` | `NoiseChunk.CacheOnce` | **a counter** — `NoiseChunk.interpolationCounter` for the scalar, a second counter for the array form |
| `DensityFunctions.Marker.Type.CacheAllInCell` | `NoiseChunk.CacheAllInCell` | **the cell** — one array entry per block in the cell, Y stored inverted |
| `DensityFunctions.Marker.Type.BlendDensity` | `NoiseChunk.BlendDensity`, **or nothing at all** if the level's `Blender` is empty, in which case the marker is replaced by its own child | not cached |
The same rewrite resolves three singletons by object identity:
`DensityFunctions.BlendAlpha` and `DensityFunctions.BlendOffset` become flat
caches the `NoiseChunk` constructor has *already filled* (or survive as the
constants 1.0 and 0.0 when there is no blending to do), and
`DensityFunctions.BeardifierMarker` becomes this chunk's `Beardifier`. And
`DensityFunctions.HolderHolder` — the in-memory stand-in for an id reference,
which is not registered and has no codec — is resolved to its value once
instead of on every sample.
## Bounds
Every node answers `DensityFunction.minValue` and `DensityFunction.maxValue`
without a position. The arithmetic family — the two-argument nodes, the
mapped ones and *clamp* — stores its bounds as record components filled once
at construction, and so does `BlendedNoise`; a few answer with literals of
their own and the rest delegate to their input or walk their list again on
each call. The rules worth knowing:
The arithmetic bounds are **sign-aware** and eager: *mul* takes the four
cross products and picks by the signs of the operands' ends, and *min* and
*max* take the element-wise minimum and maximum of the ends. Building a *min*
or a *max* over two ranges that cannot overlap logs a warning and proceeds.
`DensityFunctions.Mapped.create` transforms the child's two endpoints, with
*abs* and *square* clamping the minimum up to zero and *invert* reporting
**±infinity** whenever the child's range straddles zero. *clamp* is the clearest of the nodes
whose bounds are not derived from a child: its record components are
literally named *minValue* and *maxValue*, so the codec's *min* and *max*
fields *are* the interface's bound methods. *shifted_noise* takes its bounds
from the noise and ignores all three of its children, and *blend_density*
reports infinity whatever its child says.
Three nodes report bounds that are not densities or not final.
`DensityFunctions.Marker` passes its child's bounds through except when its
type is `DensityFunctions.Marker.Type.BlendDensity`, where it reports ±infinity — the one place a marker
is not transparent. `DensityFunctions.HolderHolder` reports ±infinity while
its holder is unbound, which is what lets forward references parse. And
`DensityFunctions.FindTopSurface` reports its *lower bound* and its upper
bound's maximum, which are **Y coordinates** — this node's range is on a
different scale from every other node in the table.
One more, on the unseeded graph: `DensityFunction.NoiseHolder` answers a
maximum of 2.0 while its `NormalNoise` is still null. Every one of the
sixty-three shipped noise definitions computes a maximum between 2.57 and 7.32
once seeded, so a freshly parsed router reports noise bounds that are too
**narrow**, and seeding widens them.
## What vanilla actually uses
Thirty-five JSON files ship under *worldgen/density_function* — four at the
top level plus the per-dimension directories — and between them they use
twenty-five of the thirty-four ids. Five more appear only inline, in the
seven `Registries.NOISE_SETTINGS` files: *blend_density* and *squeeze* in all
seven, and *square*, *invert* and *find_top_surface* in the three overworld
variants.
That leaves four ids vanilla data never writes. *constant* is never written
as a typed object, because a bare number is one. *cache_all_in_cell* and
*beardifier* are added **in code**, by `NoiseChunk`'s constructor, around the
router's final density. And *shift* — the three-dimensional domain warp — is
used by nothing: `DensityFunctions.shift` has no callers anywhere in the
decompile, and no shipped file names the id. `DensityFunctions.ShiftA` and
`DensityFunctions.ShiftB` cover the two two-dimensional warps vanilla wants.
---
*Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes `tools/verify_names.py`.*
---
# Threads
> Verified against **Minecraft 26.2** · Reference · Hand-kept from `net/minecraft/util/thread` and every `Thread` the game starts, beside [Anatomy](../systems/anatomy/anatomy.md)'s four — looked up, not watched.
Every thread the game creates, who creates it, what runs on it, and what
is *allowed* to run on it. The last column is the rule the rest of the
documentation leans on: game state belongs to exactly one thread, and
anything else submits a task to that thread's event loop.
## The picture
Two threads own game state — the Render thread owns the client's, the
Server thread owns the world's — and everything else is a way of getting
work to them or from them. Work crosses a thread boundary in exactly three
ways, and the figure labels each edge with which: a **posted task** (a
`Runnable` on the owner's `BlockableEventLoop`), a **completed future** (a
worker's result, completed onto the owner's executor), or a **hopped
handler** (a packet decoded on Netty and re-posted to its owner by
`PacketUtils.ensureRunningOnSameThread`).
```mermaid
flowchart LR
subgraph ClientSide["the client process"]
RT["Render thread
Minecraft.runTick: a frame, then 0 to 10 client ticks"]
SND["Sound engine
SoundEngineExecutor: the OpenAL calls"]
end
subgraph Shared["shared by both halves"]
NET["Netty IO
Connection: split, decode, encode, plus the handshake and login handlers"]
WK["Worker-Main-n
Util.backgroundExecutor: generation, lighting, meshing"]
end
subgraph ServerSide["the server"]
ST["Server thread
MinecraftServer.runServer: a tick every 50 ms"]
IO["IO-Worker-n
Util.ioPool: region file reads and writes"]
WD["Server Watchdog
dedicated only"]
LST["Console, RCON, query, management
dedicated only"]
end
RT -- "serverbound packets, written on the caller's thread" --> NET
NET -- "clientbound play packets: hopped handler" --> RT
NET -- "serverbound play packets: hopped handler" --> ST
ST -- "clientbound packets" --> NET
ST -- "chunk generation and lighting: posted task" --> WK
WK -- "a generated chunk: completed future" --> ST
RT -- "section meshing: posted task" --> WK
WK -- "a built mesh: completed future" --> RT
ST -- "region reads and writes: posted task" --> IO
IO -- "a loaded chunk's data: completed future" --> ST
RT -- "play, stop, move a source: posted task" --> SND
LST -- "a command line: posted task" --> ST
WD -. "reads tick state unsynchronised, kills the JVM past max-tick-time" .-> ST
```
The table is the figure's rows. Netty is drawn once and shared because
it is: in singleplayer the client's `Connection` and the integrated
server's run on the same `Netty Local IO` threads, and the packets between
them are real.
## The threads a lecture leans on
| thread | made by | runs | may touch |
|---|---|---|---|
| **Render thread** (client) | the JVM main thread, renamed in `client/main/Main` | `Minecraft.run` → `Minecraft.runTick` once per frame; `Minecraft.tick` 0–10 times inside it | Everything client-side: `ClientLevel`, `LocalPlayer`, the GPU (`RenderSystem.assertOnRenderThread`), screens, options. It is also the client's event loop (`Minecraft` is a `ReentrantBlockableEventLoop`), so packet handlers run here after `PacketUtils.ensureRunningOnSameThread`. |
| **Server thread** | `MinecraftServer.spin` | `MinecraftServer.runServer` → `MinecraftServer.processPacketsAndTick` every `TickRateManager.nanosecondsPerTick` (50 ms by default); `MinecraftServer.waitUntilNextTick` drains the task queue in the slack | Every `ServerLevel`, every chunk, entity and block entity, the `PlayerList`. Serverbound *play* packet handlers run here, not on Netty. One per server; singleplayer has exactly one. |
| **Netty IO** (`Netty NIO IO #n`, `Netty Epoll IO #n`, `Netty Kqueue IO #n`, `Netty Local IO #n`) | `EventLoopGroupHolder` | the `Connection` pipeline: split, decrypt, decompress, decode; encode, compress, encrypt — **and the handshake and login handlers** | Bytes and `Packet` objects — plus, in handshake and login, the handlers themselves: `ServerHandshakePacketListenerImpl` and `ServerLoginPacketListenerImpl` never hop. The login *state machine* is not all theirs, though: `ServerLoginPacketListenerImpl` is a `TickablePacketListener`, so the Server thread advances it once a tick through `MinecraftServer.tickConnection`. A *play* handler that needs game state re-posts to the owning thread. *Local* is the in-process channel of singleplayer. |
| **Worker-Main-n** | `Util.backgroundExecutor` — a `ForkJoinPool` sized to the JDK's available-processor count minus one, clamped by `Util.maxAllowedExecutorThreads` and capped by the *max.bg.threads* property (`Util.getMaxThreads`) | chunk generation and lighting via `ChunkTaskDispatcher`; section meshing via `SectionRenderDispatcher`; resource-reload *prepare* phases; chunk serialisation | Its own inputs. Results return to the owning thread as a `CompletableFuture` completed onto that thread's executor. Never `Level` state directly. |
| **IO-Worker-n** | `Util.ioPool` | region file reads and writes through `IOWorker`, one `PriorityConsecutiveExecutor` per storage so writes to one file stay ordered | Files. `Util.nonCriticalIoPool` (`Download-n`) is the same shape for downloads, telemetry and sound decoding. |
| **Sound engine** | `SoundEngineExecutor` | a `BlockableEventLoop` that owns the per-source OpenAL calls | The `SoundEngine`'s channels; see [the sound engine](../systems/client/sound-engine.md). Device open/close and buffer deletion stay on the Render thread. |
| **Server Watchdog** | `DedicatedServer.initServer` (dedicated only, positive limit only) | `ServerWatchdog` | Writes nothing, but *reads* game state unsynchronised while the Server thread is mid-tick — the game rules and every level's `ServerLevel.getWatchdogStats`, for the crash report. It kills the JVM past `DedicatedServerProperties.maxTickTime`, and that kill does **not** save the world. |
| **Server console handler** | `DedicatedServer.initServer` | reads stdin | Queues each line to the server thread as a command; runs nothing itself. |
| **RCON Listener #n** / **RCON Client** | `RconThread` from `DedicatedServer.initServer`, when *enable-rcon* **and** *rcon.password* is set | accepts RCON sockets; one `RconClient` thread per connection | Sockets. Each command is queued to the server thread. Dedicated only, and **non-daemon** — `DedicatedServer.onServerExit` must stop it or the JVM will not exit. |
| **Query Listener #n** | `QueryThreadGs4` from `DedicatedServer.initServer`, when *enable-query* | the GS4 query protocol | Its own cached status. Dedicated only, and **non-daemon**, like RCON. |
| **Management server IO #n** | `ManagementServer`, built by `JsonRpc` in `server/Main` | a second, independent Netty event-loop group: the JSON-RPC/WebSocket management API and its heartbeat | Its own pipeline; management calls reach the game through the server's task queue. Dedicated only. |
| Timer hack thread | `Util.startTimerHackThread` | sleeps forever | Nothing. Keeps the JVM's timer resolution high by existing. |
## The nine client handlers that never hop
`Connection.channelRead0` calls a packet's handler on the Netty thread, and
a handler's first line is normally `PacketUtils.ensureRunningOnSameThread`,
which re-posts it to the owning thread and aborts
([the connection](../systems/networking/the-connection.md)). Nine handlers on the client's play listener omit it, so they run to completion
on Netty and must touch nothing the Render thread owns. Seven are declared in
`ClientPacketListener` itself; the last two are inherited from
`ClientCommonPacketListenerImpl` and are the two that matter most, because a
keep-alive is answered and a disconnect is acted on without the game thread
being involved at all.
| handler | what it does on the Netty thread |
|---|---|
| `ClientPacketListener.handlePlayerCombatEnter` | nothing — the body is empty |
| `ClientPacketListener.handlePlayerCombatEnd` | nothing — the body is empty |
| `ClientPacketListener.handleChunkBatchStart` | starts the `ChunkBatchSizeCalculator`'s clock |
| `ClientPacketListener.handleChunkBatchFinished` | stops it and sends `ServerboundChunkBatchReceivedPacket` with the chunks-per-tick it now wants — so the loop in [what the client is told](../systems/networking/what-the-client-is-told.md) times packet decode, not mesh building |
| `ClientPacketListener.handleDebugSample` | hands the sample to `DebugScreenOverlay.logRemoteSample` |
| `ClientPacketListener.handlePongResponse` | records the round trip in `PingDebugMonitor` |
| `ClientPacketListener.handleLowDiskSpaceWarning` | calls `Minecraft.sendLowDiskSpaceWarning`, which posts the toast to the Render thread itself — the one that crosses after all, by `Minecraft.execute` rather than by the hop |
| `ClientCommonPacketListenerImpl.handleKeepAlive` | replies with `ServerboundKeepAlivePacket` through `ClientCommonPacketListenerImpl.sendWhen`, deferred while the window is frozen at `RenderSystem.isFrozenAtPollEvents` — so the answer that keeps a connection alive never waits for a frame |
| `ClientCommonPacketListenerImpl.handleDisconnect` | calls `Connection.disconnect` straight from the event loop |
## Situational threads
Real, but nothing in the corpus hangs on them: *User Authenticator* (one per
login, for the session-server call), *Chat-Filter-Worker*, *Server Pinger* and
*Server Connector* (the multiplayer screen), *Telemetry-Sender*,
`LanServerPinger` and its detector, *World Upgrader*, *Datafixer Bootstrap*
(priority 1, so it yields to everything), the client and server shutdown
hooks with `ClientShutdownWatchdog` behind them, Swing's event dispatch
thread when a dedicated server is started without *--nogui* and runs its
`MinecraftServerGui`, the *Friends List* fetcher behind the social screen,
and `ChaseServer`'s two threads and `ChaseClient`'s one, which exist only
behind `SharedConstants.DEBUG_CHASE_COMMAND` and the */chase* command it
registers. Realms starts nine more, and is out of scope with the rest of
*com/mojang/realmsclient*.
## The rules that follow
- **Two owners, one wire.** Client state is the Render thread's; server state
is the Server thread's; in singleplayer they share a JVM and still only
talk about the *world* through packets over the local channel. (Settings —
pause, view distance, publishing — do cross by direct call; see Anatomy.)
- **Handlers hop.** A play packet is decoded on Netty and *handled* on the
owning game thread; `PacketUtils.ensureRunningOnSameThread` is the hop.
Handshake and login are the exception: their handlers run to completion on
Netty. The login state machine is still advanced from the Server thread,
which ticks the listener once a tick.
- **Workers compute, owners commit.** Chunk generation, lighting and meshing
produce results on the worker pool; only the owning thread installs them.
- **Waiting drains.** An owning thread never blocks idle: `BlockableEventLoop.managedBlock`
keeps running its own queue while it waits on a future, which is why a
server tick that waits for a chunk does not deadlock the chunk that needs
the server tick. `ServerChunkCache.MainThreadExecutor` is the extra event
loop that makes that work.
---
# Math and primitives
> Verified against **Minecraft 26.2** · Reference · The coordinate spaces, geometry and randomness every system page assumes — looked up, not watched.
Every system in the game speaks in a handful of value types: an integer
block position, a chunk column, a 16³ section, a double-precision world
position, a direction, a box, a collision shape, a random source. They are
the types the codebase reaches for most — `BlockPos` alone has 1,221
importers, more than any other Minecraft class — and most of what is confusing
about "which coordinate is this"
is answered by knowing which type a method takes.
## The coordinate spaces
Six integer spaces and one double one, and every conversion is a named method
— which is the figure: the spaces as nodes, the
conversions as the edges between them, and the three that also pack to a
long key.
```mermaid
flowchart LR
V["Vec3: a double world position"] -- "BlockPos.containing, which floors" --> B["BlockPos: one block, int"]
B -- "Vec3.atCenterOf, atLowerCornerOf, atBottomCenterOf" --> V
B -- "ChunkPos.containing, shift 4" --> C["ChunkPos: a 16-block column"]
C -- "ChunkPos.getMinBlockX, getWorldPosition" --> B
B -- "SectionPos.of, blockToSectionCoord, shift 4" --> S["SectionPos: a 16-cubed section"]
S -- "sectionToBlockCoord, sectionRelative masks 15" --> B
C -- "SectionPos.of, with a section y" --> S
B -- "QuartPos.fromBlock, shift 2" --> Q["QuartPos: a 4-block biome cell"]
Q -- "QuartPos.toBlock" --> B
S -- "QuartPos.fromSection" --> Q
Q -- "QuartPos.toSection" --> S
C -- "ChunkPos.getRegionX, getRegionLocalX, shift 5" --> R["region: 32 chunks, one .mca file"]
R -- "ChunkPos.minFromRegion" --> C
B -- "GlobalPos.of, plus a Level key" --> G["GlobalPos: a dimension and a block"]
B -- "BlockPos.asLong: 26-bit x and z, 12-bit y" --> L["long keys"]
S -- "SectionPos.asLong: 22-bit x and z, 20-bit y" --> L
C -- "ChunkPos.pack: 32 and 32" --> L
```
| space | unit | type | owner / notes | conversions |
|---|---|---|---|---|
| **block** | 1 block, int | `BlockPos` (extends `Vec3i`) | immutable; `BlockPos.MutableBlockPos` for loops | `BlockPos.containing` floors a double position; `BlockPos.asLong` packs to a long and `BlockPos.of` unpacks one |
| **world position** | 1 block, double | `Vec3` (implements `Position`) | entity positions, ray casts, velocities | `Vec3.atCenterOf`, `Vec3.atLowerCornerOf`, `Vec3.atBottomCenterOf` from a `Vec3i`; `Vec3.directionFromRotation` from pitch/yaw |
| **chunk column** | 16 blocks | `ChunkPos` — a **record** of x and z | the key of every chunk map | `ChunkPos.containing` from a `BlockPos`; `ChunkPos.pack` / `ChunkPos.unpack`; `ChunkPos.getMinBlockX`, `ChunkPos.getWorldPosition` |
| **section** | 16³ cube | `SectionPos` (extends `Vec3i`) | lighting, entity sections, render sections | `SectionPos.of` from block/chunk/entity; `SectionPos.blockToSectionCoord` (shift 4), `SectionPos.sectionToBlockCoord`, `SectionPos.sectionRelative` (mask 15); `SectionPos.asLong` |
| **quart / biome** | 4 blocks | `QuartPos` (static only) | biome sampling | `QuartPos.fromBlock` (shift 2), `QuartPos.toBlock`, `QuartPos.fromSection`, `QuartPos.toSection` |
| **region** | 32 chunks | none — methods on `ChunkPos` | the `.mca` file grid | `ChunkPos.getRegionX`, `ChunkPos.getRegionLocalX`, `ChunkPos.minFromRegion`; `ChunkPos.REGION_SIZE` |
| **dimension-qualified block** | — | `GlobalPos` — record of a `Level` key and a `BlockPos` | compass targets, beds, portals | `GlobalPos.of` |
| **integer box** | blocks | `BoundingBox` for structures; `BlockBox` is a newer record that nothing yet uses | structure bounds, piece placement | `BoundingBox.intersects`, `BoundingBox.encapsulate`; `BlockBox.aabb`, `BlockBox.contains` |
| **double box** | blocks | `AABB` (a class of six public final doubles) | entity bounding boxes, block shapes' bounds | `AABB.move`, `AABB.inflate`, `AABB.intersects`, `AABB.clip` |
| **pitch / yaw** | degrees, float | `Vec2` | look direction, as (xRot, yRot) — except the *Rotation* NBT tag, which stores it the other way round | `Direction.fromYRot`, `Direction.toYRot`; `Vec3.xRot`, `Vec3.yRot` |
| **pose rotation** | degrees, float ×3 | `Rotations` | `ArmorStand` poses, and nothing else | — |
| **model / render space** | float | JOML `Vector3f`, `Matrix4f`, `Quaternionf` (external) | everything under `client/renderer` | `Vec3.toVector3f`; `Direction.step`, `Direction.getRotation`; `com/mojang/math` `Axis` builds quaternions |
`Position` is the three-double interface `Vec3` implements. `Vec3i` is the
mutable-under-the-hood int triple with the arithmetic (`Vec3i.offset`,
`Vec3i.relative`, `Vec3i.distSqr`, `Vec3i.distManhattan`); `BlockPos` adds
the iteration helpers (`BlockPos.betweenClosed`, `BlockPos.withinManhattan`,
`BlockPos.spiralAround`), each of which walks a single reused
`BlockPos.MutableBlockPos` rather than allocating.
`BlockPos.breadthFirstTraversal` is the exception in the same class: it is not
an iterator at all, and allocates a queue of nodes and a set of visited longs.
`Cursor3D` is a *different* allocation-free box cursor — the one that also
classifies each position as inside, face, edge or corner
(`Cursor3D.TYPE_INSIDE`, `Cursor3D.TYPE_FACE`, `Cursor3D.TYPE_EDGE`,
`Cursor3D.TYPE_CORNER`) — and it is used by `SectionPos`, `BlockCollisions`
and `ClientLevel`, not by `BlockPos`.
Colours are a primitive too, and they live in `net/minecraft/util`: `ARGB` is
where every pack, unpack, lerp, multiply and alpha helper is, with
`CommonColors` for the named constants, `ColorRGBA` for the codec-friendly
value and `Brightness` for the packed block/sky light pair.
## Three long keys
Three long-packings appear everywhere as map keys.
- **`BlockPos.asLong`** — 26 bits X, 26 bits Z, 12 bits Y, high to low.
`BlockPos.PACKED_HORIZONTAL_LENGTH` is literally derived from the world
border's 30,000,000, which is why it is 26
(`BlockPos.MAX_HORIZONTAL_COORDINATE` is 33,554,431); the remaining
`BlockPos.PACKED_Y_LENGTH` is 12, giving −2048 to 2047. The *usable*
range is narrower: `DimensionType` reserves a 32-block margin, so
`DimensionType.MIN_Y` is −2032, `DimensionType.MAX_Y` is 2031 and
`DimensionType.Y_SIZE` is 4064. `BlockPos.getFlatIndex` masks off the low
four Y bits — it snaps a packed position to the bottom of its own
16-block section, which is the skylight walk-up idiom, not a per-column
key (the top eight Y bits survive). `BlockPos.STREAM_CODEC` sends the
packed long; `Vec3i.STREAM_CODEC` sends three varints.
- **`SectionPos.asLong`** — 22 bits X, 22 bits Z, 20 bits Y. A section-relative
position packs into a short (`SectionPos.sectionRelativePos`).
- **`ChunkPos.pack`** — X in the low 32 bits, Z in the high 32.
`ChunkPos.INVALID_CHUNK_POS` is a sentinel; `ChunkPos.isValid` is bounded
by `ChunkPyramid.MAX_CHUNK_COORDINATE_VALUE`, which lives with the chunk
status pyramid because the safety margin is derived from how many
neighbours generation reads.
## Directions and symmetry
`Direction` is the six-valued enum in 3D-data order `DOWN, UP, NORTH, SOUTH,
WEST, EAST` (`Direction.get3DDataValue`); its horizontal subset has its own
order starting at south (`Direction.get2DDataValue`). `Direction.Axis` (X, Y, Z),
`Direction.AxisDirection` and `Direction.Plane` (horizontal, vertical) are
the nested helpers; `Direction8` the compass points; `FrontAndTop` the twelve
jigsaw and crafter orientations; `AxisCycle` the axis permutation
`VoxelShape` lookups use.
Block rotation is `Rotation` and `Mirror` (`world/level/block`), each of
which maps onto `OctahedralGroup` (`com/mojang/math`) — the 48-element
symmetry group of a cube, each element a permutation plus three inversion
flags. That group is not decoration: `Shapes.rotate`, `Shapes.rotateAll`,
`Shapes.rotateHorizontal` and `Shapes.rotateAttachFace` are how a block
declares one shape and is handed the rest — three more from
`Shapes.rotateHorizontal`, five from `Shapes.rotateAll`, and eleven from
`Shapes.rotateAttachFace`, which is `Shapes.rotateHorizontal` run once per
`AttachFace`. `Transformation` wraps a JOML
matrix and lazily decomposes it into translation, left rotation, scale and
right rotation for model JSON.
There are two things called `Axis`: `Direction.Axis` and the quaternion
factory in `com/mojang/math`. They are unrelated.
## Shapes and collision
A `VoxelShape` is a set of boxes on a per-axis coordinate grid, backed by a
`DiscreteVoxelShape` bit grid (`BitSetDiscreteVoxelShape`). `Shapes` is the
factory and algebra: `Shapes.block`, `Shapes.empty`, `Shapes.box`,
`Shapes.or`, `Shapes.join` with a `BooleanOp`, `Shapes.joinIsNotEmpty`,
`Shapes.collide`, `Shapes.blockOccludes`, with `Shapes.EPSILON` and
`Shapes.BIG_EPSILON` the tolerances every comparison uses. Implementations
differ by how the grid is stored — `CubeVoxelShape` (even divisions),
`ArrayVoxelShape` (explicit coordinate lists), `SliceShape` (a
one-cell-thick view, used for face culling and occlusion) — and `Shapes.join`
picks an `IndexMerger` strategy per axis, returning a `CubeVoxelShape` only
when all three merge evenly.
Shape queries are cheap because they are mostly not computed:
`BlockBehaviour.BlockStateBase.initCache` builds a
`BlockBehaviour.BlockStateBase.Cache` per block state holding the collision
shape, the large-collision shape, whether the collision shape is a full block,
and a per-face sturdiness array — but **only for a block whose shape is not
dynamic**, and the occlusion shape is not in it. That one is a field on
`BlockBehaviour.BlockStateBase` itself, built whether the cache is or not, and
a dynamic-shape block answers every collision query live.
`CollisionContext` is what a shape query knows about who is asking:
`CollisionContext.of` an entity (`EntityCollisionContext` — descending,
bottom Y, held item, whether fluids collide), `CollisionContext.empty`,
`CollisionContext.placementContext` — all three of which are
`EntityCollisionContext`s, as is the `MinecartCollisionContext`
`CollisionContext.of` returns for a minecart under the experimental movement
flag. The one case that is neither is `PositionCollisionContext`, from
`CollisionContext.positionContext`. Ray casts
return a `HitResult`: `BlockHitResult` (position, face, inside,
world-border) or `EntityHitResult`.
## Two random families, and two that are neither
`RandomSource` (`net/minecraft/util`) is the interface. Most implementations
live in `world/level/levelgen`, and the legacy family shares `BitRandomSource`,
which defines `RandomSource.nextInt` and friends on top of a raw bit generator;
`XoroshiroRandomSource` implements `RandomSource` directly, and
`RandomSequences` keeps one more in `world/`. Two families coexist in one
process:
- **Legacy LCG** — `LegacyRandomSource` (the java.util.Random algorithm),
`SingleThreadedRandomSource` (same, no atomics), `ThreadSafeLegacyRandomSource`.
`RandomSource.create` returns a `LegacyRandomSource` with a uniquified
seed; this is `Level.random` (inherited unchanged by `ServerLevel` and
`ClientLevel`), `Entity.random`, `GameRenderer.random`,
`ParticleEngine.random`. `RandomSource.createThreadLocalInstance` returns a
`SingleThreadedRandomSource` and is what `ClientLevel.animateTick` uses for
block animation, and `LevelRenderer` for the block-destroy overlay.
- **Xoroshiro** — `XoroshiroRandomSource` (128-bit state via
`RandomSupport.Seed128bit`), the newer of the two and the one a noise
settings file gets unless it asks otherwise:
`NoiseGeneratorSettings.getRandomSource` returns
`WorldgenRandom.Algorithm.XOROSHIRO` unless the settings opt into legacy.
**Four of the seven shipped noise settings do opt in**, and two of them are
dimensions of an ordinary world — *nether* and *end*, alongside *caves* and
*floating_islands* — so most of a new world's generation is legacy, not
Xoroshiro.
`RandomState` forks it positionally (`PositionalRandomFactory.at`,
`PositionalRandomFactory.fromHashOf`) for the named noise consumers — the
aquifer and the ore placer each get their own deterministic stream from
the seed and position.
`WorldgenRandom` wraps any delegate and adds the seeding conventions —
`WorldgenRandom.setDecorationSeed`, `WorldgenRandom.setFeatureSeed`,
`WorldgenRandom.setLargeFeatureWithSalt`, `WorldgenRandom.seedSlimeChunk` —
that make a structure land in the same place for the same seed. Features go
through *those*, not through `RandomState`.
There is a third randomness path that is neither: `RandomSequence` and
`RandomSequences`, a saved, `Identifier`-keyed table of `XoroshiroRandomSource`
streams derived from the world seed, which is what makes a loot table and
`/random` reproducible across sessions. And a fourth that is not a
`RandomSource` at all: `LinearCongruentialGenerator`, the bare mixer
`BiomeManager` uses for biome fuzzing.
`RandomSource.nextGaussian` is produced by `MarsagliaPolarGaussian`, which
caches a spare value — which is why reseeding a source must reset it.
## `Mth` and `Util`
`Mth` is the maths grab-bag (677 importers): `Mth.floor`, `Mth.clamp`,
`Mth.lerp`, `Mth.wrapDegrees`, `Mth.rotLerp`, `Mth.smallestEncompassingPowerOfTwo`,
`Mth.log2`, `Mth.positiveModulo`, `Mth.hsvToRgb`. `Mth.sin` and `Mth.cos`
are lookups in a 65,536-entry table (`Mth.cos` is the same table with a
quarter-turn phase shift), and the table is filled from the JDK's ordinary
sine rather than its strict one — so the platform-dependent step, if
you are chasing animation determinism, is the table's construction and not
the lookup. `Util` (in `net/minecraft/util`, 454 importers) is where the
executors live — `Util.backgroundExecutor`, `Util.ioPool`,
`Util.nonCriticalIoPool` — along with time sources and collection helpers;
`Unit` is the single-valued "void" type codecs and futures use. The other
numeric odds and ends worth knowing by name are `CubicSpline` (terrain
shaping), `InclusiveRange`, and `BitStorage`, the packed-integer array
underneath every palette.
## What trips people up
**`ChunkPos` is a record with no *asLong*.** The names are `ChunkPos.pack`
and `ChunkPos.unpack`; construction from a block is `ChunkPos.containing`;
the components are accessed as x() and z().
**`Vec3i.toMutable` returns a JOML `Vector3i`,** not a
`BlockPos.MutableBlockPos`; the mutable block position is constructed
directly and its `BlockPos.MutableBlockPos.set` /
`BlockPos.MutableBlockPos.move` are the loop idiom.
**`BlockPos` is immutable, `Vec3i` only pretends to be.** `Vec3i` keeps
protected setters that `BlockPos.MutableBlockPos` uses; every other subclass
treats them as final. `BlockPos.immutable` is the copy to call before
storing a mutable one.
**`Level.random` deliberately crashes on cross-thread use.**
`LegacyRandomSource` holds an atomic seed not for safety but as a
*detector*: any concurrent use fails the compare-and-set and raises a
`ThreadingDetector` exception — both the reseed and every draw test it. The genuinely safe variant,
`ThreadSafeLegacyRandomSource`, and `RandomSource.createThreadSafe` are both
deprecated. Touching a level's random from a worker is meant to be loud.
**Tick randomness and worldgen randomness are different generators.** The
LCG drives every `Level` and `Entity`; the saved `RandomSequences` behind loot
and `/random` are Xoroshiro, and so is terrain wherever the noise settings have
not opted into legacy — which the nether and the end have.
`PositionalRandomFactory.parityConfigString` is implemented by **both**
families, so the parity dumps cover whichever one a dimension is on.
**`BlockBox` is declared and unused.** It is a tidy `BlockPos`-pair record
in `net/minecraft/core`, and in 26.2 nothing calls it; structure bounds are
still `BoundingBox` in `world/level/levelgen/structure`. Worth knowing
before assuming a rename happened.
**`Direction.getRotation` treats UP as identity**, and `Direction.step`
returns a fresh JOML vector while `Direction.getUnitVec3f` returns the read-
only shared one.
**`SectionPos` has `SectionPos.x` as both an instance and a static method,** instance and static, same name.
**`BlockUtil` is in `net/minecraft/util`, not `net/minecraft/core`;**
`BlockBox`, `BlockMath` and `Cursor3D` are in `net/minecraft/core`.
`BlockMath` is model-rotation plumbing, not coordinates.
## Where to look
`Vec3i` · `BlockPos` · `ChunkPos` · `SectionPos` · `QuartPos` · `GlobalPos` ·
`Cursor3D` · `Vec3` · `AABB` · `BoundingBox` · `Direction` · `Rotation` ·
`OctahedralGroup` · `VoxelShape` · `Shapes` · `CollisionContext` ·
`HitResult` · `ARGB` · `Mth` · `RandomSource` · `BitRandomSource` ·
`WorldgenRandom` · `PositionalRandomFactory` · `RandomSequences` · `Util`
---
*Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes `tools/verify_names.py`.*
---
# 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.dat* — `PrimaryLevelData` 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](#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](../systems/world/README.md) is where these things are used;
[the level tick](../systems/server/server-level-tick.md) is where most of
them are read.
## Who owns what
| datum | owner | saved as | told to the client by |
|---|---|---|---|
| seed, structures, bonus chest, dimension list | `WorldGenSettings` (`MinecraftServer.getWorldGenSettings`) | *data/minecraft/world_gen_settings.dat* | the obfuscated seed in `CommonPlayerSpawnInfo`, the dimension list in `ClientboundLoginPacket`; structures and the bonus chest, nothing |
| world spawn | `PrimaryLevelData.respawnData` | *level.dat* | `ClientboundSetDefaultSpawnPositionPacket` |
| game time | `PrimaryLevelData.gameTime` (shared by every level) | *level.dat* | `ClientboundSetTimePacket` |
| day time | `ServerClockManager` | *data/minecraft/world_clocks.dat* | `ClientboundSetTimePacket` |
| difficulty, lock, hardcore | `LevelSettings.DifficultySettings` | *level.dat* | `ClientboundChangeDifficultyPacket` — hardcore alone rides in `ClientboundLoginPacket` |
| game type, allow-commands, name, data packs | `LevelSettings` | *level.dat* | the 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 rules | `GameRuleMap` via `GameRules` | *data/minecraft/game_rules.dat* | five rules only, plus `ClientboundGameRuleValuesPacket` on request |
| weather | `WeatherData` | *data/minecraft/weather.dat* | `ClientboundGameEventPacket` |
| world border | `WorldBorder`, one per `ServerLevel` | *dimensions/minecraft/…/data/minecraft/world_border.dat* | `ClientboundInitializeBorderPacket` and the five `ClientboundSetBorder…` packets |
| scoreboard | `ServerScoreboard` (`MinecraftServer.scoreboard`), buffered by `ScoreboardSaveData` at save time | *data/minecraft/scoreboard.dat* | `ClientboundSetObjectivePacket`, `ClientboundSetScorePacket`, `ClientboundSetPlayerTeamPacket` |
| maps | `MapItemSavedData` per `MapId`, `MapIndex` for the counter | *data/minecraft/maps/\.dat*, *data/minecraft/maps/last_id.dat* | `ClientboundMapItemDataPacket` |
| raids | `Raids` per level | *dimensions/minecraft/…/data/minecraft/raids.dat* | boss bars |
| chunk tickets | `TicketStorage` per level ([tickets](../systems/world/tickets-and-loading.md)) | *dimensions/minecraft/…/data/minecraft/chunk_tickets.dat* | — |
| dragon fight | `EnderDragonFight`, where `DimensionType.hasEnderDragonFight` | *dimensions/minecraft/…/data/minecraft/ender_dragon_fight.dat* | boss bars |
| boss bars, scheduled functions, random sequences, stopwatches, trader timers, command storage | `CustomBossEvents`, `TimerQueue`, `RandomSequences`, `Stopwatches`, `WanderingTraderData`, `CommandStorage` | *data/minecraft/\.dat*, and one *data/\/command_storage.dat* per namespace | boss bars only |
| player data | `PlayerDataStorage` | *players/data/\.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](../systems/server/server-tick.md)) —
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.saveDataTag`
→ `LevelStorageSource.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 `.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/\/\.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/\/command_storage.dat*.
There are two storages. `MinecraftServer.savedDataStorage`
(`MinecraftServer.getDataStorage`) is server-global at *\/data/*;
maps, scoreboard, rules, weather and clocks are server-wide.
`ServerChunkCache.savedDataStorage` (`ServerChunkCache.getDataStorage`,
forwarded by `ServerLevel.getDataStorage`) is per dimension at
*dimensions/\/\/data/* — the overworld included
([chunk storage](../systems/world/chunk-storage.md)); 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 `GameRuleCategory` — `GameRuleCategory.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](gamerules.md). The 1.21 names
(*doDaylightCycle*, *doMobSpawning*) and the *GameRules.BooleanValue* /
*IntegerValue* / *Key* classes are gone.
The values are saved data, not level data: a `GameRuleMap` — `SavedData`,
*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](../systems/world/environment-attributes-and-timelines.md)).
`MinecraftServer.updateMobSpawningFlags` sends nothing; it only calls
`Level.setSpawnSettings`, which forwards to `ServerChunkCache.setSpawnSettings`.
New is an in-game editor: `ServerboundClientCommandPacket.Action.REQUEST_GAMERULE_VALUES`
→ `ServerGamePacketListenerImpl.sendGameRuleValues` →
`ClientboundGameRuleValuesPacket` → `InWorldGameRulesScreen`, and edits
back as `ServerboundSetGameRulePacket` → `ServerGamePacketListenerImpl.handleSetGameRule`
(gated on `Permissions.COMMANDS_GAMEMASTER`).
## The border is per dimension
`WorldBorder` is `SavedData` — *world_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.BorderExtent` — `WorldBorder.StaticBorderExtent`
or `WorldBorder.MovingBorderExtent`, which `WorldBorder.tick` advances
([the level tick](../systems/server/server-level-tick.md)). 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](../systems/foundations/identifiers-and-registries.md));
`Level.OVERWORLD`, `Level.NETHER`, `Level.END` are `ResourceKey`s 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 `WorldGenSettings` — `SavedData`,
*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](../systems/worldgen/creating-a-world.md) 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.PEACEFUL` … `Difficulty.HARD`) sits in
`LevelSettings.DifficultySettings`. `MinecraftServer.setDifficulty` writes
it, `MinecraftServer.updateMobSpawningFlags`, and
`MinecraftServer.sendDifficultyUpdate` → `ClientboundChangeDifficultyPacket`.
`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](../systems/world/environment-attributes-and-timelines.md)).
`WeatherData` — server-global `SavedData`, `MinecraftServer.getWeatherData`
— was covered in [the level tick](../systems/server/server-level-tick.md).
`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`.*
---
# Naming drift
> Verified against **Minecraft 26.2** · Reference · The translation
> layer: every name a 1.21-era reader will reach for that 26.2 does not have,
> and what it is called now.
Rule three of this corpus is *newest version only*: no page says "in 1.21
this was…", because version-difference prose is the first thing to rot and
the last thing anyone rereads. That rule costs something, and this page is
where the cost is paid back once. Every page assumes you are reading the
26.2 tree; this page assumes you are not, yet, and are still typing the
names you learned somewhere else.
Two audiences. A reader coming from **1.21** — the version most public
writing, most tutorials and most model weights are anchored to — needs the
first table: the old name on the left, what to grep for on the right. A
reader coming from **Yarn** (Fabric's community mappings) needs the last
one: not a version difference at all, just a different name for the same
class in the same version.
The one sentence: *if a name in your head does not appear in the tree, it
is probably on this page.*
## How to read the tables
The left column is **italic, not backticked**. Most of these names do not
exist in 26.2, and `tools/verify_names.py` — which checks every backticked
identifier on every page against the decompile — would reject the page if
they were. Italics is the corpus's mark for *a name, but not a 26.2 name*.
The right column is backticked and therefore verified: those names are in
the tree.
"gone" in the right column means exactly that: there is no replacement
class, the responsibility moved into something structurally different, and
the entry names where it went. Those are the interesting rows — a rename is
a nuisance, a disappearance is a design change, and the page named beside
each part explains it.
Every row here was found the same way: a fact-sheet agent reading the 26.2
decompile went looking for a name it expected and did not find it. The
table is therefore *not* exhaustive — it is exhaustive over the names the
corpus needed. Two hundred and forty-three rows, and the distribution is
itself a finding: the three biggest tables are **commands** (36), **the
server** (31) and **items** (30), and the fourth is **rendering** (27). The
client was rewritten around extract-then-render, which is why almost nothing
at the top of the render stack kept its name — but the permission rewrite and
the game-rule registry moved more names than the renderer did.
## The four you will hit in the first ten minutes
`Identifier` is *ResourceLocation*. `Lightmap` is *LightTexture*.
`DeltaTracker` is *Timer*, and `partialTick` is now a `DeltaTracker.Timer`
you ask rather than a float you are handed. And `Gui` no longer means the
HUD: the HUD is `Hud`, held as `Gui.hud`, while `Gui` is the screen and
overlay manager that also owns `Gui.screen` and `Gui.setScreen` — so a
1.21-era `Minecraft.setScreen` call site is now on `Gui`. Both `Gui` and
`Hud` exist, which is the single most confusing pair of names in the tree.
## The tables
### Everywhere
| the name you remember | 26.2 |
|---|---|
| *ResourceLocation* | `Identifier` |
| *LightTexture* | `Lightmap` |
| *Timer* | `DeltaTracker` |
### Part II — Foundations
| the name you remember | 26.2 |
|---|---|
| *TagManager* | gone |
| *Minecraft.reloadResources* | `Minecraft.reloadResourcePacks` |
| *ItemStack.save* / *parse* | `ValueOutput` / `ItemStack.CODEC` |
| *ChunkPos.asLong* | `ChunkPos.pack` / `ChunkPos.unpack` (a record) |
### Part III — The server
| the name you remember | 26.2 |
|---|---|
| *DO_DAYLIGHT_CYCLE* | `GameRules.ADVANCE_TIME` |
| *DO_MOB_SPAWNING* | `GameRules.SPAWN_MOBS` |
| *DO_WEATHER_CYCLE* | `GameRules.ADVANCE_WEATHER` |
| *GameRules* package | `world/level/gamerules` |
| *GameRules.Key<T>* / *GameRules.Value* / *BooleanValue* / *IntegerValue* / *GameRules.Type* (all nested) | top-level `GameRule`, with `GameRuleType`, `GameRuleTypeVisitor`, `GameRuleMap` for the values and `GameRuleCategory` for the grouping |
| game rules as a hard-coded map | a **registry** — `Registries.GAME_RULE` / `BuiltInRegistries.GAME_RULE`, bootstrapped by `GameRules` |
| *level.dat* field *GameRules*, ids camelCase and unnamespaced | field *game_rules*, ids namespaced (*minecraft:advance_time*) — the whole rename table is `GameRuleRegistryFix` |
| *doEntityDrops* | `GameRules.ENTITY_DROPS` |
| *doImmediateRespawn* | `GameRules.IMMEDIATE_RESPAWN` |
| *doInsomnia* | `GameRules.SPAWN_PHANTOMS` |
| *doLimitedCrafting* | `GameRules.LIMITED_CRAFTING` — the name survives elsewhere, as a component of `ClientboundLoginPacket` and a field on `LocalPlayer` |
| *doPatrolSpawning* / *doTraderSpawning* / *doWardenSpawning* | `GameRules.SPAWN_PATROLS` / `GameRules.SPAWN_WANDERING_TRADERS` / `GameRules.SPAWN_WARDENS` |
| *doVinesSpread* | `GameRules.SPREAD_VINES` |
| *enableCommandBlocks* **and** *commandBlocksEnabled* | one rule, `GameRules.COMMAND_BLOCKS_WORK` |
| *spawnerBlocksEnabled* | `GameRules.SPAWNER_BLOCKS_WORK` |
| *commandModificationBlockLimit* | `GameRules.MAX_BLOCK_MODIFICATIONS` |
| *minecartMaxSpeed* | `GameRules.MAX_MINECART_SPEED` |
| *snowAccumulationHeight* | `GameRules.MAX_SNOW_ACCUMULATION_HEIGHT` |
| *spawnRadius* | `GameRules.RESPAWN_RADIUS` |
| *disableElytraMovementCheck* | `GameRules.ELYTRA_MOVEMENT_CHECK` — **inverted** |
| *disablePlayerMovementCheck* | `GameRules.PLAYER_MOVEMENT_CHECK` — **inverted** |
| *disableRaids* | `GameRules.RAIDS` — **inverted** |
| *doFireTick* + *allowFireTicksAwayFromPlayer* (two booleans) | one integer, `GameRules.FIRE_SPREAD_RADIUS_AROUND_PLAYER` (0 none, 128 near players only, −1 everywhere) |
| *spawnChunkRadius*, *entitiesWithPassengersCanUsePortals*, *gameLoopFunction* | gone with no replacement — the fix simply deletes them |
| day time on *ServerLevel* | `ServerClockManager` (`world/clock`) |
| per-level weather | server-global `WeatherData` |
| *GameProfile* on the player lists | `NameAndId` (a record of UUID and name) — `PlayerList.canPlayerLogin`, `PlayerList.isWhiteListed`, `PlayerList.op`, the ban/op/whitelist files |
| *ServerPlayer.sendAllPlayerInfo* / *sendActivePlayerEffects* | `PlayerList.sendAllPlayerInfo` / `PlayerList.sendActivePlayerEffects` |
| *MinecraftServer.getScheduledEvents* returning a per-level queue | the same name, returning a server-wide `TimerQueue` saved data, advanced only by the overworld's `ServerLevel.tickTime` |
| *ServerLevel.updateSkyBrightness* reading day time | the method survives, declared on `Level`, and now reads `EnvironmentAttributes.SKY_LIGHT_LEVEL` through `EnvironmentAttributeSystem` |
| *ChunkMap.forEachBlockTickingChunk* meaning block-ticking | it walks the **entity**-ticking set; the name did not follow the split |
### Part IV — The world
| the name you remember | 26.2 |
|---|---|
| *ChunkStorage* | gone — `ChunkMap extends SimpleRegionStorage` |
| *DimensionDataStorage* | `SavedDataStorage` (two of them) |
| *getLightBlock* | `BlockBehaviour.BlockStateBase.getLightDampening` |
| *PalettedContainer.Strategy* | top-level `Strategy` + `Configuration` |
| *ForcedChunksSavedData* | `TicketStorage` |
| *TicketType<T>* | a registry record with flag bits |
| *DimensionType* booleans | `EnvironmentAttributeMap` |
| *DimensionType.ultraWarm* | split four ways: `EnvironmentAttributes.FAST_LAVA`, `EnvironmentAttributes.WATER_EVAPORATES`, `EnvironmentAttributes.INCREASED_FIRE_BURNOUT`, `EnvironmentAttributes.SNOW_GOLEM_MELTS` |
| *DimensionType.piglinSafe* | `EnvironmentAttributes.PIGLINS_ZOMBIFY` — **inverted** |
| *DimensionType.bedWorks* | `EnvironmentAttributes.BED_RULE`, a `BedRule` record, not a boolean |
| *DimensionType.hasRaids* | `EnvironmentAttributes.CAN_START_RAID` |
| *DimensionType.natural* | `EnvironmentAttributes.NETHER_PORTAL_SPAWNS_PIGLINS` and neighbours |
| *DimensionType.fixedTime* | `DimensionType.hasFixedTime`, a bare boolean — the time itself moved to `WorldClock` and `Timelines.OVERWORLD_DAY` |
| *DimensionType.ambientLight* | unchanged; one of the three visual fields that did not become an attribute, with `DimensionType.skybox` and `DimensionType.cardinalLightType` |
| *Schedule* (the villager's) | `EnvironmentAttributes.VILLAGER_ACTIVITY` on `Timelines.VILLAGER_SCHEDULE` |
| *Level.dayTime* | `ServerClockManager`, keyed by `WorldClock` |
| *data/<id>.dat* | *data/<namespace>/<id>.dat* — every saved-data file gained a namespace folder |
### Part V — Blocks
| the name you remember | 26.2 |
|---|---|
| *ItemInteractionResult* | gone — `InteractionResult.TryEmptyHandInteraction` |
| *DirectionProperty* | gone — `EnumProperty` |
| *Level.markAndNotifyBlock* | gone — inline in `Level.setBlock` |
| *BlockBehaviour.onRemove* | `BlockBehaviour.affectNeighborsAfterRemoval` + `BlockEntity.preRemoveSideEffects` |
| *doTileDrops* | `GameRules.BLOCK_DROPS` |
| *BlockModelShaper* | `BlockStateModelSet` / `BlockModelSet` |
| *RenderShape.ENTITYBLOCK_ANIMATED* | gone — `RenderShape.INVISIBLE` / `RenderShape.MODEL` only |
| *Player.canInteractWithBlock* | `Player.isWithinBlockInteractionRange` |
| *Block.rebuildCache* | gone — `BlockBehaviour.BlockStateBase.initCache` from the `Blocks` static init |
| *Material* | gone — individual `BlockBehaviour.Properties` flags |
| *BlockEntity.saveToItem* | `BlockItem.setBlockEntityData` + `BlockEntity.collectComponents` |
| *MobEffects.DIG_SPEED* / *DIG_SLOWDOWN* | `MobEffects.HASTE` / `MobEffects.MINING_FATIGUE` |
### Part VI — Entities
| the name you remember | 26.2 |
|---|---|
| *Player extends LivingEntity* | `Player extends Avatar extends LivingEntity` |
| *EntityType.PIG* (constants) | `EntityTypes.PIG` + `EntityTypeIds.PIG` |
| *MobSpawnType* | `EntitySpawnReason` (+ `EntitySpawnRequest`) |
| *SpawnPlacements.Type* | `SpawnPlacementType` / `SpawnPlacementTypes` |
| *Entity.hurt(DamageSource, float)* returning a boolean | split into `Entity.hurtServer` and `Entity.hurtClient`. Both old shapes survive as deprecated finals — `Entity.hurt` delegating to the server half, `Entity.hurtOrSimulate` as the boolean-returning successor — so grep still finds the name |
| *doMobLoot* | `GameRules.MOB_DROPS` |
| *LivingEntity.isDamageSourceBlocked* | gone — `DataComponents.BLOCKS_ATTACKS` |
| *Schedule* / *ScheduleBuilder* | gone — `Timeline` + `EnvironmentAttribute` |
| *BlockPathTypes* | `PathType` |
| *Mob.brainProvider* | `LivingEntity.makeBrain(Brain.Packed)` |
| *Entity.moveTo* / *absMoveTo* | `Entity.snapTo` / `Entity.absSnapTo` |
| *Entity.maxUpStep* (field) | `Attributes.STEP_HEIGHT` |
| *Entity.updateFluidHeightAndDoFluidPushing* | `EntityFluidInteraction` |
| *Entity.lerpTo* | `Entity.moveOrInterpolateTo` + `InterpolationHandler` |
| *EntityDataSerializers.OPTIONAL_UUID* / *COMPOUND_TAG* | gone |
| UUID-keyed *AttributeModifier* | `Identifier`-keyed record |
| *AttributeMap.getDirtyAttributes* | `AttributeMap.getAttributesToSync` + `AttributeMap.getAttributesToUpdate` |
| *PlayerRenderer* | `AvatarRenderer` (serves players and mannequins, keyed by skin model) |
### Part VII — Items and inventories
| the name you remember | 26.2 |
|---|---|
| *InteractionResultHolder* | gone — `InteractionResult.Success.heldItemTransformedTo` |
| *UseAnim* | `ItemUseAnimation` |
| *Item.getFoodProperties* | `DataComponents.FOOD` on the stack |
| *ItemStack.getTag* / *getOrCreateTag* | gone — components |
| *LivingEntity.triggerItemUseEffects* | `Consumable.emitParticlesAndSounds` |
| *FoodProperties* effects list | `Consumable.onConsumeEffects` |
| *ClickType* | `ContainerInput` |
| *MultiPlayerGameMode.handleInventoryMouseClick* | `MultiPlayerGameMode.handleContainerInput` |
| *ClientboundSetCarriedItemPacket* | split: `ClientboundSetCursorItemPacket` + `ClientboundSetHeldSlotPacket` |
| *ClientboundSetSlotPacket* | `ClientboundContainerSetSlotPacket` |
| *ClientboundHorseScreenOpenPacket* | `ClientboundMountScreenOpenPacket` |
| *Container.startOpen(Player)* | `Container.startOpen(ContainerUser)` |
| *Recipe.getResultItem* / *getIngredients* | `Recipe.assemble` / `PlacementInfo`. The first is gone outright; the second is off the `Recipe` interface and survives only as a test-visible method on `ShapedRecipe` |
| *Ingredient.EMPTY* | gone — `Ingredient.CODEC` rejects an empty literal list, but a tag that resolves to nothing still yields an empty one, hence `Ingredient.isEmpty` |
| *ClientboundUpdateRecipesPacket* carrying recipes | property sets + the stonecutter input set; the book gets `RecipeDisplayEntry`s |
| *net.minecraft.advancements.CriteriaTriggers* | `CriteriaTriggers`, moved to `net/minecraft/advancements/triggers` |
| *Player.permissionLevel* / *hasPermissions(int)* | `Player.permissions` → a `PermissionSet`, queried by named `Permissions` keys |
| *ServerboundPlayerCommandPacket.Action.PRESS_SHIFT_KEY* / *RELEASE_SHIFT_KEY* | gone — sneak rides `ServerboundPlayerInputPacket` → `Entity.setShiftKeyDown` |
| *Mannequin* on the client | `ClientMannequin`, installed by swapping the mutable `Mannequin.constructor` factory at client startup |
| *data/<ns>/recipes/* | `data//recipe/` (singular) |
| *EnchantmentCategory* | `Enchantment.EnchantmentDefinition` item sets |
| *Enchantment.getDamageBonus*, *EnchantmentHelper.getFireAspect*… | gone — `EnchantmentEffectComponents` |
| *EnchantedBookItem* | gone — `DataComponents.STORED_ENCHANTMENTS` |
| *Item.getEnchantmentValue* | `DataComponents.ENCHANTABLE` |
| *LootContextParam* / *LootContextParamSet* | `ContextKey` / `ContextKeySet` (`util/context`) |
| *LootDataManager* / *LootTables* | `ReloadableServerRegistries` + `BuiltInLootTables` |
| *LootTableReference* | `NestedLootTable` |
| *LootingEnchantFunction* | `EnchantedCountIncreaseFunction` |
| *SetCountFunction* | `SetItemCountFunction` |
| *LootContextParams.KILLER_ENTITY* | `LootContextParams.ATTACKING_ENTITY` |
### Part VIII — The player
| the name you remember | 26.2 |
|---|---|
| *Inventory.armor* / *offhand* / *compartments* | one 36-slot `Inventory.items` + `Inventory.EQUIPMENT_SLOT_MAPPING` |
| *Inventory.setPickedItem* | `Inventory.addAndPickItem` / `Inventory.pickSlot` |
| *Entity.moveTo* | `Entity.absSnapTo` / `Entity.snapTo` |
| *GameRenderer.pick* | `Minecraft.pick` → `LocalPlayer.raycastHitResult` |
| *ServerboundInteractPacket.Action.ATTACK* | `ServerboundAttackPacket` (a record of one int) |
| *GameRules.NATURAL_REGENERATION* | `GameRules.NATURAL_HEALTH_REGENERATION` |
| *isCritArrow* / *Player.sweepAttack* | `Player.canCriticalAttack` / `Player.isSweepAttack` + `Player.doSweepAttack`, all three private. *isCritArrow* was never a `Player` method and is still live on `AbstractArrow` |
| *LivingEntity.eat* / *Player.eat* | gone — `Consumable.onConsume` → `FoodProperties` → `FoodData.eat` |
| *MobEffect.createModifier* | `MobEffect.createModifiers` (plural) |
### Part IX — Networking
| the name you remember | 26.2 |
|---|---|
| *Connection.setListener* / *setProtocol* / *getCurrentProtocol* | gone — `Connection.setupInboundProtocol` / `Connection.setupOutboundProtocol` |
| *ConnectionProtocol.getById* / packet tables | gone — a bare enum; ids are `ProtocolInfoBuilder.addPacket` order in `IdDispatchCodec` |
| *Connection.NETWORK_WORKER_GROUP* etc. | `EventLoopGroupHolder` (in `server/network`) |
| *MemoryConnection* | gone — `Connection.isMemoryConnection` |
| *ensureRunningOnSameThread(…, BlockableEventLoop)* | `PacketUtils.ensureRunningOnSameThread` with a `PacketProcessor` |
| *Packet.write(FriendlyByteBuf)* | gone — a per-packet `StreamCodec` constant the protocol reads |
| *ClientboundAddPlayerPacket* / *ClientboundAddMobPacket* | gone — `ClientboundAddEntityPacket` |
| *ClientboundUpdateViewPositionPacket* | `ClientboundSetChunkCacheCenterPacket` |
| *ClientboundUpdateViewDistancePacket* | `ClientboundSetChunkCacheRadiusPacket` |
| *ClientboundLevelChunkPacket* | `ClientboundLevelChunkWithLightPacket` |
| routine *ClientboundTeleportEntityPacket* | `ClientboundEntityPositionSyncPacket` |
| *ClientboundGameProfilePacket* | `ClientboundLoginFinishedPacket` (+ a session id) |
| *ServerboundLoginStartPacket* | `ServerboundHelloPacket` |
| *ClientboundEncryptionRequestPacket* / response | `ClientboundHelloPacket` / `ServerboundKeyPacket` |
| *ClientboundSetCompressionPacket* | `ClientboundLoginCompressionPacket` |
| *ClientboundResourcePackPacket* | `ClientboundResourcePackPushPacket` / `…PopPacket` |
| *MinecraftServer.getSessionService* | `MinecraftServer.services` |
| *PlayerChunkSender* in *server/level* | `server/network` |
| *Component.Serializer* (Gson) | `ComponentSerialization` (codecs; NBT on the wire) |
| *TextComponent* / *TranslatableComponent* / … | `network/chat/contents` — `PlainTextContents` etc. |
| *ComponentUtils.updateForEntity* | `ComponentUtils.resolve` with a `ResolutionContext` |
| *SignedMessageHeader* / *MessageSigner* | `SignedMessageLink` / `SignedMessageChain.Encoder` |
| *ChatPreview* and its packets | gone |
| *ClientboundSetTimePacket(gameTime, dayTime, …)* | a game time plus a `WorldClock` update map |
### Part X — The client
| the name you remember | 26.2 |
|---|---|
| *Gui* (the HUD) | `Hud`, held as `Gui.hud`; the name `Gui` now means the screen/overlay manager |
| *Minecraft.screen* / *Minecraft.setScreen* | `Gui.screen` / `Gui.setScreen` |
| *GuiGraphics* | `GuiGraphicsExtractor` (records states; does not draw) |
| *Screen.render* / every *render** on *Gui* | `Screen.extractRenderState` / every *extract\** on `Hud` |
| *LayeredDraw* | call order plus `GuiRenderState.nextStratum` |
| *Options.hideGui* | `Hud.isHidden`, published as `GuiRenderState.isHudHidden` |
| *Minecraft.getPartialTick*, *Timer* | `DeltaTracker.Timer` and its three questions |
| *Minecraft.destroy* | gone — `Minecraft.stop`, then `Minecraft.exitWorldAndClose` and `Minecraft.close` |
| *Options.keyBindings* | `Options.keyMappings`; `KeyMapping.Category` is a registrable record, not a string |
| *Options.mouseSensitivity* | the field is `Options.sensitivity` with an accessor of that name; *mouseSensitivity* survives only as the key in *options.txt* |
| *MouseHandler.lastMouseEventTime* | gone |
| raw *(key, scancode, modifiers, action)* on every `Screen` method | the `client/input` records: `KeyEvent`, `MouseButtonEvent`, `CharacterEvent`, `PreeditEvent` |
| *ClientChunkCache.ChunkArray* | `ClientChunkCache.Storage` |
| *Font.drawInBatch* and every *drawString* variant | `Font.prepareText` → `Font.PreparedText`; the drawing verbs are on `GuiGraphicsExtractor` |
| *Font.StringRenderOutput* | `Font.PreparedText` plus `Font.GlyphVisitor` |
| *BakedGlyph* (a class) | an interface; the sheet implementation is `BakedSheetGlyph`, effects are `EffectGlyph` |
| *RawGlyph* / *SheetGlyphInfo* | `UnbakedGlyph` (info and bake) and `GlyphBitmap` (pixels and upload) |
| *GlyphProviderBuilder* / *GlyphProviderBuilderType* | `GlyphProviderDefinition` / `GlyphProviderType` |
| *Style.withFont* taking an id | still `Style.withFont`, but the type is `FontDescription`, which may be a sprite rather than a font |
| *FontSet.getGlyph* as public API | private — `FontSet.source` then `GlyphSource.getGlyph` |
### Part XI — Rendering
Twenty-seven rows, and almost all of them are one refactor: extract then
render.
| the name you remember | 26.2 |
|---|---|
| *MultiBufferSource* / *BufferSource* | `SubmitNodeCollector` / `SubmitNodeStorage` / `FeatureRenderDispatcher` |
| *ShaderInstance*, *RenderStateShard* | `RenderPipeline` + `RenderPipelines` + `BindGroupLayouts` |
| *VertexBuffer*, *Tesselator*, *BufferUploader* | `GpuBuffer` / `GpuBufferSlice`, `ByteBufferBuilder` → `MeshData`, `UberGpuBuffer` |
| *RenderSystem.setShader* / *enableBlend* / *depthMask* … | fields of a `RenderPipeline` |
| *VertexFormat.Mode*, *VertexFormat.IndexType*, *TextureFormat* | `PrimitiveTopology`, `IndexType`, `GpuFormat` |
| *Window.updateDisplay*, vsync as a swap interval | `GpuSurface.present`, vsync as a `GpuSurface.PresentMode` |
| *LightTexture.pack* and friends | `LightCoordsUtil` |
| *DimensionSpecialEffects* | `DimensionType.skybox` + `EnvironmentAttributes` + `Timeline` |
| *FogParameters*, *RenderSystem.setShaderFogColor* | `FogData`, `RenderSystem.setShaderFog` (a uniform slice) |
| *Level.getSkyColor*, *ClientLevel.getStarBrightness*, *ClientLevel.effects* | `EnvironmentAttributeProbe.getValue` on an `EnvironmentAttribute` |
| *LevelRenderer.renderLevel* / *renderSky* / *renderChunkLayer* | `LevelRenderer.render` and the `LevelRenderer.addSkyPass` family of frame-graph passes |
| *LevelRenderer.blockChanged* / *setSectionDirty* / *allChanged* | the same names on `LevelExtractor` |
| *ChunkRenderDispatcher*, *RenderChunk*, *CompiledChunk* | `SectionRenderDispatcher`, its `SectionRenderDispatcher.RenderSection`, `CompiledSectionMesh` |
| *RenderType.chunkBufferLayers* (five layers) | `ChunkSectionLayer` — three layers |
| *BakedModel*, *ModelResourceLocation* | `BlockStateModel` / `ItemModel`; block models keyed by `BlockState` |
| *BlockModelShaper*, *ItemModelShaper*, *BlockRenderDispatcher*, *ItemRenderer* | `BlockStateModelSet`, `ItemModelResolver`, `ModelBlockRenderer` |
| *BlockElement* / *BlockElementFace*, *AtlasSet*, *ItemColors* | `CuboidModelElement` / `CuboidFace`, `AtlasManager`, `ItemTintSource` |
| *EntityRenderer.render*, *RenderLayer.render* | `EntityRenderer.extractRenderState` + `EntityRenderer.submit` |
| *TextureSheetParticle*, sheet *ParticleRenderType*s | `SingleQuadParticle` + `SingleQuadParticle.Layer` |
| *ParticleGroup* (a limit record) | `ParticleLimit`; `ParticleGroup` is now the per-render-type bucket |
| *Camera.setup* | `Camera.update` + `Camera.extractRenderState` |
| *RenderStateShard* composition (the texture/target/layering half) | `RenderType` over a `RenderPipeline`, catalogued in `RenderTypes`, built by `RenderSetup` |
| *BakedQuad* as four vertices | a ten-component record, with a `BakedQuad.MaterialInfo` of six |
| *LiquidBlockRenderer* | `FluidRenderer`, over a `FluidModel` |
| *ItemOverrides* / *getPropertyOverride* | `SelectItemModel` / `RangeSelectItemModel` / `ConditionalItemModel` |
| *ScreenManager* (the Blaze3D monitor manager) | `MonitorManager`, with `Monitor` and `VideoMode` — same package, same GLFW monitor callback |
| *Window.setVsync* | a `GpuSurface.PresentMode` in the surface configuration |
### Part XII — World generation
| the name you remember | 26.2 |
|---|---|
| *GenerationStep.Carving* | gone — `BiomeGenerationSettings.carvers` is one flat `HolderSet` |
| *DensityFunctions.WeirdScaledSampler* | `DensityFunctions.IntervalSelect` |
| *StructureFeature* / *ConfiguredStructureFeature* | `Structure` / `Registries.STRUCTURE` |
| *Feature.RANDOM_PATCH*, *Feature.FLOWER* | gone — composed from `Feature.SIMPLE_BLOCK` + placement |
| *Feature.POINTED_DRIPSTONE* / *DRIPSTONE_CLUSTER* | `Feature.SPELEOTHEM` / `Feature.SPELEOTHEM_CLUSTER` |
| *AbstractTreeGrower* and its subclasses | one final `TreeGrower` with constants |
| *TreeConfiguration.dirtProvider* | `TreeConfiguration.belowTrunkProvider` |
| *Biome.BiomeCategory* / *Biome.getDownfall* | gone |
| *MultiNoiseBiomeSource.Preset* | `MultiNoiseBiomeSourceParameterList.Preset` |
| *BiomeSpecialEffects.fogColor* / *skyColor* / music / ambient sound | `EnvironmentAttributes.*` via `Biome.getAttributes` |
| the +8 chunk population offset | gone — decoration starts at the chunk corner, `InSquarePlacement` scatters |
| *StructureTemplateManager* folder *structures/* | *structure/* |
### Part XIII — Commands and data packs
The permission rewrite is the largest single break in this table: the
integer permission level is gone from the whole command API, replaced by
`PermissionSet` and `PermissionCheck` in `net/minecraft/server/permissions`.
The ints survive only in *ops.json*, in *server.properties* and on the wire.
| the name you remember | 26.2 |
|---|---|
| *ResourceLocationArgument* | `IdentifierArgument` (the registry id is unchanged) |
| *CommandSourceStack.hasPermission(int)* | `CommandSourceStack.permissions` + `PermissionSet.hasPermission` |
| *CommandSourceStack.getPermissionLevel* | gone — a source carries a `PermissionSet`. `PermissionLevel` itself is very much alive: `LevelBasedPermissionSet`, *server.properties*, `ServerOpListEntry` and the JSON-RPC schema all still speak it |
| *CommandSourceStack.withPermission(int)* | `CommandSourceStack.withPermission` taking a `PermissionSet` |
| *SharedSuggestionProvider.hasPermission(int)* | gone — the interface extends `PermissionSetSupplier` |
| *Commands.LEVEL_GAMEMASTERS* as an int | same name, now a `PermissionCheck` |
| *Commands.hasPermission(int)* | `Commands.hasPermission` taking a `PermissionCheck`, returning a `PermissionProviderCheck` |
| *ServerPlayer.hasPermissions(int)* | `ServerPlayer.permissions` |
| *MinecraftServer.getProfilePermissions* returning an int | the same name returning a `LevelBasedPermissionSet` |
| *MinecraftServer.getFunctionCompilationLevel* | `MinecraftServer.getFunctionCompilationPermissions` |
| *Commands.LEVEL_ALL* / *LEVEL_MODERATORS* / *LEVEL_ADMINS* / *LEVEL_OWNERS* as ints | all four are `PermissionCheck`s too — `PermissionCheck.AlwaysPass` for the first, `PermissionCheck.Require` for the rest |
| *ServerPlayer.setPermissionLevel(int)* | `PlayerList.sendPlayerPermissionLevel` on the server; `LocalPlayer.setPermissions` on the client |
| *ColorArgument* | `TeamColorArgument`, yielding a `TeamColor` rather than a `ChatFormatting` |
| *PlayerTeam.getColor* returning a *ChatFormatting* | returns an optional `TeamColor`, its own enum carrying a `TextColor` |
| *TestFunctionArgument* / *TestClassNameArgument* | gone — `/test` addresses tests as registry ids through `ResourceSelectorArgument` and `TestFinder` |
| *net.minecraft.advancements.Criterion* / *CriterionTrigger* / *SimpleCriterionTrigger* | all moved to `net/minecraft/advancements/triggers`; `CriterionTriggerInstance` is the one that stayed behind in `net/minecraft/advancements` |
| *ServerOpListEntry.getLevel* | `ServerOpListEntry.permissions` |
| *ParserUtils.parseJson* | gone — `SnbtGrammar` plus `ParserBasedArgument` |
| *ItemInput.createItemStack(int, boolean)* | `ItemInput.createItemStack` with one argument; the guard is `GiveCommand.MAX_ALLOWED_ITEMSTACKS` |
| *ServerFunctionManager.ExecutionContext* (nested) | top-level `ExecutionContext` in `net/minecraft/commands/execution` |
| *CommandFunction.Entry* / *CommandEntry* / *FunctionEntry* | gone — a line is a `BuildContexts.Unbound`, a macro line a `MacroFunction.MacroEntry` |
| *CommandFunction.CacheableFunction* (nested) | top-level `CacheableFunction`, codec-backed |
| *Commands.performCommand* returning a success count | returns nothing; results are a `CommandResultCallback` pair |
| `data//functions/`, `data//tags/functions/` | singular — *function/* and *tags/function/* |
| *maxCommandChainLength* | `GameRules.MAX_COMMAND_SEQUENCE_LENGTH` |
| *maxCommandForkCount* | `GameRules.MAX_COMMAND_FORKS` |
| *announceAdvancements* | `GameRules.SHOW_ADVANCEMENT_MESSAGES` |
| *net.minecraft.advancements.critereon* | split **three** ways: `net/minecraft/advancements/triggers`, `net/minecraft/advancements/predicates`, and `advancements/predicates/entity` for the entity half |
| *AdvancementList* | `AdvancementTree` (+ `AdvancementNode`, `AdvancementHolder`) |
| *FrameType* | `AdvancementType` |
| *CriterionTrigger.addPlayerListener* / *removePlayerListener* | gone — triggers are stateless; subscriptions live in `PlayerAdvancements` |
| *LootContextParamSet* | `ContextKeySet` |
| *@GameTest*, *@GameTestGenerator*, *@BeforeBatch*, *@AfterBatch* | gone — `GameTestInstance` in `Registries.TEST_INSTANCE` |
| *GameTestRegistry* / *TestFunction* | gone — `Registries.TEST_FUNCTION` + `TestFunctionLoader`, and `TestData` |
| a test's batch as a string | `GameTestInstance.batch` — the batch *is* a `TestEnvironmentDefinition` |
| the structure block as the test host | `TestInstanceBlock` / `TestInstanceBlockEntity` |
## The shape changes, not just the names
A rename table flatters the reader: it suggests that if you learn the two
hundred and forty-three rows above you can read the tree. You cannot, because a dozen of these rows are one
design change each, and the change is what the corresponding page is about.
The recurring ones:
- **Tags on an item became components.** *ItemStack.getTag* /
*getOrCreateTag* have no replacement; a stack is an `Item` plus a
`PatchedDataComponentMap` and every former tag key is a
`DataComponentType` in `DataComponents` — [data components](../systems/foundations/data-components.md),
[items and stacks](../systems/items/items-and-stacks.md).
- **Hand-written serialisation became codecs.** *Packet.write* is gone: a
packet is a record with a `StreamCodec` the protocol table reads.
*ItemStack.save* is gone: there is `ItemStack.CODEC` and, for saved data,
the `ValueOutput` façade — [packets and stream codecs](../systems/networking/packets-and-stream-codecs.md),
[codecs, NBT and JSON](../systems/foundations/codecs-nbt-json.md).
- **Rendering split into extract and render.** *GuiGraphics*,
*EntityRenderer.render*, *LevelRenderer.renderLevel*, *MultiBufferSource*
and every `RenderSystem` state setter are gone or repurposed, because the
frame now builds an immutable render state on the game thread and draws
from it — [the frame](../systems/rendering/the-frame.md), [Blaze3D](../systems/rendering/blaze3d.md),
[entity rendering](../systems/rendering/entity-rendering.md).
- **Per-dimension and per-biome constants became one attribute system.**
*DimensionSpecialEffects* and most of *BiomeSpecialEffects* are gone;
fog, sky, water colour, ambient sound and music are
`EnvironmentAttribute`s resolved through an `EnvironmentAttributeProbe`
over a stack of layers — [lightmap, fog and sky](../systems/rendering/lightmap-fog-and-sky.md),
[biomes](../systems/worldgen/biomes.md).
- **Enums of behaviour became registries of data.** *EnchantmentCategory*,
*MobSpawnType*, *BlockPathTypes*, *GenerationStep.Carving* and
*Biome.BiomeCategory* are all gone, replaced by item sets, registry
records, `HolderSet`s or nothing at all.
- **UUIDs became identifiers.** An `AttributeModifier` is keyed by
`Identifier`, not a UUID, which is why a data pack can now name one —
[attributes](../systems/entities/attributes.md).
- **Sides split.** *Entity.hurt* became `Entity.hurtServer` and
`Entity.hurtClient`; *Player.attack* is still there but the packet that
reaches it is `ServerboundAttackPacket`, a record of one integer, and
`ServerboundInteractPacket` is right-click only. The general rule: where
1.21 had one method that checked `Level.isClientSide`, 26.2 tends to have
two methods — [damage and death](../systems/entities/damage-and-death.md),
[the sword swing](../systems/player/the-sword-swing.md).
## Yarn
Yarn is Fabric's community mapping set. It is not a different version of
the game and nothing on this list is a *change*: it is the same 26.2 class
under the name a Fabric modder has in their head. Only the ones that
actually trip people are listed — where the Yarn and Mojang names differ
enough that grep fails.
Both columns are italic here, because Yarn names are not in the decompile
and `verify_names.py` cannot check them; the Mojang column is the one this
corpus uses everywhere else, and every one of those names is backticked and
verified on its own page.
| Yarn | Mojang (this corpus) |
|---|---|
| *World* / *ServerWorld* / *ClientWorld* | *Level* / *ServerLevel* / *ClientLevel* |
| *WorldChunk* | *LevelChunk* |
| *MinecraftClient* | *Minecraft* |
| *PlayerEntity* / *ServerPlayerEntity* / *ClientPlayerEntity* | *Player* / *ServerPlayer* / *LocalPlayer* |
| *ClientPlayNetworkHandler* | *ClientPacketListener* |
| *ServerPlayNetworkHandler* | *ServerGamePacketListenerImpl* |
| *ClientConnection* | *Connection* |
| *Text* / *MutableText* | *Component* / *MutableComponent* |
| *TextRenderer* | *Font* |
| *TextHandler* | *StringSplitter* |
| *TextVisitFactory* | *StringDecomposer* |
| *OrderedText* | *FormattedCharSequence* |
| *StringVisitable* | *FormattedText* |
| *FontStorage* | *FontSet* |
| *GlyphAtlasTexture* | *FontTexture* |
| *DrawContext* | *GuiGraphicsExtractor* — and in 26.2 it does not draw; see the drift table |
| *NbtCompound* / *NbtList* / *NbtElement* | *CompoundTag* / *ListTag* / *Tag* |
| *RegistryEntry* / *RegistryEntryList* | *Holder* / *HolderSet* |
| *Registries* / *RegistryKeys* | *BuiltInRegistries* / *Registries* |
| *Vec3d* | *Vec3* |
| *Box* | *AABB* |
| *Hand* | *InteractionHand* |
| *ActionResult* | *InteractionResult* |
| *Inventory* (the interface) | *Container* |
| *PlayerInventory* | *Inventory* |
| *ScreenHandler* / *ScreenHandlerType* | *AbstractContainerMenu* / *MenuType* |
| *StatusEffect* / *StatusEffectInstance* | *MobEffect* / *MobEffectInstance* |
| *EntityAttribute* / *EntityAttributeInstance* | *Attribute* / *AttributeInstance* |
| *ParticleEffect* | *ParticleOptions* |
| *BlockPos.Mutable* | *BlockPos.MutableBlockPos* |
| *Identifier* | *Identifier* — Yarn was right first; Mojang renamed to match in 26.2 |
The last row is the joke that keeps giving: the single most-cited example
of "Yarn names are better" stopped being an example, and a decade of
Fabric code now compiles against a Mojang-named class with the Yarn name.
## What a rename table cannot tell you
- **A verified name is not a correct claim.** `verify_names.py` proves the
right-hand column exists; it cannot prove the left-hand column ever did.
The 1.21 side of this table is the only unverifiable content in the
corpus, which is why it is confined to one page.
- **The names did not move where you would guess.** Rendering is the
fourth-largest table, behind commands, the server and items. Two rewrites
nobody advertised — permissions ceasing to be integers, and game rules
becoming a registry — renamed more identifiers than the render-stack
refactor did, and the render one is the famous half only because its
classes are the ones tutorials name.
- **Renames cluster with rewrites.** No part of the tree renamed a class
and kept its design; where the name changed, the responsibility usually
moved too. Reading the row is not enough, which is what the linked page
is for.
- **Two subsystems have no rows at all, and that is the answer.** Dialogs
(`net/minecraft/server/dialog` and its client screens) and the JSON-RPC
management server postdate 1.21 entirely: there is no old name to look up,
and a reader who cannot find one is not missing a row. Game tests are the
opposite case — the *whole* 1.21 API is gone, which is why they have five.
- **`Minecraft.setScreen` is a trap rather than a rename.**
`Minecraft.setScreenAndShow` exists in 26.2 and a 1.21-era reader grepping
for the old name will land on it, then wonder why the screen stack behaves
differently. The method that replaced the old one is `Gui.setScreen`.
- **Some names survived and changed meaning**, which is worse than a
rename because grep still finds them: `Gui` (now the screen manager, not
the HUD), `Material` (now a texture reference in
`client/resources/model/sprite`, not a block property), `ParticleGroup` (now a per-render-type bucket, not
a count limit), `Strategy` (now top-level, not nested in
`PalettedContainer`), and `MultiVariant`, whose name survives only in the
data-generator package while the runtime type is gone.
## Where to look
`Identifier`, then `Holder` and `HolderSet` (`net/minecraft/core`), then `DataComponents`
— those three carry more of the drift than any others. After that, pick the
part you are lost in and read its page rather than its rows.
---
*Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes `tools/verify_names.py`.*
---
# Glossary
> Verified against **Minecraft 26.2** · Reference · One sentence
> per term the rest of the corpus uses, and a link to the page that owns it.
Every page in this corpus assumes the vocabulary of the pages before it.
That is deliberate — a lecture that redefines "chunk holder" every time it
appears is unwatchable — but it means a reader who arrives in the middle
has no way back. This page is the way back: the terms, alphabetically, one
sentence each, each pointing at the page where the term is actually
explained.
A sentence here is a *reminder*, not a definition to rely on. If the
sentence is all you needed, good; if it is not, the link is the point of
the entry. Where a term is a class name, the class name is the entry —
this corpus uses Mojang's names for concepts as well as for types, and
inventing a second vocabulary to sit beside them would only double the
work.
## A
**Activity** — the filter that decides which of a brain's behaviours are
asked at all, rather than a mode it runs in: the active set is always the core
activities plus exactly one other, and an `ActivityData` declares each one's
prioritised behaviour list. → [AI](../systems/entities/ai-goals-and-brains.md)
**Advancement** — a data-pack-defined goal: criteria, a requirements
expression over them, an optional display entry and a reward. → [advancements](../systems/commands/advancements.md)
**Aquifer** — the worldgen component that decides what a point is made of
once the density is known — stone, air, water or lava — from its own barrier
and fluid-level noises; a carver writes the block itself but asks the aquifer
which block to write. → [terrain](../systems/worldgen/terrain.md)
**Argument type** — a Brigadier `ArgumentType` that parses one *argument* off
the command line, however many words that takes — three for a `Vec3Argument`,
all of the rest for a `MessageArgument`; vanilla's live in
`net/minecraft/commands/arguments` and are described to the client through an
`ArgumentTypeInfo`. → [Brigadier and commands](../systems/commands/brigadier-and-commands.md)
**Atlas** — one large texture stitched at load time out of many sprite
files, so a chunk section can be drawn with a single bound texture. → [models and atlases](../systems/rendering/models-and-atlases.md)
**Attribute** — a named, ranged, modifiable number on a `LivingEntity`;
modifiers are keyed by `Identifier`, and eight attributes are not
client-syncable at all. → [attributes](../systems/entities/attributes.md)
**Authority** — whose copy of an entity produces the position that counts:
the server for a mob, the owning client for its own player and for the boat
that player is steering — the server still runs your player's physics, then
overwrites its answer with the number your client sent. Five predicates on
`Entity` decide it, starting with the final
`Entity.isLocalInstanceAuthoritative`. → [authority](../systems/entities/authority.md)
**Avatar** — the class between `LivingEntity` and `Player`; a `Mannequin`
is an `Avatar` that is not a player, and `AvatarRenderer` draws both. → [player anatomy](../systems/player/player-anatomy.md)
## B
**Batch** — a group of game tests keyed by the environment they share;
a batch *is* an environment, not a name and not a class. → [game tests](../systems/commands/game-tests.md)
**Beardifier** — the density-function term that bends terrain around a
structure; terrain adaptation writes no blocks, it changes the noise. → [structure placement](../systems/worldgen/structure-placement.md)
**Behaviour** — one unit of brain AI, gated on memories and asked once a
tick; unless it overrides `Behavior.canStillUse` it stops inside the same
`Brain.tick` that started it, so everything it does it does in
`Behavior.start`. → [AI](../systems/entities/ai-goals-and-brains.md)
**Biome** — a named bundle of generation settings, mob spawns, block tints
and environment attributes, attached to a 4×4×4 volume of the world. → [biomes](../systems/worldgen/biomes.md)
**BiomeSource** — the object that answers "which biome is at this quart
position": by a climate search, from one fixed biome, from a checkerboard of a
listed few, or — in the End — off a single erosion sample. → [biomes](../systems/worldgen/biomes.md)
**Blaze3D** — Mojang's GPU abstraction, with OpenGL and Vulkan backends
behind one `GpuDevice`. → [Blaze3D](../systems/rendering/blaze3d.md)
**Blend alpha** — the mixing weight `Blender` computes from the distance to
the nearest measured old column: zero against the seam (use the old
measurement), one out of range (use the noise). → [blending at the old-chunk border](../systems/worldgen/blending.md)
**Blending data** — the sixteen-column ring of heights, densities and biomes
a `BlendingData` measures out of its **own** chunk's blocks, on the sides
facing ground the game has yet to generate; its presence on a chunk is what
makes the chunk old. → [blending at the old-chunk border](../systems/worldgen/blending.md)
**Block** — the singleton describing a kind of block: its behaviour, its
property set and its state table. One `Block`, many `BlockState`s. → [blocks and states](../systems/blocks/blocks-and-states.md)
**Block entity** — the per-position mutable state a block cannot fit into
its state (a chest's contents, a furnace's progress), stored on the chunk. → [block entities](../systems/blocks/block-entities.md)
**Block event** — a message from a block to itself (a piston push, a note
block, a chest lid), queued on `ServerLevel` and drained at one fixed point
in the level tick — so it lands late, usually within the same tick — and
mirrored to nearby clients as a packet. → [pistons and block events](../systems/blocks/pistons-and-block-events.md)
**BlockState** — one combination of a block's property values, built once by
the block's `StateDefinition` and compared by identity;
`Block.BLOCK_STATE_REGISTRY` is the flat table that numbers it for the wire and
the global palette. → [blocks and states](../systems/blocks/blocks-and-states.md)
**Border tick** — a position along an old chunk's seam queued for
post-processing, so a leaf or a fluid there is re-evaluated when the chunk
goes live. → [blending at the old-chunk border](../systems/worldgen/blending.md)
**Brain** — the memory-and-behaviour AI used by villagers, piglins and
axolotls, as opposed to the older goal system. → [AI](../systems/entities/ai-goals-and-brains.md)
**Brigadier** — Mojang's command-parsing library: a tree of literal and
argument nodes with per-node requirements, shared by client and server. → [Brigadier and commands](../systems/commands/brigadier-and-commands.md)
**Built-in block model** — a block model attached in code by
`BuiltInBlockModels` rather than by a resource pack, living in a second
model table that terrain never reads; how a minecart or a block display
draws a chest. → [block-entity rendering](../systems/rendering/block-entity-rendering.md)
## C
**Camera** — the client's eye: position, rotation and the cull frustum,
copied into a `CameraRenderState` once per frame — plus an
`EnvironmentAttributeProbe` that `Camera.tick` advances and no render state
carries. → [the frame](../systems/rendering/the-frame.md)
**Carver** — a worldgen pass that hollows out caves and ravines by writing
air, water or lava, asking the `Aquifer` which — except the nether carver,
which does not ask. → [terrain](../systems/worldgen/terrain.md)
**Cell** — the lattice unit of terrain noise, four blocks wide and deep and
eight tall in the overworld, 768 to a chunk: the expensive three-dimensional
density terms are evaluated at its corners and interpolated within it, and the
caches keyed on it mean nothing outside the cell loop. → [terrain](../systems/worldgen/terrain.md), [density functions](../systems/worldgen/density-functions.md)
**Chunk** — a 16-by-16 column of the world's full height: sections,
heightmaps, block entities, tick queues and a status. → [chunk anatomy](../systems/world/chunk-anatomy.md)
**Chunk layer** — which of the three `ChunkSectionLayer` buffers a block's
quads are meshed into — solid, cutout or translucent — decided at bake time
from the alpha inside that quad's own patch of its sprite rather than from the
block. → [models and atlases](../systems/rendering/models-and-atlases.md), [section meshing](../systems/rendering/section-meshing.md)
**ChunkHolder** — the server's per-chunk record of the *level* the two graphs
computed for it, a future per threshold and status, and what changed in it this
tick; which tickets asked for that level is `TicketStorage`'s business, not the
holder's. → [tickets and loading](../systems/world/tickets-and-loading.md)
**ChunkMap** — the server's chunk table: holders, the ticket-driven level
graphs, entity tracking, and the region-file storage underneath. → [tickets and loading](../systems/world/tickets-and-loading.md)
**ChunkStatus** — one rung of the generation ladder; a chunk advances one
status at a time, and the `ChunkStep` for each status declares the neighbour
radius that step needs. → [the chunk generation pipeline](../systems/world/chunk-generation-pipeline.md)
**Climate** — the noise sample (temperature, humidity, continentalness,
erosion, depth, weirdness) a biome is chosen by. → [biomes](../systems/worldgen/biomes.md)
**Codec** — a DataFixerUpper object that both encodes and decodes one type
against any `DynamicOps`; the corpus's universal serialisation vocabulary. → [codecs, NBT and JSON](../systems/foundations/codecs-nbt-json.md)
**CommandSourceStack** — *who is running this command, and from where*:
position, rotation, level, entity, a `PermissionSet` and an output sink,
immutable, so every `with…` returns a copy. → [Brigadier and commands](../systems/commands/brigadier-and-commands.md)
**Compiled query** — the immutable `EntitySelector` a parse produces:
thirteen fields, no reader and no grammar, resolvable any number of times
against different sources. → [entity selectors](../systems/commands/entity-selectors.md)
**Component** — two different things the corpus keeps apart: a *data
component* on an item stack, and a `Component` of chat text. → [data components](../systems/foundations/data-components.md), [text components](../systems/foundations/text-components.md)
**Connection** — the tail handler of one Netty pipeline plus the channel it
holds, with exactly one packet listener at a time, swapped when the protocol
phase changes. → [the connection](../systems/networking/the-connection.md)
**Container** — the interface a thing with item slots implements (a chest,
a hopper, an inventory), as distinct from the *menu* a player interacts
with it through. → [containers and menus](../systems/items/containers-and-menus.md)
**Criterion** — one condition inside an advancement, backed by a
`CriterionTrigger` the server fires when the relevant thing happens. → [advancements](../systems/commands/advancements.md)
## D
**DamageSource** — the *what hit you, and who is responsible* record every
damage calculation and death message reads. → [damage and death](../systems/entities/damage-and-death.md)
**Data component** — a typed, codec-backed value keyed by a
`DataComponentType`: a patch over the item's prototype on a stack, a whole map
on a block entity, read-only on an entity; what NBT item tags became. → [data components](../systems/foundations/data-components.md)
**Data pack** — a pack of JSON, structure NBT and function files supplying
the server's data-driven content; the server half of the resource system. → [the resource system](../systems/foundations/resource-system.md)
**DataLayer** — the nibble array one section's block light or sky light lives
in, owned by the light engine and never by the section. → [lighting](../systems/world/lighting.md)
**Debug subscription** — a registered kind of debug value a client can ask
the server for; most kinds the server polls, diffs and sends only when they
change, and the rest it pushes as they happen. → [debugging the running game](../systems/client/debugging-the-running-game.md)
**DeltaTracker** — the client's clock: how much of a tick has elapsed, and
the source of every partial tick in the frame but the lightmap's, which is a
literal one. → [the client loop](../systems/client/the-client-loop.md)
**Density function** — a node in the JSON-defined graph that turns a
position into a number; the graph in the registry is never the graph that
actually runs. → [density functions](../systems/worldgen/density-functions.md)
**Dialog** — a data-pack-defined form the server can put on a player's
screen, whose submitted values come back as a packet. → [dialogs](../systems/commands/dialogs.md)
**Dimension** — one `ServerLevel` and its `DimensionType`: a height range,
a set of environment attributes and its own chunk storage. → [level data and rules](level-data-and-rules.md)
## E
**Enchantment** — a data-pack record of effect components conditioned on loot
predicates; its registry is synchronised, but a client that already has the
pack is sent only the id. → [enchantments](../systems/items/enchantments.md)
**Entity** — a thing the level ticks in its own right: a position, a bounding
box, synched data and a tick method. A *block entity* is not one. → [entity anatomy](../systems/entities/entity-anatomy.md)
**EntityType** — the registry entry for a kind of entity: its factory,
category, size, feature flags and the two numbers that decide how it reaches
clients. Spawn rules are keyed *by* the type in `SpawnPlacements`, not held on
it. → [entity anatomy](../systems/entities/entity-anatomy.md)
**EnvironmentAttribute** — a per-dimension, per-biome, per-time-of-day,
per-weather value resolved through a stack of layers: directly on the server,
through the camera's smoothing probe on the client. Not only the visual ones:
alongside fog and sky colour sit twenty gameplay attributes — whether lava
flows fast, whether piglins zombify, whether a bed works, and the villager's
schedule. → [environment attributes and timelines](../systems/world/environment-attributes-and-timelines.md)
**Event loop** — the queue-and-thread pairing `BlockableEventLoop` is: an
owning thread that drains posted tasks and, through
`BlockableEventLoop.managedBlock`, keeps draining while it waits.
`Minecraft` and `MinecraftServer` are both one — but only the server rations
the drain against a time budget; the client empties the queue every frame. → [the server tick](../systems/server/server-tick.md#the-event-loop-and-what-a-ticks-spare-time-buys)
**Experiment** — a built-in data pack whose `PackSource` is
`PackSource.FEATURE`, enabling one non-vanilla `FeatureFlag`; switching one
on in the create-world screen is a data-pack reload. → [creating a world](../systems/worldgen/creating-a-world.md)
**Extract** — the first half of the client's frame: walk the game state, cull
it, and write the render states, so that the drawing half reads no live game
object from `LevelRenderer.render` down — the top of the render half still
does. The top-level states are single objects re-filled each frame, not fresh
immutable values. → [the frame](../systems/rendering/the-frame.md)
## F
**Feature** — the algorithm half of decoration: what to build, with no say in
which positions it is offered. → [features and placement](../systems/worldgen/features-and-placement.md)
**Flat level generator preset** — a `FlatLevelGeneratorPreset`: a display
item plus a `FlatLevelGeneratorSettings`, one row of the Superflat *Presets*
screen. → [creating a world](../systems/worldgen/creating-a-world.md)
**Fluid** — the registry object behind a `FluidState`, a source and a flowing
instance per liquid, with `FlowingFluid` holding the spread algorithm and
`LiquidBlock` the block form. → [fluids](../systems/world/fluids.md)
**Font** — a resource-pack-defined glyph source plus the measuring and
wrapping API on top of it; a glyph is baked into a texture the first time it
is asked for. → [text and fonts](../systems/client/text-and-fonts.md)
**Frame** — the execution engine's unit of a running function: a depth, a
result callback that `/return` feeds sideways, and a control that can delete
the frame's pending work — one object shared by reference across a whole
function body, and deliberately *not* a stack frame. → [the execution engine](../systems/commands/the-execution-engine.md)
**Frame graph** — the client's per-frame declaration of render passes and
the targets each reads and writes, resolved before anything is drawn. → [visibility and the frame graph](../systems/rendering/visibility-and-the-frame-graph.md)
**Function** — a `.mcfunction` file: a list of commands loaded as a
`CommandFunction`, optionally with macro lines. → [functions and macros](../systems/commands/functions-and-macros.md)
## G
**Game event** — a broadcast fact about something that just happened at a
position (a block placed, a door opened) that sculk sensors and mobs
listen for. → [game events and vibrations](../systems/world/game-events-and-vibrations.md)
**Game rule** — one typed, server-wide switch or number in `GameRules`, saved
with the world and sometimes sent to the client. → [level data and rules](level-data-and-rules.md)
**Game test** — a data-driven test instance: a structure, an environment
and a check the server runs and reports on. → [game tests](../systems/commands/game-tests.md)
**Globally-rendered block entity** — one whose renderer says
`BlockEntityRenderer.shouldRenderOffScreen`, so the client keeps it in a
level-wide set and draws it whether or not its section is visible; three
renderers qualify. → [block-entity rendering](../systems/rendering/block-entity-rendering.md)
**Goal** — one unit of the older mob AI: a start condition, an answer to
whether it may be interrupted, and the set of `Goal.Flag` controls it claims
while running. The priority belongs to the `WrappedGoal` that holds it, and
the flag table rather than the priority is what arbitrates. → [AI](../systems/entities/ai-goals-and-brains.md)
**GpuDevice** — the façade every GPU resource is created through; both
graphics backends sit behind it as `GpuDeviceBackend` implementations. A draw
reaches the driver through the `CommandEncoder` it hands out and the
`RenderPass` that opens. → [Blaze3D](../systems/rendering/blaze3d.md)
**GuiRenderState** — the 2D render tree: strata of nodes that infer their own
layering from bounding boxes and are batched into draw calls at the end of the
frame. → [the GUI render tree](../systems/client/the-gui-render-tree.md)
## H
**Heightmap** — a per-chunk 2D array holding the first *free* Y above the
topmost block matching a predicate; six types exist, and a live chunk keeps the
four that survive worldgen. → [chunk anatomy](../systems/world/chunk-anatomy.md)
**Holder** — a reference to a registry entry that can exist before the
entry is bound: `Holder.Reference` for a registered value, `Holder.Direct`
for an inline one. → [identifiers and registries](../systems/foundations/identifiers-and-registries.md)
**HolderSet** — a set of holders: either a tag (`HolderSet.Named`) or a
literal list. → [tags](../systems/foundations/tags.md)
**HUD** — the in-world overlay (hotbar, hearts, chat, boss bars), which in
26.2 is the class `Hud` — `Gui` now means the screen manager. → [the HUD](../systems/client/hud.md)
## I
**Identifier** — a namespace and a path; the id of everything. A 1.21-era
reader knows it as *ResourceLocation*. → [identifiers and registries](../systems/foundations/identifiers-and-registries.md)
**Ingredient** — a recipe's "any of these items" test. It cannot be an
empty inline list, but a tag that resolves to nothing makes one empty — and a
recipe holding it is never placeable. → [recipes](../systems/items/recipes.md)
**Integrated server** — the `MinecraftServer` a singleplayer client runs on
its own Server thread. Every change to the *world* still crosses as a packet;
a handful of settings cross by direct call. → [anatomy](../systems/anatomy/anatomy.md)
**InteractionResult** — the answer a block or item gives to a click: was
the input consumed, should the arm swing, did the held item change. → [block interaction](../systems/blocks/block-interaction.md)
**Item** — the singleton for a kind of item, holding none of the components
a stack shows; a stack is a holder to one of these, a count, a pop time and a
patched component map — the item's defaults plus the ways this stack differs
from them. → [items and stacks](../systems/items/items-and-stacks.md)
**ItemStackTemplate** — the immutable item-shaped record (an item holder, a
count, a component patch) that data uses where a live, mutable `ItemStack`
would be wrong. → [items and stacks](../systems/items/items-and-stacks.md)
## J
**Jigsaw** — the structure-assembly system that grows a village out of
template pieces by matching connector blocks. → [jigsaw and templates](../systems/worldgen/jigsaw-and-templates.md)
## K
**KeyMapping** — one bindable action: whether its key is down, plus a counter
of owed clicks that `KeyMapping.consumeClick` **drains** rather than
edge-detects. → [input and keybinds](../systems/client/input-and-keybinds.md)
## L
**Level** — a world: `ServerLevel` on the server, `ClientLevel` on the
client, sharing an abstract `Level` and remarkably little else. → [the level tick](../systems/server/server-level-tick.md), [the client level](../systems/client/the-client-level.md)
**Lightmap** — the small texture the client samples to turn a block-light /
sky-light pair into a colour; drawn on the GPU once per tick. → [lightmap, fog and sky](../systems/rendering/lightmap-fog-and-sky.md)
**LocalPlayer** — the `Player` a human steers: its own `ClientInput`, its own
prediction, and the last input and position it sent. → [player anatomy](../systems/player/player-anatomy.md)
**Loot table** — the data-driven roll that turns an event (a block broken, a
mob killed, anything at all reading a container that has not been rolled yet)
into item stacks. → [loot tables](../systems/items/loot-tables.md)
## M
**Macro** — a function line beginning with a `$` substitution. The plain
lines of the file are parsed once at load; a macro line is substituted and
**re-parsed** per distinct argument tuple, cached only eight deep. → [functions and macros](../systems/commands/functions-and-macros.md)
**Memory** — one typed, optionally expiring value in a `Brain`; behaviours
are gated on which memories are present. → [AI](../systems/entities/ai-goals-and-brains.md)
**Menu** — the server-authoritative object behind an open container screen:
slots, a synchroniser and a state id. → [containers and menus](../systems/items/containers-and-menus.md)
**MultiPlayerGameMode** — the client's only channel for acting on the world:
every break, place, use and attack goes through it, and the ones that predict
open a prediction window before they send. → [prediction and acknowledgement](../systems/client/prediction-and-acks.md)
## N
**NBT** — Minecraft's binary tag format; in 26.2 a sealed `Tag` hierarchy
whose scalars are records and whose containers (`CompoundTag`, `ListTag`) are
final classes, read and written through `NbtIo` and reached by codecs through
`NbtOps`. → [codecs, NBT and JSON](../systems/foundations/codecs-nbt-json.md)
**NBT path** — a compiled query over a tag, six node kinds deep — a named
child, an index, all elements, and three kinds of match — that `/data` uses to
read and write, and which materialises the structure it walks through on a
write. → [scores, teams and stored data](../systems/commands/scoreboard-and-data.md)
**Neighbour update** — the server-only notification a block sends its six
neighbours after a change; distinct from a *shape update*, which runs on
both sides. → [blocks and states](../systems/blocks/blocks-and-states.md)
**NoiseChunk** — the per-chunk machine that fills the noise lattice and
installs the caches the density-function graph asked for. → [density functions](../systems/worldgen/density-functions.md)
**NoiseRouter** — the density functions a generator asks for, as one record;
`NoiseRouter.mapAll` rebuilds them all at once, which is how a whole graph gets
its caches installed in one pass. → [density functions](../systems/worldgen/density-functions.md)
## O
**Objective** — a named scoreboard column: a criterion, a display name, a
render type and a number format. Only the *dummy* and *trigger* criteria wait
for commands; every other one — including every statistic in the game, since
`Stat` extends `ObjectiveCriteria` — is driven from `ServerPlayer`. → [scores, teams and stored data](../systems/commands/scoreboard-and-data.md)
**Old chunk** — a chunk whose `ChunkAccess.blendingData` is non-null, which
is to say one whose save data carried a *blending_data* compound;
`ChunkAccess.isOldNoiseGeneration` is the test. → [blending at the old-chunk border](../systems/worldgen/blending.md)
## P
**Packet** — an interface: a `PacketType`, which is a name and a direction,
and one handler method. Roughly half the implementations are records, the
wire form is a `StreamCodec` the phase's protocol description holds rather
than something the class owns, and a few types are registered into more than
one phase. → [packets and stream codecs](../systems/networking/packets-and-stream-codecs.md)
**PalettedContainer** — the bit-packed storage a chunk section keeps its
block states and biomes in, with a palette that grows as the section gets
more varied. → [chunk anatomy](../systems/world/chunk-anatomy.md)
**Partial tick** — the fraction of a tick elapsed at the moment a frame is
drawn, used to interpolate the world. There is no single one: a frame carries
six values, they disagree on purpose, and the one screens are handed is not a
fraction of a tick at all. → [the frame](../systems/rendering/the-frame.md)
**Path** — the list of nodes a navigator is following, produced by
`PathFinder`'s A\* over a snapshot of already-loaded chunks, with a
`NodeEvaluator` deciding what each candidate block *is* to this mob. → [pathfinding](../systems/entities/pathfinding.md)
**Permission atom** — a named capability with an `Identifier`
(`Permission.Atom`), the other kind of permission besides a command level;
an operator's level-based set grants exactly one, the entity-selector atom,
from gamemaster up. → [permissions](../systems/commands/permissions.md)
**Permission level** — one rung of `PermissionLevel` (all, moderators,
gamemasters, admins, owners), and only the *ordered* half of a permission: a
command source carries a `PermissionSet` and a node requires a
`PermissionCheck`, neither of which is an integer. → [permissions](../systems/commands/permissions.md)
**Permission set** — what a command source carries and a node's check is
asked against: on the server a level-based set (a rung plus one atom). The
client rebuilds four of those from the op level it is told — rung zero maps to
`PermissionSet.NO_PERMISSIONS` instead — and keeps a chat set built by
subtraction beside them. No packet carries a `PermissionSet` itself. → [permissions](../systems/commands/permissions.md)
**PlacedFeature** — a configured feature plus an ordered list of placement
modifiers; the unit a biome actually names. → [features and placement](../systems/worldgen/features-and-placement.md)
**Point of interest** — a block state the game has decided is worth walking
to: a bed, a job site, a portal. `PoiManager` indexes them by position, in its
own files beside the chunks. → [points of interest](../systems/world/points-of-interest.md)
**Prediction ledger** — the corpus's name for `BlockStatePredictionHandler`:
the client's record of what the server is known to have at a block it changed
optimistically. The ack is a receipt for a number, not a verdict — it settles
every entry at or below it, writing back a correction if one arrived and
rolling the block back if none did. → [prediction and acknowledgement](../systems/client/prediction-and-acks.md)
**Protocol phase** — one of handshake, status, login, configuration and
play; each has its own packet table and its own listener. → [protocol phases](../systems/networking/protocol-phases.md)
## Q
**Quart** — a four-block cell, the resolution biomes are stored and
sampled at; `QuartPos` is the arithmetic. → [biomes](../systems/worldgen/biomes.md), [math and primitives](math-and-primitives.md)
## R
**Recipe** — a server-side matcher and assembler; no `Recipe` ever crosses
the wire. The client gets a `RecipeDisplay` and a `RecipeDisplayId`, which is a
position in a list rather than the recipe's name. → [recipes](../systems/items/recipes.md)
**Region file** — the 32-by-32-chunk container file chunks are stored in,
addressed by a sector table at its head. → [chunk storage](../systems/world/chunk-storage.md)
**Registry** — a frozen, id-assigning table of one kind of thing; some are
built into the jar, some are loaded from data packs, some are sent to the
client. → [identifiers and registries](../systems/foundations/identifiers-and-registries.md)
**Reload listener** — the unit of a reload: one object that reads what it
needs off the worker pool and swaps its live state on the owning thread, every
apply running in order behind a `PreparableReloadListener.PreparationBarrier`. → [the resource system](../systems/foundations/resource-system.md)
**Render state** — the write-once snapshot of what to draw, produced by the
extract half of the frame and consumed by the drawing half. The property that
matters is that the drawing half reads no game object, not that the state is
an immutable value. → [the frame](../systems/rendering/the-frame.md), [entity rendering](../systems/rendering/entity-rendering.md)
**RenderPipeline** — the client's declaration of how to rasterise: shaders,
blend, depth, cull, vertex format, topology. It says nothing about which
textures to bind or which target to draw into; that is `RenderType`. → [Blaze3D](../systems/rendering/blaze3d.md)
**RenderType** — a `RenderPipeline` plus everything a pipeline does not say:
which target to draw into, which textures to bind, and the layering and
batching rules. → [Blaze3D](../systems/rendering/blaze3d.md)
**Resource pack** — a pack of assets; the client half of the same pack
machinery data packs use. → [the resource system](../systems/foundations/resource-system.md)
## S
**SavedData** — a named, codec-backed blob stored beside the world (the
border, the weather, the rules, raids, the dragon fight); `level.dat`
itself is nearly a stub. → [level data and rules](level-data-and-rules.md)
**Scheduled tick** — a block or fluid position queued to run at a named
future tick, with a priority breaking ties inside that tick. → [scheduled ticks](../systems/world/scheduled-ticks.md)
**Score** — one number for one holder under one objective, reached through a
`ScoreAccess` handle rather than a setter. → [scores, teams and stored data](../systems/commands/scoreboard-and-data.md)
**Screen** — one full-window client UI with its own widget tree and
lifecycle; the server is told nothing about most of them. → [GUI and screens](../systems/client/gui-and-screens.md)
**Section** — a 16-cubed piece of a chunk: one paletted container of block
states, one of biomes, and four counters. Its light lives in the light
engine's own storage, not on the section. → [chunk anatomy](../systems/world/chunk-anatomy.md)
**Section mesh** — the compiled vertex buffers for one section
(`CompiledSectionMesh`), rebuilt when the section is both dirty and visible —
usually on a worker, but inline on the client thread when the chunk-builder
option asks for it. → [section meshing](../systems/rendering/section-meshing.md)
**Selector head** — the single character after the *@* (*a*, *e*, *n*,
*p*, *r*, *s*) that sets a selector's default limit, order and entity scope
before any option is read; three of the six also pin the type to player, and
two instead add an aliveness test. → [entity selectors](../systems/commands/entity-selectors.md)
**Sensor** — the half of brain AI that writes memories from the world, on a
fixed interval. → [AI](../systems/entities/ai-goals-and-brains.md)
**ServerEntity** — the server's per-tracked-entity bookkeeping: what the
watching clients were last told, and what to send them next. → [what the client is told](../systems/networking/what-the-client-is-told.md)
**Shape update** — the "your neighbour changed, recompute yourself" call
that runs on both client and server, unlike a neighbour update. → [blocks and states](../systems/blocks/blocks-and-states.md)
**Signed message** — a chat message carrying a signature over its content
and its place in a per-player chain, so the server can prove who said it. → [chat and signing](../systems/networking/chat-and-signing.md)
**Simulation distance** — how far the world *ticks*, as against how far you
can see: the radius behind `TicketType.PLAYER_SIMULATION`, deciding which
chunks tick blocks, fluids and entities. → [tickets and loading](../systems/world/tickets-and-loading.md)
**Special model renderer** — a hand-written submitter for a shape no cuboid
model can express, reached from an item model or a block state rather than
from a block entity; thirteen of them. → [block-entity rendering](../systems/rendering/block-entity-rendering.md)
**Staging buffer** — the list an executing action appends its spawned
commands to, spliced onto the *head* of the queue after it runs — which is
what makes an `ArrayDeque` behave as a call stack. → [the execution engine](../systems/commands/the-execution-engine.md)
**StreamCodec** — the wire counterpart of a `Codec`: encodes to and decodes
from a `ByteBuf`, with no schema and no field names. → [packets and stream codecs](../systems/networking/packets-and-stream-codecs.md)
**Structure** — a generated building or landmark: a placement lottery, a
start assembled in memory, and pieces written a chunk at a time. → [structure placement](../systems/worldgen/structure-placement.md)
**StructurePiece** — one room, corridor or slab of a structure. In the
hand-built half it is a Java class that writes its own blocks and constructs its
own neighbours, chosen by no pool; the jigsaw half's `PoolElementStructurePiece`
is one too. Every piece carries a registered `StructurePieceType`, which is how
it comes back off disk. → [hand-built structures](../systems/worldgen/hand-built-structures.md)
**StructureStart** — one decided structure: the `Structure`, the chunk it
started in, a `PiecesContainer`, a reference count and a cached bounding box,
stored on the chunk it began in. → [structure placement](../systems/worldgen/structure-placement.md)
**Submit node** — one thing to draw that is not terrain, written into
`SubmitNodeStorage` by the *submit* pass out of the render states extract left
behind, and sorted into a phase before the feature renderers turn it into
vertices. → [entity rendering](../systems/rendering/entity-rendering.md), [submit phases](submit-phases.md)
**SynchedEntityData** — the per-entity table of small values the server
pushes to watching clients, keyed by class-tree ordinal. → [synched entity data](../systems/entities/synched-entity-data.md)
## T
**Tag** — a named set of registry entries defined by data packs and merged
across them, unless a higher pack sets *replace*. (The unrelated NBT sense of
the word belongs to [NBT](../systems/foundations/codecs-nbt-json.md).) → [tags](../systems/foundations/tags.md)
**Team** — a named set of score holders carrying a colour, a friendly-fire
flag, a collision rule and a nametag rule — so a class in the scores package
is read by collision and by rendering. → [scores, teams and stored data](../systems/commands/scoreboard-and-data.md)
**Tick** — one step of the server's simulation, 50 ms at the default rate
that `/tick rate` can change, or one step of the client's; a client behind
the clock catches up to ten accumulated ticks in a frame and discards the
rest. → [the server tick](../systems/server/server-tick.md), [the client loop](../systems/client/the-client-loop.md)
**Ticket** — the reason a chunk is loaded: a type and a level, fed into two
separate graphs, with `ChunkLevel` deciding what the level buys — a holder
only, then full, then block-ticking, then entity-ticking. → [tickets and loading](../systems/world/tickets-and-loading.md)
**Timeline** — one clock's data-driven curve set: an optional period, the
named instants on that clock, and one `AttributeTrack` per environment
attribute — keyframed over *modifier arguments*, not over values. → [environment attributes and timelines](../systems/world/environment-attributes-and-timelines.md)
**Trigger** — the server-side hook that tells **one** player's advancement
state that something happened, by sweeping that player's listener map for
this trigger. Nothing broadcasts. → [advancements](../systems/commands/advancements.md)
## U
**Unattended command** — a command the player did not type: a dialog button
or a chat click event, sent through `ClientPacketListener.sendUnattendedCommand`.
The client re-parses it and asks first if it fails to parse, needs a signature,
or needs a permission the client believes it lacks; a clean one goes without a
prompt. A sign's command is not one of these — it runs on the server at
gamemaster level and the client is never asked. → [permissions](../systems/commands/permissions.md)
## V
**View distance** — how far the *server* sends chunks. A client's
render-distance request only clamps what it is sent; the ticket radius comes
from the server's own number. Not *simulation distance*, which is how far
the world ticks. → [tickets and loading](../systems/world/tickets-and-loading.md)
**VoxelShape** — the collision or outline volume of a block state, held as
a set of boxes with fast merge and sweep operations. → [math and primitives](math-and-primitives.md)
## W
**Watchdog** — `ServerWatchdog`, the daemon that treats a tick longer than
*max-tick-time* as a dead server: it writes a crash report, calls `System.exit`,
and halts the JVM ten seconds later whether the shutdown finished or not. → [how a server dies](../systems/server/how-a-server-dies.md)
**Window** — the GLFW handle the whole client hangs off: the framebuffer
size, the GUI scale, fullscreen, and the six window callbacks — not the input
ones, which `KeyboardHandler` and `MouseHandler` register. → [the window](../systems/rendering/the-window.md)
**World clock** — the identity a timeline is sampled against: a unit record
in `Registries.WORLD_CLOCK`, two of them in vanilla, holding nothing at all.
The tick count, the rate and the paused flag are
`ServerClockManager.ClockInstance`'s, one per clock, and `/time` can move or
pause each independently. → [environment attributes and timelines](../systems/world/environment-attributes-and-timelines.md)
**World gen settings** — `WorldGenSettings`: a `WorldOptions` (the seed,
*generate structures*, *bonus chest*) and the `LevelStem` map, a `SavedData`
written to *data/minecraft/world_gen_settings.dat*. It is the only part of world
generation that is saved; everything else Part XII reads is re-read from the
enabled packs on every world open. → [creating a world](../systems/worldgen/creating-a-world.md)
**World preset** — a `WorldPreset` registry entry holding one `LevelStem`
per dimension; what the world-type button selects, and what *level-type*
names on a dedicated server. → [creating a world](../systems/worldgen/creating-a-world.md)
**World stem** — `WorldStem`, the four things `WorldLoader.load` hands the
server constructor in one bundle: the resource manager, the reloadable server
resources, the layered registries, and the level data with its gen settings. → [starting a server](../systems/server/starting-a-server.md)
**World-limited** — a parse-time flag, set by any of seven positional
selector options, that confines a selector's resolve to the source's own
level instead of every level on the server. → [entity selectors](../systems/commands/entity-selectors.md)
**WorldGenRegion** — the bounded, write-guarded view of the world a
generation step is given; it throws rather than loading a chunk, which is
why cascading worldgen cannot happen. → [the chunk generation pipeline](../systems/world/chunk-generation-pipeline.md)
---
*Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes `tools/verify_names.py`.*
---
# Diagram lanes
> Generated from `TEMPLATE.md`'s lane key by `python tools/check_lanes.py --index`. Do not edit by hand.
Almost every lane in a sequence diagram is a class, and a lane means the same thing on
every page: the key in `TEMPLATE.md` is the authority, and `check_lanes.py` fails a deploy on
a page that disagrees with it. The last rows are the exceptions — lanes that stand for a
thread or a boundary rather than for one class.
333 lanes are classes and 9 are not. A lane is normally the initials of
the class's CamelCase words (`ServerGamePacketListenerImpl` is `SGPL`), but three other
rules make about a third of them: a short one-word class is its own lane (`Player`,
`Sheep`), a longer one-word class
takes a fixed prefix (`Connection` is `Conn`, `Enchantment` is `Ench`), and a collision is
resolved by lengthening the **later** claimant, never by reassigning a row — which is why
`ChestMenu` is `ChestM` and not `CM` (`ChunkMap` had it first). Derive nothing from a lane;
read it off this table.
| lane | class |
|---|---|
| `AA` | `AbstractArrow` |
| `AB` | `AbstractBoat` |
| `ACM` | `AbstractContainerMenu` |
| `AFBE` | `AbstractFurnaceBlockEntity` |
| `AM` | `AtlasManager` |
| `AP` | `AcquirePoi` |
| `AR` | `AdvancementRewards` |
| `ATS` | `AttributeTrackSampler` |
| `AttrI` | `AttributeInstance` |
| `AttrM` | `AttributeMap` |
| `BC` | `BuildContexts` |
| `BD` | `BlendingData` |
| `BDR` | `BrainDebugRenderer` |
| `BEL` | `BlockableEventLoop` |
| `BERD` | `BlockEntityRenderDispatcher` |
| `BI` | `BucketItem` |
| `BIR` | `BuiltInRegistries` |
| `BLE` | `BlockLightEngine` |
| `Blender` | `Blender` |
| `Block` | `Block` |
| `Boot` | `Bootstrap` |
| `BowI` | `BowItem` |
| `Brain` | `Brain` |
| `BSPH` | `BlockStatePredictionHandler` |
| `CA` | `ChunkAccess` |
| `CAdv` | `ClientAdvancements` |
| `CallF` | `CallFunction` |
| `Camera` | `Camera` |
| `CBE` | `ChestBlockEntity` |
| `CCC` | `ClientChunkCache` |
| `CComPL` | `ClientCommonPacketListenerImpl` |
| `CCPL` | `ClientConfigurationPacketListenerImpl` |
| `CDS` | `ClientDebugSubscriber` |
| `CE` | `CommandEncoder` |
| `CF` | `ConfiguredFeature` |
| `CG` | `CollisionGetter` |
| `CGT` | `ChunkGenerationTask` |
| `CH` | `ChunkHolder` |
| `ChanA` | `ChannelAccess` |
| `Channel` | `Channel` |
| `ChatC` | `ChatComponent` |
| `CHelp` | `ContainerHelper` |
| `ChestM` | `ChestMenu` |
| `ChestR` | `ChestRenderer` |
| `CHPL` | `ClientHandshakePacketListenerImpl` |
| `ChunkG` | `ChunkGenerator` |
| `CI` | `CraftingInput` |
| `CL` | `ClientLevel` |
| `ClimS` | `Climate.Sampler` |
| `CLis` | `ChatListener` |
| `CM` | `ChunkMap` |
| `CMap` | `ContextMap` |
| `Cmds` | `Commands` |
| `CMTE` | `ChunkMap.TrackedEntity` |
| `CNU` | `CollectingNeighborUpdater` |
| `Comp` | `Component` |
| `Conn` | `Connection` |
| `Cons` | `Consumable` |
| `ContT` | `ContinuationTask` |
| `CPL` | `ClientPacketListener` |
| `CPList` | `Climate.ParameterList` |
| `CR` | `CombatRules` |
| `CraftM` | `CraftingMenu` |
| `CRT` | `Climate.RTree` |
| `CRU` | `ComponentRenderUtils` |
| `CS` | `ComponentSerialization` |
| `CScr` | `ChatScreen` |
| `CSP` | `ClientSuggestionProvider` |
| `CSR` | `ChestSpecialRenderer` |
| `CST` | `ChunkStatusTasks` |
| `CSug` | `CommandSuggestions` |
| `CSync` | `ContainerSynchronizer` |
| `CT` | `CombatTracker` |
| `CTD` | `ChunkTaskDispatcher` |
| `CU` | `ComponentUtils` |
| `CWS` | `CreateWorldScreen` |
| `DataC` | `DataCommands` |
| `DB` | `DoorBlock` |
| `DCP` | `DataComponentPatch` |
| `DL` | `DirectoryLock` |
| `DlgC` | `DialogCommand` |
| `DlgS` | `DialogScreen` |
| `DM` | `DistanceManager` |
| `DMR` | `DefaultedMappedRegistry` |
| `DRWE` | `DefaultRedstoneWireEvaluator` |
| `DS` | `DedicatedServer` |
| `DScr` | `DeathScreen` |
| `EAP` | `EnvironmentAttributeProbe` |
| `EAS` | `EnvironmentAttributeSystem` |
| `EC` | `ExecutionContext` |
| `EffC` | `EffectCommands` |
| `EH` | `EnchantmentHelper` |
| `EM` | `EnchantmentMenu` |
| `Ench` | `Enchantment` |
| `Entity` | `Entity` |
| `ERD` | `EntityRenderDispatcher` |
| `ES` | `EntityStorage` |
| `EScr` | `EnchantmentScreen` |
| `ET` | `EntityType` |
| `ETL` | `EntityTickList` |
| `EVS` | `EnvironmentAttributeSystem.ValueSampler` |
| `ExecC` | `ExecuteCommand` |
| `FBR` | `FormattedBidiReorder` |
| `FD` | `FoodData` |
| `FF` | `FlowingFluid` |
| `FGB` | `FrameGraphBuilder` |
| `FM` | `FurnaceMenu` |
| `FolP` | `FoliagePlacer` |
| `Font` | `Font` |
| `FP` | `FoodProperties` |
| `FR` | `FogRenderer` |
| `FRD` | `FeatureRenderDispatcher` |
| `FS` | `FeatureSorter` |
| `FSet` | `FontSet` |
| `GB` | `GpuBackend` |
| `GC` | `GiveCommand` |
| `GD` | `GpuDevice` |
| `GED` | `GameEventDispatcher` |
| `GGE` | `GuiGraphicsExtractor` |
| `GI` | `GameTestInfo` |
| `GlCE` | `GlCommandEncoder` |
| `GLX` | `GLX` |
| `GpuS` | `GpuSurface` |
| `GR` | `GameRenderer` |
| `GS` | `GaussianSampler` |
| `GStit` | `GlyphStitcher` |
| `GTR` | `GameTestRunner` |
| `GTT` | `GameTestTicker` |
| `Gui` | `Gui` |
| `GuiR` | `GuiRenderer` |
| `HS` | `HashedStack` |
| `Hud` | `Hud` |
| `ICT` | `InventoryChangeTrigger` |
| `Ignite` | `Ignite` |
| `IIHR` | `ItemInHandRenderer` |
| `IMR` | `ItemModelResolver` |
| `Inv` | `Inventory` |
| `InvS` | `InventoryScreen` |
| `IOW` | `IOWorker` |
| `IP` | `ItemParser` |
| `IS` | `IntegratedServer` |
| `IStack` | `ItemStack` |
| `Item` | `Item` |
| `Items` | `Items` |
| `JPP` | `JigsawPlacement.Placer` |
| `JS` | `JigsawStructure` |
| `JWT` | `JoinWorldTask` |
| `KH` | `KeyboardHandler` |
| `KI` | `KeyboardInput` |
| `KM` | `KeyMapping` |
| `KTS` | `KeyframeTrackSampler` |
| `Language` | `Language` |
| `LB` | `LiquidBlock` |
| `LC` | `LevelChunk` |
| `LCS` | `LevelChunkSection` |
| `LCT` | `LoadingChunkTracker` |
| `LCTs` | `LevelChunkTicks` |
| `LDS` | `LevelDebugSynchronizers` |
| `LE` | `LivingEntity` |
| `LEH` | `LevelEventHandler` |
| `LevB` | `LeverBlock` |
| `Level` | `Level` |
| `Library` | `Library` |
| `LIC` | `LootItemCondition` |
| `LIF` | `LootItemFunctions` |
| `LLE` | `LevelLightEngine` |
| `LLSS` | `LayerLightSectionStorage` |
| `LM` | `Lightmap` |
| `LO` | `LoadingOverlay` |
| `LootC` | `LootContext` |
| `LootP` | `LootParams` |
| `LP` | `LocalPlayer` |
| `LPool` | `LootPool` |
| `LR` | `LevelRenderer` |
| `LRA` | `LayeredRegistryAccess` |
| `LRSE` | `LightmapRenderStateExtractor` |
| `LSA` | `LevelStorageSource.LevelStorageAccess` |
| `LT` | `LootTable` |
| `LTs` | `LevelTicks` |
| `LX` | `LevelExtractor` |
| `MB` | `ModelBakery` |
| `MC` | `Minecraft` |
| `MComp` | `MutableComponent` |
| `ME` | `MobEffect` |
| `MEI` | `MobEffectInstance` |
| `MH` | `MouseHandler` |
| `MM` | `ModelManager` |
| `MNBS` | `MultiNoiseBiomeSource` |
| `Mob` | `Mob` |
| `MonM` | `MonitorManager` |
| `MoveC` | `MoveControl` |
| `MPGM` | `MultiPlayerGameMode` |
| `MPRM` | `MultiPackResourceManager` |
| `MR` | `MappedRegistry` |
| `MS` | `MinecraftServer` |
| `MTS` | `MoveToTargetSink` |
| `NBC` | `NoiseBasedChunkGenerator` |
| `NbtIo` | `NbtIo` |
| `NC` | `NoiseChunk` |
| `NE` | `NodeEvaluator` |
| `NS` | `NaturalSpawner` |
| `PA` | `PlayerAdvancements` |
| `Parrot` | `Parrot` |
| `PBB` | `PistonBaseBlock` |
| `PChain` | `PostChain` |
| `PCS` | `PlayerChunkSender` |
| `PDec` | `PacketDecoder` |
| `PDM` | `PatchedDataComponentMap` |
| `PDS` | `PlayerDataStorage` |
| `PE` | `ParticleEngine` |
| `PEnc` | `PacketEncoder` |
| `PESM` | `PersistentEntitySectionManager` |
| `PESP` | `PoolElementStructurePiece` |
| `PF` | `PathFinder` |
| `PL` | `PlayerList` |
| `PlacedF` | `PlacedFeature` |
| `Player` | `Player` |
| `PM` | `PoiManager` |
| `PMBE` | `PistonMovingBlockEntity` |
| `PMod` | `PlacementModifier` |
| `PN` | `PathNavigation` |
| `PNR` | `PathNavigationRegion` |
| `PP` | `PacketProcessor` |
| `PPass` | `PostPass` |
| `PR` | `PackRepository` |
| `PRL` | `PreparableReloadListener` |
| `PSR` | `PistonStructureResolver` |
| `PST` | `PrepareSpawnTask` |
| `PTT` | `DistanceManager.PlayerTicketTracker` |
| `RB` | `RepeaterBlock` |
| `RC` | `ReloadCommand` |
| `RCont` | `RandomizableContainer` |
| `RCPL` | `ClientPacketListener` |
| `RDC` | `RegistryDataCollector` |
| `RDL` | `RegistryDataLoader` |
| `RemS` | `RemoteSlot` |
| `ResultC` | `ResultContainer` |
| `ResultS` | `ResultSlot` |
| `RGL` | `ReportGameListener` |
| `RLT` | `RegistryLoadTask` |
| `RM` | `RecipeManager` |
| `RMRLT` | `ResourceManagerRegistryLoadTask` |
| `RootP` | `RootPlacer` |
| `RP` | `RenderPass` |
| `RRM` | `ReloadableResourceManager` |
| `RS` | `RenderSystem` |
| `RSR` | `ReloadableServerResources` |
| `RSReg` | `ReloadableServerRegistries` |
| `RSWB` | `RedStoneWireBlock` |
| `RSyn` | `RegistrySynchronization` |
| `SA` | `ScoreAccess` |
| `SAI` | `SpatialAttributeInterpolator` |
| `SBL` | `SoundBufferLibrary` |
| `SC` | `StopCommand` |
| `SCC` | `ServerChunkCache` |
| `SCD` | `SerializableChunkData` |
| `SCL` | `ServerConnectionListener` |
| `SCM` | `ServerClockManager` |
| `SCPL` | `ServerConfigurationPacketListenerImpl` |
| `Screen` | `Screen` |
| `SCT` | `SimulationChunkTracker` |
| `SDS` | `ServerDebugSubscribers` |
| `SE` | `ServerEntity` |
| `SectC` | `SectionCompiler` |
| `SED` | `SynchedEntityData` |
| `SFL` | `ServerFunctionLibrary` |
| `SFM` | `ServerFunctionManager` |
| `SGPL` | `ServerGamePacketListenerImpl` |
| `ShadM` | `ShaderManager` |
| `Shapes` | `Shapes` |
| `Sheep` | `Sheep` |
| `SHPL` | `ServerHandshakePacketListenerImpl` |
| `SIB` | `SleepInBed` |
| `SICF` | `SetItemCountFunction` |
| `SL` | `ServerLevel` |
| `SLPL` | `ServerLoginPacketListenerImpl` |
| `SndE` | `SoundEngine` |
| `SndM` | `SoundManager` |
| `SNS` | `SubmitNodeStorage` |
| `SOG` | `SectionOcclusionGraph` |
| `SP` | `ServerPlayer` |
| `SPB` | `StructurePiecesBuilder` |
| `SPGM` | `ServerPlayerGameMode` |
| `SPie` | `StrongholdPieces` |
| `SprL` | `SpriteLoader` |
| `SR` | `SkyRenderer` |
| `SRB` | `ServerRecipeBook` |
| `SRD` | `SectionRenderDispatcher` |
| `SRI` | `SimpleReloadInstance` |
| `SRT` | `SynchronizeRegistriesTask` |
| `SS` | `ServerScoreboard` |
| `SSB` | `SculkSensorBlock` |
| `SSPL` | `ServerStatusPacketListenerImpl` |
| `SSpl` | `StringSplitter` |
| `SStart` | `StructureStart` |
| `SStr` | `StrongholdStructure` |
| `STemp` | `StructureTemplate` |
| `STP` | `StructureTemplatePool` |
| `SumC` | `SummonCommand` |
| `SUT` | `SectionUpdateTracker` |
| `SW` | `ServerWatchdog` |
| `TA` | `TextureAtlas` |
| `TagP` | `TagParser` |
| `TC` | `TestCommand` |
| `TCTD` | `ThrottlingChunkTaskDispatcher` |
| `TDec` | `TreeDecorator` |
| `TDSS` | `TrackingDebugSynchronizer.SourceSynchronizer` |
| `TF` | `TreeFeature` |
| `TIB` | `TestInstanceBlockEntity` |
| `Time` | `Timelines` |
| `TL` | `TagLoader` |
| `TLE` | `ThreadedLevelLightEngine` |
| `TP` | `TrunkPlacer` |
| `TrC` | `TranslatableContents` |
| `TRM` | `ServerTickRateManager` |
| `TS` | `TicketStorage` |
| `TVO` | `TagValueOutput` |
| `UAFS` | `UpdateActivityFromSchedule` |
| `VNP` | `ValidateNearbyPoi` |
| `VSel` | `VibrationSelector` |
| `VSL` | `VibrationSystem.Listener` |
| `VST` | `VibrationSystem.Ticker` |
| `WB` | `WorldBorder` |
| `WCUS` | `WorldCreationUiState` |
| `WGL` | `WorldGenLevel` |
| `Window` | `Window` |
| `WL` | `WorldLoader` |
| `WOF` | `WorldOpenFlows` |
| `WR` | `WorldgenRandom` |
| `WS` | `WorldStem` |
| `ZM` | `ZombieModel` |
| `ZR` | `ZombieRenderer` |
| `ZS` | `ZombieRenderState` |
| `Auth` | *the User Authenticator thread, not a class* |
| `Disk` | *the save on disk, not a class* |
| `Game` | *the game's own code above Blaze3D, not a class* |
| `Hook` | *the Server Shutdown Thread JVM hook, not a class* |
| `JVM` | *the process itself, not a class* |
| `Main` | *the JVM main thread, running whichever program's Main the diagram is about — the server's or the client's, so not one class* |
| `Netty` | *the Netty event loop, not a class* |
| `Wire` | *the network between the two programs, not a class* |
| `Worker` | *the `Util.backgroundExecutor` pool, not a class* |
---
# Class index
> Generated by `python tools/verify_names.py --index` on every deploy. Do not edit by hand.
Every class **backticked** on a page — a system page, a Reference page, the atlas, the
introduction or the lecture map — and the pages that name it. Two things are outside it:
a class named only inside a diagram, because the index reads backticked names and a
mermaid label is not one, and the generated views, whose backticks are registry and packet
ids rather than class names.
2739 names across 131 pages. A row is a
simple name, not a class: a few names belong to more than one class (there are five
`Main`s), and a few are library classes from Brigadier, DataFixerUpper or authlib.
| class | pages |
|---|---|
| `AABB` | [math-and-primitives](../reference/math-and-primitives.md), [movement-and-collision](../systems/entities/movement-and-collision.md) |
| `Abilities` | [entity-anatomy](../systems/entities/entity-anatomy.md), [input-to-movement](../systems/player/input-to-movement.md), [player-anatomy](../systems/player/player-anatomy.md) |
| `AboveRootPlacement` | [trees](../systems/worldgen/trees.md) |
| `AbstractArrow` | [naming-drift](../reference/naming-drift.md), [non-living-damage](../reference/non-living-damage.md), [the-client-level](../systems/client/the-client-level.md), [damage-and-death](../systems/entities/damage-and-death.md), [enchantments](../systems/items/enchantments.md), [what-the-client-is-told](../systems/networking/what-the-client-is-told.md), [the-sword-swing](../systems/player/the-sword-swing.md) |
| `AbstractBoat` | [the-client-level](../systems/client/the-client-level.md), [ai-goals-and-brains](../systems/entities/ai-goals-and-brains.md), [authority](../systems/entities/authority.md), [input-to-movement](../systems/player/input-to-movement.md) |
| `AbstractButton` | [gui-and-screens](../systems/client/gui-and-screens.md) |
| `AbstractClientPlayer` | [attributes](../systems/entities/attributes.md), [entity-anatomy](../systems/entities/entity-anatomy.md), [player-anatomy](../systems/player/player-anatomy.md) |
| `AbstractConsecutiveExecutor` | [chunk-generation-pipeline](../systems/world/chunk-generation-pipeline.md) |
| `AbstractContainerEventHandler` | [hierarchy](../maps/hierarchy.md) |
| `AbstractContainerMenu` | [block-entities](../systems/blocks/block-entities.md), [diodes-and-observers](../systems/blocks/diodes-and-observers.md), [advancements](../systems/commands/advancements.md), [codecs-nbt-json](../systems/foundations/codecs-nbt-json.md), [data-components](../systems/foundations/data-components.md), [containers-and-menus](../systems/items/containers-and-menus.md), [enchanting](../systems/items/enchanting.md), [loot-tables](../systems/items/loot-tables.md), [recipes](../systems/items/recipes.md), [using-an-item](../systems/items/using-an-item.md), [the-two-phase-tick](../systems/player/the-two-phase-tick.md) |
| `AbstractContainerScreen` | [hierarchy](../maps/hierarchy.md), [gui-and-screens](../systems/client/gui-and-screens.md), [the-client-loop](../systems/client/the-client-loop.md), [containers-and-menus](../systems/items/containers-and-menus.md) |
| `AbstractCookingRecipe` | [recipes](../systems/items/recipes.md) |
| `AbstractCraftingMenu` | [recipes](../systems/items/recipes.md) |
| `AbstractDebugChart` | [hud](../systems/client/hud.md) |
| `AbstractDeviceTracker` | [sound-engine](../systems/client/sound-engine.md) |
| `AbstractEndPortalRenderer` | [block-entity-rendering](../systems/rendering/block-entity-rendering.md) |
| `AbstractFurnaceBlock` | [block-entities](../systems/blocks/block-entities.md) |
| `AbstractFurnaceBlockEntity` | [block-entities](../systems/blocks/block-entities.md), [recipes](../systems/items/recipes.md) |
| `AbstractFurnaceMenu` | [block-entities](../systems/blocks/block-entities.md), [recipes](../systems/items/recipes.md) |
| `AbstractHurtingProjectile` | [non-living-damage](../reference/non-living-damage.md), [damage-and-death](../systems/entities/damage-and-death.md) |
| `AbstractMinecart` | [the-client-level](../systems/client/the-client-level.md) |
| `AbstractScrollArea` | [gui-and-screens](../systems/client/gui-and-screens.md) |
| `AbstractSelectionList` | [gui-and-screens](../systems/client/gui-and-screens.md) |
| `AbstractSignRenderer` | [block-entity-rendering](../systems/rendering/block-entity-rendering.md) |
| `AbstractSoundInstance` | [what-makes-a-sound](../systems/client/what-makes-a-sound.md) |
| `AbstractTexture` | [models-and-atlases](../systems/rendering/models-and-atlases.md) |
| `AbstractTickableSoundInstance` | [sound-engine](../systems/client/sound-engine.md) |
| `AbstractVillager` | [contexts-and-predicates](../systems/items/contexts-and-predicates.md), [loot-tables](../systems/items/loot-tables.md) |
| `AbstractWidget` | [gui-and-screens](../systems/client/gui-and-screens.md) |
| `AbstractZombieRenderer` | [entity-rendering](../systems/rendering/entity-rendering.md) |
| `AcaciaFoliagePlacer` | [trees](../systems/worldgen/trees.md) |
| `AccountProfileKeyPairManager` | [chat-and-signing](../systems/networking/chat-and-signing.md) |
| `AcquirePoi` | [ai-goals-and-brains](../systems/entities/ai-goals-and-brains.md), [pathfinding](../systems/entities/pathfinding.md), [points-of-interest](../systems/world/points-of-interest.md) |
| `Action` | [dialogs](../systems/commands/dialogs.md), [data-driven-types](../systems/foundations/data-driven-types.md), [text-components](../systems/foundations/text-components.md) |
| `ActionButton` | [dialogs](../systems/commands/dialogs.md) |
| `ActionTypes` | [dialogs](../systems/commands/dialogs.md) |
| `ActiveProfiler` | [what-this-book-skips](../systems/anatomy/what-this-book-skips.md) |
| `ActiveTextCollector` | [text-and-fonts](../systems/client/text-and-fonts.md) |
| `Activity` | [ai-goals-and-brains](../systems/entities/ai-goals-and-brains.md), [environment-attributes-and-timelines](../systems/world/environment-attributes-and-timelines.md), [points-of-interest](../systems/world/points-of-interest.md) |
| `ActivityData` | [glossary](../reference/glossary.md), [ai-goals-and-brains](../systems/entities/ai-goals-and-brains.md) |
| `AddValue` | [enchantments](../systems/items/enchantments.md) |
| `Advancement` | [advancements](../systems/commands/advancements.md) |
| `AdvancementCommands` | [advancements](../systems/commands/advancements.md) |
| `AdvancementHolder` | [naming-drift](../reference/naming-drift.md), [advancements](../systems/commands/advancements.md) |
| `AdvancementNode` | [naming-drift](../reference/naming-drift.md), [advancements](../systems/commands/advancements.md) |
| `AdvancementProgress` | [advancements](../systems/commands/advancements.md) |
| `AdvancementRequirements` | [advancements](../systems/commands/advancements.md) |
| `AdvancementRewards` | [advancements](../systems/commands/advancements.md), [functions-and-macros](../systems/commands/functions-and-macros.md), [contexts-and-predicates](../systems/items/contexts-and-predicates.md) |
| `AdvancementsScreen` | [advancements](../systems/commands/advancements.md) |
| `AdvancementTab` | [advancements](../systems/commands/advancements.md) |
| `AdvancementTabType` | [advancements](../systems/commands/advancements.md) |
| `AdvancementTree` | [naming-drift](../reference/naming-drift.md), [advancements](../systems/commands/advancements.md) |
| `AdvancementType` | [naming-drift](../reference/naming-drift.md), [advancements](../systems/commands/advancements.md) |
| `AdvancementVisibilityEvaluator` | [advancements](../systems/commands/advancements.md) |
| `AdvancementWidget` | [advancements](../systems/commands/advancements.md) |
| `AdventureModePredicate` | [block-entities](../systems/blocks/block-entities.md) |
| `AgeableMob` | [entity-anatomy](../systems/entities/entity-anatomy.md), [entity-lifecycle](../systems/entities/entity-lifecycle.md), [synched-entity-data](../systems/entities/synched-entity-data.md) |
| `AgeableMobRenderer` | [entity-rendering](../systems/rendering/entity-rendering.md) |
| `Allay` | [ai-goals-and-brains](../systems/entities/ai-goals-and-brains.md), [pathfinding](../systems/entities/pathfinding.md), [game-events-and-vibrations](../systems/world/game-events-and-vibrations.md) |
| `AllayAi` | [game-events-and-vibrations](../systems/world/game-events-and-vibrations.md) |
| `AllOf` | [enchantments](../systems/items/enchantments.md) |
| `AllOfCondition` | [data-driven-types](../systems/foundations/data-driven-types.md), [contexts-and-predicates](../systems/items/contexts-and-predicates.md) |
| `AlterGroundDecorator` | [trees](../systems/worldgen/trees.md) |
| `AlternativesEntry` | [loot-tables](../systems/items/loot-tables.md) |
| `AmbientAdditionsSettings` | [what-makes-a-sound](../systems/client/what-makes-a-sound.md) |
| `AmbientMoodSettings` | [what-makes-a-sound](../systems/client/what-makes-a-sound.md) |
| `AmbientParticle` | [data-driven-types](../systems/foundations/data-driven-types.md), [particles](../systems/rendering/particles.md) |
| `AmphibiousNodeEvaluator` | [pathfinding](../systems/entities/pathfinding.md) |
| `AmphibiousPathNavigation` | [pathfinding](../systems/entities/pathfinding.md) |
| `Animal` | [entity-anatomy](../systems/entities/entity-anatomy.md), [entity-lifecycle](../systems/entities/entity-lifecycle.md), [synched-entity-data](../systems/entities/synched-entity-data.md) |
| `AnimationDefinition` | [entity-rendering](../systems/rendering/entity-rendering.md) |
| `AnimationState` | [entity-rendering](../systems/rendering/entity-rendering.md) |
| `AnvilMenu` | [enchanting](../systems/items/enchanting.md), [enchantments](../systems/items/enchantments.md), [hunger-and-experience](../systems/player/hunger-and-experience.md) |
| `AnyBlockInteractionTrigger` | [contexts-and-predicates](../systems/items/contexts-and-predicates.md) |
| `ApplyBonusCount` | [enchantments](../systems/items/enchantments.md), [loot-tables](../systems/items/loot-tables.md) |
| `ApplyEntityImpulse` | [enchantments](../systems/items/enchantments.md) |
| `ApplyExhaustion` | [enchantments](../systems/items/enchantments.md), [hunger-and-experience](../systems/player/hunger-and-experience.md) |
| `ApplyMobEffect` | [enchantments](../systems/items/enchantments.md) |
| `ApplyStatusEffectsConsumeEffect` | [hunger-and-experience](../systems/player/hunger-and-experience.md), [status-effects](../systems/player/status-effects.md) |
| `Aquifer` | [glossary](../reference/glossary.md), [blending](../systems/worldgen/blending.md), [terrain](../systems/worldgen/terrain.md) |
| `AreaEffectCloud` | [non-living-damage](../reference/non-living-damage.md), [damage-and-death](../systems/entities/damage-and-death.md) |
| `ARGB` | [math-and-primitives](../reference/math-and-primitives.md) |
| `ArgumentSignatures` | [brigadier-and-commands](../systems/commands/brigadier-and-commands.md), [chat-and-signing](../systems/networking/chat-and-signing.md) |
| `ArgumentType` | [glossary](../reference/glossary.md) |
| `ArgumentTypeInfo` | [glossary](../reference/glossary.md), [brigadier-and-commands](../systems/commands/brigadier-and-commands.md) |
| `ArgumentTypeInfos` | [brigadier-and-commands](../systems/commands/brigadier-and-commands.md) |
| `ArgumentUtils` | [brigadier-and-commands](../systems/commands/brigadier-and-commands.md), [permissions](../systems/commands/permissions.md), [data-driven-types](../systems/foundations/data-driven-types.md) |
| `ArgumentVisitor` | [brigadier-and-commands](../systems/commands/brigadier-and-commands.md) |
| `ArmedEntityRenderState` | [entity-rendering](../systems/rendering/entity-rendering.md) |
| `ArmorModelSet` | [entity-rendering](../systems/rendering/entity-rendering.md) |
| `ArmorSlot` | [enchantments](../systems/items/enchantments.md) |
| `ArmorStand` | [math-and-primitives](../reference/math-and-primitives.md), [authority](../systems/entities/authority.md), [damage-and-death](../systems/entities/damage-and-death.md), [entity-anatomy](../systems/entities/entity-anatomy.md), [player-anatomy](../systems/player/player-anatomy.md) |
| `ArrayVoxelShape` | [math-and-primitives](../reference/math-and-primitives.md) |
| `AssignProfessionFromJobSite` | [ai-goals-and-brains](../systems/entities/ai-goals-and-brains.md), [points-of-interest](../systems/world/points-of-interest.md) |
| `AtlasGlyphProvider` | [text-and-fonts](../systems/client/text-and-fonts.md) |
| `AtlasIds` | [what-this-book-skips](../systems/anatomy/what-this-book-skips.md), [lightmap-fog-and-sky](../systems/rendering/lightmap-fog-and-sky.md), [models-and-atlases](../systems/rendering/models-and-atlases.md) |
| `AtlasManager` | [naming-drift](../reference/naming-drift.md), [resource-system](../systems/foundations/resource-system.md), [models-and-atlases](../systems/rendering/models-and-atlases.md) |
| `AtlasSprite` | [text-components](../systems/foundations/text-components.md) |
| `AtmosphericFogEnvironment` | [lightmap-fog-and-sky](../systems/rendering/lightmap-fog-and-sky.md), [environment-attributes-and-timelines](../systems/world/environment-attributes-and-timelines.md) |
| `AttachedToLeavesDecorator` | [trees](../systems/worldgen/trees.md) |
| `AttachedToLogsDecorator` | [trees](../systems/worldgen/trees.md) |
| `AttachFace` | [math-and-primitives](../reference/math-and-primitives.md) |
| `Attackable` | [entity-anatomy](../systems/entities/entity-anatomy.md) |
| `AttackRange` | [data-components](../systems/foundations/data-components.md), [the-spear](../systems/player/the-spear.md), [the-sword-swing](../systems/player/the-sword-swing.md) |
| `Attribute` | [attributes](../systems/entities/attributes.md) |
| `AttributeCommand` | [attributes](../systems/entities/attributes.md) |
| `AttributeInstance` | [attributes](../systems/entities/attributes.md), [status-effects](../systems/player/status-effects.md) |
| `AttributeMap` | [naming-drift](../reference/naming-drift.md), [attributes](../systems/entities/attributes.md), [what-the-client-is-told](../systems/networking/what-the-client-is-told.md) |
| `AttributeModifier` | [naming-drift](../reference/naming-drift.md), [attributes](../systems/entities/attributes.md), [enchantments](../systems/items/enchantments.md), [environment-attributes-and-timelines](../systems/world/environment-attributes-and-timelines.md), [biomes](../systems/worldgen/biomes.md) |
| `AttributeRange` | [environment-attributes-and-timelines](../systems/world/environment-attributes-and-timelines.md) |
| `Attributes` | [naming-drift](../reference/naming-drift.md), [block-breaking](../systems/blocks/block-breaking.md), [scoreboard-and-data](../systems/commands/scoreboard-and-data.md), [ai-goals-and-brains](../systems/entities/ai-goals-and-brains.md), [attributes](../systems/entities/attributes.md), [damage-and-death](../systems/entities/damage-and-death.md), [entity-anatomy](../systems/entities/entity-anatomy.md), [entity-lifecycle](../systems/entities/entity-lifecycle.md), [movement-and-collision](../systems/entities/movement-and-collision.md), [pathfinding](../systems/entities/pathfinding.md), [input-to-movement](../systems/player/input-to-movement.md), [player-anatomy](../systems/player/player-anatomy.md), [status-effects](../systems/player/status-effects.md), [the-spear](../systems/player/the-spear.md), [the-sword-swing](../systems/player/the-sword-swing.md) |
| `AttributeSupplier` | [attributes](../systems/entities/attributes.md) |
| `AttributeTrack` | [glossary](../reference/glossary.md), [environment-attributes-and-timelines](../systems/world/environment-attributes-and-timelines.md) |
| `AttributeTrackSampler` | [environment-attributes-and-timelines](../systems/world/environment-attributes-and-timelines.md) |
| `AttributeType` | [environment-attributes-and-timelines](../systems/world/environment-attributes-and-timelines.md) |
| `AttributeTypes` | [data-driven-types](../systems/foundations/data-driven-types.md), [environment-attributes-and-timelines](../systems/world/environment-attributes-and-timelines.md) |
| `AudioStream` | [sound-engine](../systems/client/sound-engine.md) |
| `Avatar` | [glossary](../reference/glossary.md), [VI · Entities](../systems/entities/README.md), [attributes](../systems/entities/attributes.md), [entity-anatomy](../systems/entities/entity-anatomy.md), [synched-entity-data](../systems/entities/synched-entity-data.md), [VIII · The player](../systems/player/README.md), [player-anatomy](../systems/player/player-anatomy.md) |
| `AvatarRenderer` | [glossary](../reference/glossary.md), [naming-drift](../reference/naming-drift.md), [entity-anatomy](../systems/entities/entity-anatomy.md), [player-anatomy](../systems/player/player-anatomy.md), [entity-rendering](../systems/rendering/entity-rendering.md) |
| `AvatarRenderState` | [entity-rendering](../systems/rendering/entity-rendering.md) |
| `AxeItem` | [data-components](../systems/foundations/data-components.md) |
| `Axis` | [math-and-primitives](../reference/math-and-primitives.md) |
| `AxisCycle` | [math-and-primitives](../reference/math-and-primitives.md) |
| `Axolotl` | [ai-goals-and-brains](../systems/entities/ai-goals-and-brains.md) |
| `BackendCreationException` | [blaze3d](../systems/rendering/blaze3d.md) |
| `BackgroundMusic` | [what-makes-a-sound](../systems/client/what-makes-a-sound.md) |
| `BackUpIfTooClose` | [pathfinding](../systems/entities/pathfinding.md) |
| `BadOmenMobEffect` | [points-of-interest](../systems/world/points-of-interest.md) |
| `BakedGlyph` | [text-and-fonts](../systems/client/text-and-fonts.md) |
| `BakedQuad` | [naming-drift](../reference/naming-drift.md), [models-and-atlases](../systems/rendering/models-and-atlases.md) |
| `BakedSheetGlyph` | [naming-drift](../reference/naming-drift.md), [text-and-fonts](../systems/client/text-and-fonts.md) |
| `BandwidthDebugChart` | [hud](../systems/client/hud.md) |
| `BandwidthDebugMonitor` | [debugging-the-running-game](../systems/client/debugging-the-running-game.md) |
| `BannerRenderer` | [block-entity-rendering](../systems/rendering/block-entity-rendering.md) |
| `BannerSpecialRenderer` | [block-entity-rendering](../systems/rendering/block-entity-rendering.md) |
| `BaseContainerBlockEntity` | [loot-tables](../systems/items/loot-tables.md) |
| `BaseEntityBlock` | [hierarchy](../maps/hierarchy.md), [block-entities](../systems/blocks/block-entities.md), [pistons-and-block-events](../systems/blocks/pistons-and-block-events.md) |
| `BaseRailBlock` | [blocks-and-states](../systems/blocks/blocks-and-states.md), [chunk-anatomy](../systems/world/chunk-anatomy.md) |
| `BaseSpawner` | [entity-lifecycle](../systems/entities/entity-lifecycle.md) |
| `BeaconRenderer` | [block-entity-rendering](../systems/rendering/block-entity-rendering.md) |
| `BeaconScreen` | [containers-and-menus](../systems/items/containers-and-menus.md) |
| `Beardifier` | [density-function-nodes](../reference/density-function-nodes.md), [density-functions](../systems/worldgen/density-functions.md), [hand-built-structures](../systems/worldgen/hand-built-structures.md), [jigsaw-and-templates](../systems/worldgen/jigsaw-and-templates.md), [structure-placement](../systems/worldgen/structure-placement.md), [terrain](../systems/worldgen/terrain.md) |
| `BedBlock` | [points-of-interest](../systems/world/points-of-interest.md) |
| `BedPart` | [points-of-interest](../systems/world/points-of-interest.md) |
| `BedRenderState` | [block-entity-rendering](../systems/rendering/block-entity-rendering.md) |
| `BedRule` | [naming-drift](../reference/naming-drift.md) |
| `Bee` | [biggest](../maps/biggest.md), [debugging-the-running-game](../systems/client/debugging-the-running-game.md), [pathfinding](../systems/entities/pathfinding.md), [points-of-interest](../systems/world/points-of-interest.md) |
| `BeehiveBlockEntity` | [debugging-the-running-game](../systems/client/debugging-the-running-game.md), [points-of-interest](../systems/world/points-of-interest.md) |
| `BeehiveDecorator` | [trees](../systems/worldgen/trees.md) |
| `Behavior` | [glossary](../reference/glossary.md), [ai-goals-and-brains](../systems/entities/ai-goals-and-brains.md) |
| `BehaviorBuilder` | [ai-goals-and-brains](../systems/entities/ai-goals-and-brains.md) |
| `BehaviorControl` | [ai-goals-and-brains](../systems/entities/ai-goals-and-brains.md) |
| `BelowZeroRetrogen` | [chunk-anatomy](../systems/world/chunk-anatomy.md), [chunk-generation-pipeline](../systems/world/chunk-generation-pipeline.md), [blending](../systems/worldgen/blending.md), [terrain](../systems/worldgen/terrain.md) |
| `BendingTrunkPlacer` | [trees](../systems/worldgen/trees.md) |
| `BinaryHeap` | [pathfinding](../systems/entities/pathfinding.md) |
| `BindGroupLayout` | [blaze3d](../systems/rendering/blaze3d.md), [post-processing](../systems/rendering/post-processing.md) |
| `BindGroupLayouts` | [naming-drift](../reference/naming-drift.md), [blaze3d](../systems/rendering/blaze3d.md) |
| `Biome` | [lectures](../lectures.md), [naming-drift](../reference/naming-drift.md), [identifiers-and-registries](../systems/foundations/identifiers-and-registries.md), [lightmap-fog-and-sky](../systems/rendering/lightmap-fog-and-sky.md), [server-level-tick](../systems/server/server-level-tick.md), [environment-attributes-and-timelines](../systems/world/environment-attributes-and-timelines.md), [XII · World generation](../systems/worldgen/README.md), [biomes](../systems/worldgen/biomes.md), [blending](../systems/worldgen/blending.md), [structure-placement](../systems/worldgen/structure-placement.md) |
| `BiomeAmbientSoundsHandler` | [what-makes-a-sound](../systems/client/what-makes-a-sound.md) |
| `BiomeColors` | [lightmap-fog-and-sky](../systems/rendering/lightmap-fog-and-sky.md), [biomes](../systems/worldgen/biomes.md) |
| `BiomeFilter` | [features-and-placement](../systems/worldgen/features-and-placement.md) |
| `BiomeGenerationSettings` | [naming-drift](../reference/naming-drift.md), [biomes](../systems/worldgen/biomes.md) |
| `BiomeManager` | [math-and-primitives](../reference/math-and-primitives.md), [chunk-anatomy](../systems/world/chunk-anatomy.md), [biomes](../systems/worldgen/biomes.md) |
| `BiomeSource` | [data-driven-types](../systems/foundations/data-driven-types.md), [biomes](../systems/worldgen/biomes.md), [structure-placement](../systems/worldgen/structure-placement.md) |
| `BiomeSpecialEffects` | [lightmap-fog-and-sky](../systems/rendering/lightmap-fog-and-sky.md), [environment-attributes-and-timelines](../systems/world/environment-attributes-and-timelines.md), [biomes](../systems/worldgen/biomes.md) |
| `BiomeTags` | [tags](../systems/foundations/tags.md) |
| `BitRandomSource` | [math-and-primitives](../reference/math-and-primitives.md) |
| `BitSetDiscreteVoxelShape` | [math-and-primitives](../reference/math-and-primitives.md) |
| `BitStorage` | [math-and-primitives](../reference/math-and-primitives.md), [chunk-anatomy](../systems/world/chunk-anatomy.md) |
| `BlackholeTickAccess` | [the-client-level](../systems/client/the-client-level.md), [fluids](../systems/world/fluids.md), [scheduled-ticks](../systems/world/scheduled-ticks.md) |
| `BlackstoneReplaceProcessor` | [hand-built-structures](../systems/worldgen/hand-built-structures.md) |
| `Blaze` | [pathfinding](../systems/entities/pathfinding.md) |
| `BlendedNoise` | [density-function-nodes](../reference/density-function-nodes.md), [density-functions](../systems/worldgen/density-functions.md), [terrain](../systems/worldgen/terrain.md) |
| `Blender` | [density-function-nodes](../reference/density-function-nodes.md), [glossary](../reference/glossary.md), [chunk-generation-pipeline](../systems/world/chunk-generation-pipeline.md), [blending](../systems/worldgen/blending.md), [density-functions](../systems/worldgen/density-functions.md), [terrain](../systems/worldgen/terrain.md) |
| `BlendFunction` | [blaze3d](../systems/rendering/blaze3d.md) |
| `BlendingData` | [glossary](../reference/glossary.md), [blending](../systems/worldgen/blending.md), [density-functions](../systems/worldgen/density-functions.md), [terrain](../systems/worldgen/terrain.md) |
| `BlindnessFogEnvironment` | [lightmap-fog-and-sky](../systems/rendering/lightmap-fog-and-sky.md) |
| `BlitRenderState` | [the-gui-render-tree](../systems/client/the-gui-render-tree.md) |
| `BlobFoliagePlacer` | [trees](../systems/worldgen/trees.md) |
| `Block` | [fanin](../maps/fanin.md), [hierarchy](../maps/hierarchy.md), [block-update-flags](../reference/block-update-flags.md), [glossary](../reference/glossary.md), [block-breaking](../systems/blocks/block-breaking.md), [block-entities](../systems/blocks/block-entities.md), [block-interaction](../systems/blocks/block-interaction.md), [blocks-and-states](../systems/blocks/blocks-and-states.md), [diodes-and-observers](../systems/blocks/diodes-and-observers.md), [pistons-and-block-events](../systems/blocks/pistons-and-block-events.md), [signal-and-dust](../systems/blocks/signal-and-dust.md), [prediction-and-acks](../systems/client/prediction-and-acks.md), [what-makes-a-sound](../systems/client/what-makes-a-sound.md), [authority](../systems/entities/authority.md), [movement-and-collision](../systems/entities/movement-and-collision.md), [data-driven-types](../systems/foundations/data-driven-types.md), [identifiers-and-registries](../systems/foundations/identifiers-and-registries.md), [tags](../systems/foundations/tags.md), [contexts-and-predicates](../systems/items/contexts-and-predicates.md), [enchantments](../systems/items/enchantments.md), [items-and-stacks](../systems/items/items-and-stacks.md), [particles](../systems/rendering/particles.md), [server-level-tick](../systems/server/server-level-tick.md), [chunk-anatomy](../systems/world/chunk-anatomy.md), [fluids](../systems/world/fluids.md), [scheduled-ticks](../systems/world/scheduled-ticks.md) |
| `BlockableEventLoop` | [glossary](../reference/glossary.md), [threads](../reference/threads.md), [anatomy](../systems/anatomy/anatomy.md), [input-and-keybinds](../systems/client/input-and-keybinds.md), [sound-engine](../systems/client/sound-engine.md), [the-client-loop](../systems/client/the-client-loop.md), [resource-system](../systems/foundations/resource-system.md), [input-to-movement](../systems/player/input-to-movement.md), [how-a-server-dies](../systems/server/how-a-server-dies.md), [players-and-sessions](../systems/server/players-and-sessions.md), [server-tick](../systems/server/server-tick.md), [starting-a-server](../systems/server/starting-a-server.md), [chunk-generation-pipeline](../systems/world/chunk-generation-pipeline.md), [points-of-interest](../systems/world/points-of-interest.md), [tickets-and-loading](../systems/world/tickets-and-loading.md), [creating-a-world](../systems/worldgen/creating-a-world.md) |
| `BlockAgeProcessor` | [hand-built-structures](../systems/worldgen/hand-built-structures.md) |
| `BlockAndLightGetter` | [lighting](../systems/world/lighting.md) |
| `BlockAttachedEntity` | [non-living-damage](../reference/non-living-damage.md), [damage-and-death](../systems/entities/damage-and-death.md), [entity-anatomy](../systems/entities/entity-anatomy.md), [what-the-client-is-told](../systems/networking/what-the-client-is-told.md), [the-sword-swing](../systems/player/the-sword-swing.md) |
| `BlockBasedTestInstance` | [game-tests](../systems/commands/game-tests.md) |
| `BlockBehaviour` | [fanin](../maps/fanin.md), [hierarchy](../maps/hierarchy.md), [block-update-flags](../reference/block-update-flags.md), [math-and-primitives](../reference/math-and-primitives.md), [naming-drift](../reference/naming-drift.md), [block-breaking](../systems/blocks/block-breaking.md), [block-entities](../systems/blocks/block-entities.md), [block-interaction](../systems/blocks/block-interaction.md), [blocks-and-states](../systems/blocks/blocks-and-states.md), [pistons-and-block-events](../systems/blocks/pistons-and-block-events.md), [signal-and-dust](../systems/blocks/signal-and-dust.md), [prediction-and-acks](../systems/client/prediction-and-acks.md), [movement-and-collision](../systems/entities/movement-and-collision.md), [identifiers-and-registries](../systems/foundations/identifiers-and-registries.md), [tags](../systems/foundations/tags.md), [contexts-and-predicates](../systems/items/contexts-and-predicates.md), [loot-tables](../systems/items/loot-tables.md), [particles](../systems/rendering/particles.md), [server-level-tick](../systems/server/server-level-tick.md), [chunk-anatomy](../systems/world/chunk-anatomy.md), [fluids](../systems/world/fluids.md), [lighting](../systems/world/lighting.md), [scheduled-ticks](../systems/world/scheduled-ticks.md) |
| `BlockBox` | [math-and-primitives](../reference/math-and-primitives.md) |
| `BlockCollisions` | [math-and-primitives](../reference/math-and-primitives.md), [movement-and-collision](../systems/entities/movement-and-collision.md) |
| `BlockColors` | [models-and-atlases](../systems/rendering/models-and-atlases.md) |
| `BlockDataAccessor` | [scoreboard-and-data](../systems/commands/scoreboard-and-data.md) |
| `BlockDataSource` | [text-components](../systems/foundations/text-components.md) |
| `BlockDestructionProgress` | [block-breaking](../systems/blocks/block-breaking.md) |
| `BlockEntity` | [block-update-flags](../reference/block-update-flags.md), [naming-drift](../reference/naming-drift.md), [block-breaking](../systems/blocks/block-breaking.md), [block-entities](../systems/blocks/block-entities.md), [blocks-and-states](../systems/blocks/blocks-and-states.md), [diodes-and-observers](../systems/blocks/diodes-and-observers.md), [debugging-the-running-game](../systems/client/debugging-the-running-game.md), [codecs-nbt-json](../systems/foundations/codecs-nbt-json.md), [data-components](../systems/foundations/data-components.md), [containers-and-menus](../systems/items/containers-and-menus.md), [what-the-client-is-told](../systems/networking/what-the-client-is-told.md), [server-level-tick](../systems/server/server-level-tick.md), [chunk-anatomy](../systems/world/chunk-anatomy.md) |
| `BlockEntityRenderDispatcher` | [resource-system](../systems/foundations/resource-system.md), [block-entity-rendering](../systems/rendering/block-entity-rendering.md) |
| `BlockEntityRenderer` | [glossary](../reference/glossary.md), [block-entity-rendering](../systems/rendering/block-entity-rendering.md) |
| `BlockEntityRenderers` | [block-entity-rendering](../systems/rendering/block-entity-rendering.md) |
| `BlockEntityRenderState` | [block-entity-rendering](../systems/rendering/block-entity-rendering.md) |
| `BlockEntityTicker` | [chunk-anatomy](../systems/world/chunk-anatomy.md) |
| `BlockEntityType` | [block-entities](../systems/blocks/block-entities.md), [block-entity-rendering](../systems/rendering/block-entity-rendering.md) |
| `BlockEntityTypes` | [block-entities](../systems/blocks/block-entities.md), [block-entity-rendering](../systems/rendering/block-entity-rendering.md) |
| `BlockEntityWithBoundingBoxRenderer` | [block-entity-rendering](../systems/rendering/block-entity-rendering.md) |
| `BlockEventData` | [pistons-and-block-events](../systems/blocks/pistons-and-block-events.md) |
| `BlockGetter` | [movement-and-collision](../systems/entities/movement-and-collision.md), [game-events-and-vibrations](../systems/world/game-events-and-vibrations.md) |
| `BlockHitResult` | [math-and-primitives](../reference/math-and-primitives.md), [block-interaction](../systems/blocks/block-interaction.md) |
| `BlockIds` | [what-this-book-skips](../systems/anatomy/what-this-book-skips.md), [blocks-and-states](../systems/blocks/blocks-and-states.md) |
| `BlockIgnoreProcessor` | [jigsaw-and-templates](../systems/worldgen/jigsaw-and-templates.md) |
| `BlockInput` | [block-update-flags](../reference/block-update-flags.md), [block-entities](../systems/blocks/block-entities.md) |
| `BlockItem` | [naming-drift](../reference/naming-drift.md), [blocks-and-states](../systems/blocks/blocks-and-states.md), [data-components](../systems/foundations/data-components.md), [section-meshing](../systems/rendering/section-meshing.md) |
| `BlockItemIds` | [what-this-book-skips](../systems/anatomy/what-this-book-skips.md), [blocks-and-states](../systems/blocks/blocks-and-states.md), [identifiers-and-registries](../systems/foundations/identifiers-and-registries.md) |
| `BlockItemTagId` | [tags](../systems/foundations/tags.md) |
| `BlockItemTags` | [tags](../systems/foundations/tags.md) |
| `BlockLightEngine` | [lighting](../systems/world/lighting.md) |
| `BlockListReport` | [data-driven-types](../systems/foundations/data-driven-types.md) |
| `BlockMarker` | [models-and-atlases](../systems/rendering/models-and-atlases.md), [particles](../systems/rendering/particles.md) |
| `BlockMath` | [math-and-primitives](../reference/math-and-primitives.md) |
| `BlockModel` | [models-and-atlases](../systems/rendering/models-and-atlases.md) |
| `BlockModelFeatureRenderer` | [submit-phases](../reference/submit-phases.md) |
| `BlockModelGenerators` | [biggest](../maps/biggest.md) |
| `BlockModelLighter` | [section-meshing](../systems/rendering/section-meshing.md) |
| `BlockModelResolver` | [block-entity-rendering](../systems/rendering/block-entity-rendering.md) |
| `BlockModelSet` | [naming-drift](../reference/naming-drift.md) |
| `BlockPos` | [fanin](../maps/fanin.md), [math-and-primitives](../reference/math-and-primitives.md), [V · Blocks](../systems/blocks/README.md), [signal-and-dust](../systems/blocks/signal-and-dust.md), [scheduled-ticks](../systems/world/scheduled-ticks.md) |
| `BlockPosArgument` | [brigadier-and-commands](../systems/commands/brigadier-and-commands.md) |
| `BlockPositionSource` | [game-events-and-vibrations](../systems/world/game-events-and-vibrations.md) |
| `BlockPredicate` | [data-driven-types](../systems/foundations/data-driven-types.md), [features-and-placement](../systems/worldgen/features-and-placement.md) |
| `BlockPredicateType` | [data-driven-types](../systems/foundations/data-driven-types.md) |
| `BlockRotProcessor` | [jigsaw-and-templates](../systems/worldgen/jigsaw-and-templates.md) |
| `Blocks` | [biggest](../maps/biggest.md), [fanin](../maps/fanin.md), [packages](../maps/packages.md), [naming-drift](../reference/naming-drift.md), [what-this-book-skips](../systems/anatomy/what-this-book-skips.md), [block-breaking](../systems/blocks/block-breaking.md), [block-interaction](../systems/blocks/block-interaction.md), [blocks-and-states](../systems/blocks/blocks-and-states.md), [diodes-and-observers](../systems/blocks/diodes-and-observers.md), [pistons-and-block-events](../systems/blocks/pistons-and-block-events.md), [signal-and-dust](../systems/blocks/signal-and-dust.md), [movement-and-collision](../systems/entities/movement-and-collision.md), [identifiers-and-registries](../systems/foundations/identifiers-and-registries.md), [particles](../systems/rendering/particles.md), [chunk-anatomy](../systems/world/chunk-anatomy.md), [fluids](../systems/world/fluids.md), [lighting](../systems/world/lighting.md) |
| `BlocksAttacks` | [damage-and-death](../systems/entities/damage-and-death.md), [data-components](../systems/foundations/data-components.md) |
| `BlockSetType` | [block-interaction](../systems/blocks/block-interaction.md) |
| `BlockState` | [fanin](../maps/fanin.md), [glossary](../reference/glossary.md), [naming-drift](../reference/naming-drift.md), [blocks-and-states](../systems/blocks/blocks-and-states.md), [identifiers-and-registries](../systems/foundations/identifiers-and-registries.md), [models-and-atlases](../systems/rendering/models-and-atlases.md), [the-frame](../systems/rendering/the-frame.md), [fluids](../systems/world/fluids.md) |
| `BlockStateArgument` | [brigadier-and-commands](../systems/commands/brigadier-and-commands.md) |
| `BlockStateData` | [biggest](../maps/biggest.md) |
| `BlockStateModel` | [naming-drift](../reference/naming-drift.md), [models-and-atlases](../systems/rendering/models-and-atlases.md) |
| `BlockStateModelDispatcher` | [models-and-atlases](../systems/rendering/models-and-atlases.md) |
| `BlockStateModelLoader` | [models-and-atlases](../systems/rendering/models-and-atlases.md) |
| `BlockStateModelPart` | [submit-phases](../reference/submit-phases.md), [models-and-atlases](../systems/rendering/models-and-atlases.md) |
| `BlockStateModelSet` | [naming-drift](../reference/naming-drift.md), [block-entity-rendering](../systems/rendering/block-entity-rendering.md), [models-and-atlases](../systems/rendering/models-and-atlases.md), [section-meshing](../systems/rendering/section-meshing.md) |
| `BlockStateModelWrapper` | [models-and-atlases](../systems/rendering/models-and-atlases.md) |
| `BlockStateParser` | [blocks-and-states](../systems/blocks/blocks-and-states.md), [brigadier-and-commands](../systems/commands/brigadier-and-commands.md) |
| `BlockStatePredictionHandler` | [glossary](../reference/glossary.md), [prediction-and-acks](../systems/client/prediction-and-acks.md), [input-to-movement](../systems/player/input-to-movement.md) |
| `BlockStateProperties` | [blocks-and-states](../systems/blocks/blocks-and-states.md), [fluids](../systems/world/fluids.md), [trees](../systems/worldgen/trees.md) |
| `BlockStateProvider` | [data-driven-types](../systems/foundations/data-driven-types.md), [trees](../systems/worldgen/trees.md) |
| `BlockStateProviderType` | [data-driven-types](../systems/foundations/data-driven-types.md) |
| `BlockTags` | [block-breaking](../systems/blocks/block-breaking.md), [block-interaction](../systems/blocks/block-interaction.md), [pistons-and-block-events](../systems/blocks/pistons-and-block-events.md), [movement-and-collision](../systems/entities/movement-and-collision.md), [tags](../systems/foundations/tags.md), [enchanting](../systems/items/enchanting.md), [game-events-and-vibrations](../systems/world/game-events-and-vibrations.md), [points-of-interest](../systems/world/points-of-interest.md) |
| `BlockTintCache` | [the-client-level](../systems/client/the-client-level.md), [biomes](../systems/worldgen/biomes.md) |
| `BlockTypes` | [data-driven-types](../systems/foundations/data-driven-types.md) |
| `BlockUtil` | [math-and-primitives](../reference/math-and-primitives.md) |
| `BodyRotationControl` | [pathfinding](../systems/entities/pathfinding.md) |
| `Bogged` | [synched-entity-data](../systems/entities/synched-entity-data.md) |
| `BonusLevelTableCondition` | [enchantments](../systems/items/enchantments.md), [loot-tables](../systems/items/loot-tables.md) |
| `BooleanInput` | [dialogs](../systems/commands/dialogs.md) |
| `BooleanModifier` | [environment-attributes-and-timelines](../systems/world/environment-attributes-and-timelines.md) |
| `BooleanOp` | [math-and-primitives](../reference/math-and-primitives.md) |
| `BooleanProperty` | [blocks-and-states](../systems/blocks/blocks-and-states.md) |
| `Bootstrap` | [anatomy](../systems/anatomy/anatomy.md), [brigadier-and-commands](../systems/commands/brigadier-and-commands.md), [entity-selectors](../systems/commands/entity-selectors.md), [data-driven-types](../systems/foundations/data-driven-types.md), [identifiers-and-registries](../systems/foundations/identifiers-and-registries.md), [how-a-server-dies](../systems/server/how-a-server-dies.md), [starting-a-server](../systems/server/starting-a-server.md) |
| `BootstrapContext` | [what-this-book-skips](../systems/anatomy/what-this-book-skips.md), [identifiers-and-registries](../systems/foundations/identifiers-and-registries.md) |
| `BorderChangeListener` | [level-data-and-rules](../reference/level-data-and-rules.md) |
| `BorderStatus` | [level-data-and-rules](../reference/level-data-and-rules.md) |
| `BossHealthOverlay` | [hud-elements](../reference/hud-elements.md), [hud](../systems/client/hud.md) |
| `BoundingBox` | [math-and-primitives](../reference/math-and-primitives.md), [hand-built-structures](../systems/worldgen/hand-built-structures.md) |
| `BowItem` | [using-an-item](../systems/items/using-an-item.md) |
| `Brain` | [glossary](../reference/glossary.md), [ai-goals-and-brains](../systems/entities/ai-goals-and-brains.md), [entity-anatomy](../systems/entities/entity-anatomy.md), [what-the-client-is-told](../systems/networking/what-the-client-is-told.md), [environment-attributes-and-timelines](../systems/world/environment-attributes-and-timelines.md), [points-of-interest](../systems/world/points-of-interest.md) |
| `BrandPayload` | [packets-and-stream-codecs](../systems/networking/packets-and-stream-codecs.md), [protocol-phases](../systems/networking/protocol-phases.md) |
| `BreakingItemParticle` | [particles](../systems/rendering/particles.md) |
| `Breeze` | [debugging-the-running-game](../systems/client/debugging-the-running-game.md) |
| `BrigadierExceptions` | [brigadier-and-commands](../systems/commands/brigadier-and-commands.md) |
| `Brightness` | [math-and-primitives](../reference/math-and-primitives.md) |
| `BrushableBlockEntity` | [contexts-and-predicates](../systems/items/contexts-and-predicates.md) |
| `BrushableBlockRenderState` | [block-entity-rendering](../systems/rendering/block-entity-rendering.md) |
| `BrushItem` | [using-an-item](../systems/items/using-an-item.md) |
| `BubbleColumnAmbientSoundHandler` | [what-makes-a-sound](../systems/client/what-makes-a-sound.md) |
| `BubbleColumnBlock` | [fluids](../systems/world/fluids.md) |
| `Bucketable` | [entity-anatomy](../systems/entities/entity-anatomy.md) |
| `BucketItem` | [fluids](../systems/world/fluids.md) |
| `BufferBuilder` | [blaze3d](../systems/rendering/blaze3d.md) |
| `BuildContexts` | [naming-drift](../reference/naming-drift.md), [functions-and-macros](../systems/commands/functions-and-macros.md), [the-execution-engine](../systems/commands/the-execution-engine.md) |
| `BuiltInBlockModels` | [glossary](../reference/glossary.md), [block-entity-rendering](../systems/rendering/block-entity-rendering.md), [models-and-atlases](../systems/rendering/models-and-atlases.md) |
| `BuiltinDimensionTypes` | [level-data-and-rules](../reference/level-data-and-rules.md) |
| `BuiltInLootTables` | [naming-drift](../reference/naming-drift.md), [synched-entity-data](../systems/entities/synched-entity-data.md), [loot-tables](../systems/items/loot-tables.md) |
| `BuiltInPackSource` | [resource-system](../systems/foundations/resource-system.md) |
| `BuiltInRegistries` | [fanin](../maps/fanin.md), [density-function-nodes](../reference/density-function-nodes.md), [level-data-and-rules](../reference/level-data-and-rules.md), [naming-drift](../reference/naming-drift.md), [what-this-book-skips](../systems/anatomy/what-this-book-skips.md), [blocks-and-states](../systems/blocks/blocks-and-states.md), [debugging-the-running-game](../systems/client/debugging-the-running-game.md), [advancements](../systems/commands/advancements.md), [dialogs](../systems/commands/dialogs.md), [permissions](../systems/commands/permissions.md), [ai-goals-and-brains](../systems/entities/ai-goals-and-brains.md), [attributes](../systems/entities/attributes.md), [data-components](../systems/foundations/data-components.md), [data-driven-types](../systems/foundations/data-driven-types.md), [identifiers-and-registries](../systems/foundations/identifiers-and-registries.md), [resource-system](../systems/foundations/resource-system.md), [tags](../systems/foundations/tags.md), [containers-and-menus](../systems/items/containers-and-menus.md), [enchantments](../systems/items/enchantments.md), [items-and-stacks](../systems/items/items-and-stacks.md), [status-effects](../systems/player/status-effects.md), [chunk-generation-pipeline](../systems/world/chunk-generation-pipeline.md), [environment-attributes-and-timelines](../systems/world/environment-attributes-and-timelines.md), [fluids](../systems/world/fluids.md), [game-events-and-vibrations](../systems/world/game-events-and-vibrations.md), [points-of-interest](../systems/world/points-of-interest.md), [tickets-and-loading](../systems/world/tickets-and-loading.md), [density-functions](../systems/worldgen/density-functions.md), [features-and-placement](../systems/worldgen/features-and-placement.md) |
| `BuiltinStructures` | [structure-placement](../systems/worldgen/structure-placement.md) |
| `BuiltinStructureSets` | [structure-placement](../systems/worldgen/structure-placement.md) |
| `BuiltinTestFunctions` | [game-tests](../systems/commands/game-tests.md) |
| `BulkSectionAccess` | [chunk-anatomy](../systems/world/chunk-anatomy.md), [chunk-generation-pipeline](../systems/world/chunk-generation-pipeline.md) |
| `BundleContents` | [data-components](../systems/foundations/data-components.md), [items-and-stacks](../systems/items/items-and-stacks.md) |
| `BundleDelimiterPacket` | [packets-and-stream-codecs](../systems/networking/packets-and-stream-codecs.md) |
| `BundleItem` | [containers-and-menus](../systems/items/containers-and-menus.md) |
| `BundlePacket` | [packets-and-stream-codecs](../systems/networking/packets-and-stream-codecs.md) |
| `BundlerInfo` | [packets-and-stream-codecs](../systems/networking/packets-and-stream-codecs.md) |
| `BuriedTreasurePieces` | [hand-built-structures](../systems/worldgen/hand-built-structures.md) |
| `BushFoliagePlacer` | [trees](../systems/worldgen/trees.md) |
| `Button` | [gui-and-screens](../systems/client/gui-and-screens.md) |
| `ButtonListDialog` | [dialogs](../systems/commands/dialogs.md) |
| `ByIdMap` | [entity-anatomy](../systems/entities/entity-anatomy.md) |
| `ByteArrayTag` | [codecs-nbt-json](../systems/foundations/codecs-nbt-json.md) |
| `ByteBufCodecs` | [fanin](../maps/fanin.md), [blocks-and-states](../systems/blocks/blocks-and-states.md), [entity-anatomy](../systems/entities/entity-anatomy.md), [codecs-nbt-json](../systems/foundations/codecs-nbt-json.md), [identifiers-and-registries](../systems/foundations/identifiers-and-registries.md), [tags](../systems/foundations/tags.md), [text-components](../systems/foundations/text-components.md), [loot-tables](../systems/items/loot-tables.md), [packets-and-stream-codecs](../systems/networking/packets-and-stream-codecs.md) |
| `ByteBufferBuilder` | [naming-drift](../reference/naming-drift.md), [blaze3d](../systems/rendering/blaze3d.md) |
| `CacheableFunction` | [naming-drift](../reference/naming-drift.md) |
| `CachedParseState` | [brigadier-and-commands](../systems/commands/brigadier-and-commands.md) |
| `CakeBlock` | [hunger-and-experience](../systems/player/hunger-and-experience.md) |
| `CalibratedSculkSensorBlock` | [game-events-and-vibrations](../systems/world/game-events-and-vibrations.md) |
| `CalibratedSculkSensorBlockEntity` | [game-events-and-vibrations](../systems/world/game-events-and-vibrations.md) |
| `CallbackDeviceTracker` | [sound-engine](../systems/client/sound-engine.md) |
| `CallFunction` | [functions-and-macros](../systems/commands/functions-and-macros.md), [the-execution-engine](../systems/commands/the-execution-engine.md) |
| `Camera` | [glossary](../reference/glossary.md), [naming-drift](../reference/naming-drift.md), [sound-engine](../systems/client/sound-engine.md), [block-entity-rendering](../systems/rendering/block-entity-rendering.md), [the-frame](../systems/rendering/the-frame.md), [environment-attributes-and-timelines](../systems/world/environment-attributes-and-timelines.md) |
| `CameraRenderState` | [glossary](../reference/glossary.md), [lightmap-fog-and-sky](../systems/rendering/lightmap-fog-and-sky.md), [the-frame](../systems/rendering/the-frame.md) |
| `CampfireBlock` | [recipes](../systems/items/recipes.md) |
| `CampfireBlockEntity` | [recipes](../systems/items/recipes.md) |
| `CampfireRenderState` | [block-entity-rendering](../systems/rendering/block-entity-rendering.md) |
| `CapeLayer` | [entity-rendering](../systems/rendering/entity-rendering.md) |
| `CardinalLighting` | [visibility-and-the-frame-graph](../systems/rendering/visibility-and-the-frame-graph.md) |
| `CarverConfiguration` | [terrain](../systems/worldgen/terrain.md) |
| `CarvingContext` | [terrain](../systems/worldgen/terrain.md) |
| `CarvingMask` | [blending](../systems/worldgen/blending.md), [terrain](../systems/worldgen/terrain.md) |
| `CatSpawner` | [entity-lifecycle](../systems/entities/entity-lifecycle.md), [server-level-tick](../systems/server/server-level-tick.md), [starting-a-server](../systems/server/starting-a-server.md), [points-of-interest](../systems/world/points-of-interest.md) |
| `CauldronInteractions` | [identifiers-and-registries](../systems/foundations/identifiers-and-registries.md) |
| `CaveFeatures` | [what-this-book-skips](../systems/anatomy/what-this-book-skips.md) |
| `ChainModifiers` | [the-execution-engine](../systems/commands/the-execution-engine.md) |
| `ChangeItemDamage` | [enchantments](../systems/items/enchantments.md) |
| `Channel` | [sound-engine](../systems/client/sound-engine.md) |
| `ChannelAccess` | [sound-engine](../systems/client/sound-engine.md) |
| `CharacterEvent` | [naming-drift](../reference/naming-drift.md), [input-and-keybinds](../systems/client/input-and-keybinds.md) |
| `ChargedProjectiles` | [items-and-stacks](../systems/items/items-and-stacks.md) |
| `ChaseClient` | [threads](../reference/threads.md) |
| `ChaseCommand` | [brigadier-and-commands](../systems/commands/brigadier-and-commands.md) |
| `ChaseServer` | [threads](../reference/threads.md) |
| `ChatAbilities` | [permissions](../systems/commands/permissions.md), [chat-and-signing](../systems/networking/chat-and-signing.md) |
| `ChatComponent` | [hud-elements](../reference/hud-elements.md), [hud](../systems/client/hud.md) |
| `ChatDecorator` | [chat-and-signing](../systems/networking/chat-and-signing.md) |
| `ChatFormatting` | [naming-drift](../reference/naming-drift.md), [text-components](../systems/foundations/text-components.md) |
| `ChatListener` | [hud](../systems/client/hud.md), [text-components](../systems/foundations/text-components.md), [chat-and-signing](../systems/networking/chat-and-signing.md) |
| `ChatReportContextBuilder` | [chat-and-signing](../systems/networking/chat-and-signing.md) |
| `ChatRestriction` | [permissions](../systems/commands/permissions.md), [chat-and-signing](../systems/networking/chat-and-signing.md) |
| `ChatScreen` | [chat-and-signing](../systems/networking/chat-and-signing.md) |
| `ChatTrustLevel` | [chat-and-signing](../systems/networking/chat-and-signing.md) |
| `ChatType` | [chat-and-signing](../systems/networking/chat-and-signing.md) |
| `ChatVisiblity` | [chat-and-signing](../systems/networking/chat-and-signing.md) |
| `Checkbox` | [gui-and-screens](../systems/client/gui-and-screens.md) |
| `CherryFoliagePlacer` | [trees](../systems/worldgen/trees.md) |
| `CherryTrunkPlacer` | [trees](../systems/worldgen/trees.md) |
| `ChestBlock` | [block-entities](../systems/blocks/block-entities.md), [containers-and-menus](../systems/items/containers-and-menus.md), [loot-tables](../systems/items/loot-tables.md) |
| `ChestBlockEntity` | [block-entities](../systems/blocks/block-entities.md), [codecs-nbt-json](../systems/foundations/codecs-nbt-json.md), [containers-and-menus](../systems/items/containers-and-menus.md), [loot-tables](../systems/items/loot-tables.md) |
| `ChestMenu` | [containers-and-menus](../systems/items/containers-and-menus.md) |
| `ChestModel` | [block-entity-rendering](../systems/rendering/block-entity-rendering.md) |
| `ChestRenderer` | [block-entity-rendering](../systems/rendering/block-entity-rendering.md) |
| `ChestSpecialRenderer` | [block-entity-rendering](../systems/rendering/block-entity-rendering.md) |
| `Chicken` | [entity-lifecycle](../systems/entities/entity-lifecycle.md) |
| `ChunkAccess` | [glossary](../reference/glossary.md), [level-data-and-rules](../reference/level-data-and-rules.md), [entity-lifecycle](../systems/entities/entity-lifecycle.md), [server-level-tick](../systems/server/server-level-tick.md), [chunk-anatomy](../systems/world/chunk-anatomy.md), [chunk-generation-pipeline](../systems/world/chunk-generation-pipeline.md), [chunk-storage](../systems/world/chunk-storage.md), [game-events-and-vibrations](../systems/world/game-events-and-vibrations.md), [lighting](../systems/world/lighting.md), [blending](../systems/worldgen/blending.md), [terrain](../systems/worldgen/terrain.md) |
| `ChunkBatchSizeCalculator` | [threads](../reference/threads.md), [what-the-client-is-told](../systems/networking/what-the-client-is-told.md) |
| `ChunkDependencies` | [chunk-generation-pipeline](../systems/world/chunk-generation-pipeline.md) |
| `ChunkedSampleByteBuf` | [sound-engine](../systems/client/sound-engine.md) |
| `ChunkGenerationTask` | [chunk-generation-pipeline](../systems/world/chunk-generation-pipeline.md) |
| `ChunkGenerator` | [level-data-and-rules](../reference/level-data-and-rules.md), [data-driven-types](../systems/foundations/data-driven-types.md), [what-the-client-is-told](../systems/networking/what-the-client-is-told.md), [chunk-generation-pipeline](../systems/world/chunk-generation-pipeline.md), [biomes](../systems/worldgen/biomes.md), [creating-a-world](../systems/worldgen/creating-a-world.md), [features-and-placement](../systems/worldgen/features-and-placement.md), [hand-built-structures](../systems/worldgen/hand-built-structures.md), [jigsaw-and-templates](../systems/worldgen/jigsaw-and-templates.md), [structure-placement](../systems/worldgen/structure-placement.md), [terrain](../systems/worldgen/terrain.md) |
| `ChunkGenerators` | [terrain](../systems/worldgen/terrain.md) |
| `ChunkGeneratorStructureState` | [structure-placement](../systems/worldgen/structure-placement.md) |
| `ChunkHolder` | [block-entities](../systems/blocks/block-entities.md), [block-interaction](../systems/blocks/block-interaction.md), [signal-and-dust](../systems/blocks/signal-and-dust.md), [what-the-client-is-told](../systems/networking/what-the-client-is-told.md), [how-a-server-dies](../systems/server/how-a-server-dies.md), [server-level-tick](../systems/server/server-level-tick.md), [chunk-generation-pipeline](../systems/world/chunk-generation-pipeline.md), [chunk-storage](../systems/world/chunk-storage.md), [lighting](../systems/world/lighting.md), [scheduled-ticks](../systems/world/scheduled-ticks.md), [tickets-and-loading](../systems/world/tickets-and-loading.md) |
| `ChunkLevel` | [glossary](../reference/glossary.md), [server-level-tick](../systems/server/server-level-tick.md), [chunk-generation-pipeline](../systems/world/chunk-generation-pipeline.md), [chunk-storage](../systems/world/chunk-storage.md), [tickets-and-loading](../systems/world/tickets-and-loading.md) |
| `ChunkLoadCounter` | [players-and-sessions](../systems/server/players-and-sessions.md), [starting-a-server](../systems/server/starting-a-server.md), [chunk-generation-pipeline](../systems/world/chunk-generation-pipeline.md) |
| `ChunkMap` | [introduction](../introduction.md), [biggest](../maps/biggest.md), [packages](../maps/packages.md), [anatomy](../systems/anatomy/anatomy.md), [options](../systems/client/options.md), [attributes](../systems/entities/attributes.md), [entity-lifecycle](../systems/entities/entity-lifecycle.md), [synched-entity-data](../systems/entities/synched-entity-data.md), [what-the-client-is-told](../systems/networking/what-the-client-is-told.md), [how-a-server-dies](../systems/server/how-a-server-dies.md), [players-and-sessions](../systems/server/players-and-sessions.md), [server-level-tick](../systems/server/server-level-tick.md), [server-tick](../systems/server/server-tick.md), [chunk-generation-pipeline](../systems/world/chunk-generation-pipeline.md), [chunk-storage](../systems/world/chunk-storage.md), [lighting](../systems/world/lighting.md), [points-of-interest](../systems/world/points-of-interest.md), [scheduled-ticks](../systems/world/scheduled-ticks.md), [tickets-and-loading](../systems/world/tickets-and-loading.md), [blending](../systems/worldgen/blending.md), [terrain](../systems/worldgen/terrain.md) |
| `ChunkPos` | [math-and-primitives](../reference/math-and-primitives.md), [naming-drift](../reference/naming-drift.md), [IV · The world](../systems/world/README.md), [chunk-generation-pipeline](../systems/world/chunk-generation-pipeline.md), [chunk-storage](../systems/world/chunk-storage.md) |
| `ChunkPyramid` | [lectures](../lectures.md), [math-and-primitives](../reference/math-and-primitives.md), [chunk-generation-pipeline](../systems/world/chunk-generation-pipeline.md), [lighting](../systems/world/lighting.md), [tickets-and-loading](../systems/world/tickets-and-loading.md) |
| `ChunkScanAccess` | [chunk-storage](../systems/world/chunk-storage.md), [structure-placement](../systems/worldgen/structure-placement.md) |
| `ChunkSectionLayer` | [glossary](../reference/glossary.md), [naming-drift](../reference/naming-drift.md), [models-and-atlases](../systems/rendering/models-and-atlases.md), [section-meshing](../systems/rendering/section-meshing.md) |
| `ChunkSectionLayerGroup` | [visibility-and-the-frame-graph](../systems/rendering/visibility-and-the-frame-graph.md) |
| `ChunkSectionsToRender` | [visibility-and-the-frame-graph](../systems/rendering/visibility-and-the-frame-graph.md) |
| `ChunkSkyLightSources` | [chunk-anatomy](../systems/world/chunk-anatomy.md), [lighting](../systems/world/lighting.md) |
| `ChunkSource` | [pathfinding](../systems/entities/pathfinding.md), [chunk-anatomy](../systems/world/chunk-anatomy.md), [tickets-and-loading](../systems/world/tickets-and-loading.md) |
| `ChunkStatus` | [lectures](../lectures.md), [entity-anatomy](../systems/entities/entity-anatomy.md), [starting-a-server](../systems/server/starting-a-server.md), [chunk-anatomy](../systems/world/chunk-anatomy.md), [chunk-generation-pipeline](../systems/world/chunk-generation-pipeline.md), [chunk-storage](../systems/world/chunk-storage.md), [game-events-and-vibrations](../systems/world/game-events-and-vibrations.md), [lighting](../systems/world/lighting.md), [points-of-interest](../systems/world/points-of-interest.md), [scheduled-ticks](../systems/world/scheduled-ticks.md), [tickets-and-loading](../systems/world/tickets-and-loading.md), [XII · World generation](../systems/worldgen/README.md), [biomes](../systems/worldgen/biomes.md), [blending](../systems/worldgen/blending.md), [density-functions](../systems/worldgen/density-functions.md), [features-and-placement](../systems/worldgen/features-and-placement.md), [hand-built-structures](../systems/worldgen/hand-built-structures.md), [jigsaw-and-templates](../systems/worldgen/jigsaw-and-templates.md), [structure-placement](../systems/worldgen/structure-placement.md), [terrain](../systems/worldgen/terrain.md) |
| `ChunkStatusTasks` | [block-entities](../systems/blocks/block-entities.md), [chunk-anatomy](../systems/world/chunk-anatomy.md), [chunk-generation-pipeline](../systems/world/chunk-generation-pipeline.md), [chunk-storage](../systems/world/chunk-storage.md), [lighting](../systems/world/lighting.md), [scheduled-ticks](../systems/world/scheduled-ticks.md), [blending](../systems/worldgen/blending.md), [features-and-placement](../systems/worldgen/features-and-placement.md) |
| `ChunkStep` | [glossary](../reference/glossary.md), [chunk-generation-pipeline](../systems/world/chunk-generation-pipeline.md) |
| `ChunkTaskDispatcher` | [threads](../reference/threads.md), [anatomy](../systems/anatomy/anatomy.md), [chunk-generation-pipeline](../systems/world/chunk-generation-pipeline.md), [lighting](../systems/world/lighting.md) |
| `ChunkTaskPriorityQueue` | [chunk-generation-pipeline](../systems/world/chunk-generation-pipeline.md) |
| `ChunkTracker` | [lighting](../systems/world/lighting.md), [tickets-and-loading](../systems/world/tickets-and-loading.md) |
| `ChunkTrackingView` | [what-the-client-is-told](../systems/networking/what-the-client-is-told.md), [players-and-sessions](../systems/server/players-and-sessions.md), [tickets-and-loading](../systems/world/tickets-and-loading.md) |
| `ChunkType` | [chunk-anatomy](../systems/world/chunk-anatomy.md), [chunk-generation-pipeline](../systems/world/chunk-generation-pipeline.md), [chunk-storage](../systems/world/chunk-storage.md) |
| `CipherBase` | [the-connection](../systems/networking/the-connection.md) |
| `CipherDecoder` | [the-connection](../systems/networking/the-connection.md) |
| `CipherEncoder` | [the-connection](../systems/networking/the-connection.md) |
| `ClassTreeIdRegistry` | [synched-entity-data](../systems/entities/synched-entity-data.md) |
| `Clearable` | [loot-tables](../systems/items/loot-tables.md) |
| `ClearAllStatusEffectsConsumeEffect` | [hunger-and-experience](../systems/player/hunger-and-experience.md) |
| `ClickAction` | [containers-and-menus](../systems/items/containers-and-menus.md) |
| `ClickEvent` | [dialogs](../systems/commands/dialogs.md), [text-components](../systems/foundations/text-components.md) |
| `ClientAdvancements` | [advancements](../systems/commands/advancements.md) |
| `ClientAvatarEntity` | [player-anatomy](../systems/player/player-anatomy.md) |
| `ClientBootstrap` | [anatomy](../systems/anatomy/anatomy.md) |
| `ClientboundAddEntityPacket` | [naming-drift](../reference/naming-drift.md), [entity-anatomy](../systems/entities/entity-anatomy.md), [entity-lifecycle](../systems/entities/entity-lifecycle.md), [synched-entity-data](../systems/entities/synched-entity-data.md), [what-the-client-is-told](../systems/networking/what-the-client-is-told.md) |
| `ClientboundAnimatePacket` | [the-sword-swing](../systems/player/the-sword-swing.md) |
| `ClientboundBlockChangedAckPacket` | [block-breaking](../systems/blocks/block-breaking.md), [block-interaction](../systems/blocks/block-interaction.md), [prediction-and-acks](../systems/client/prediction-and-acks.md), [what-the-client-is-told](../systems/networking/what-the-client-is-told.md) |
| `ClientboundBlockDestructionPacket` | [block-breaking](../systems/blocks/block-breaking.md), [what-the-client-is-told](../systems/networking/what-the-client-is-told.md) |
| `ClientboundBlockEntityDataPacket` | [block-entities](../systems/blocks/block-entities.md), [codecs-nbt-json](../systems/foundations/codecs-nbt-json.md), [what-the-client-is-told](../systems/networking/what-the-client-is-told.md) |
| `ClientboundBlockEventPacket` | [pistons-and-block-events](../systems/blocks/pistons-and-block-events.md), [what-the-client-is-told](../systems/networking/what-the-client-is-told.md), [server-level-tick](../systems/server/server-level-tick.md) |
| `ClientboundBlockUpdatePacket` | [block-breaking](../systems/blocks/block-breaking.md), [block-entities](../systems/blocks/block-entities.md), [block-interaction](../systems/blocks/block-interaction.md), [blocks-and-states](../systems/blocks/blocks-and-states.md), [what-the-client-is-told](../systems/networking/what-the-client-is-told.md), [server-level-tick](../systems/server/server-level-tick.md), [fluids](../systems/world/fluids.md) |
| `ClientboundBundleDelimiterPacket` | [packets-and-stream-codecs](../systems/networking/packets-and-stream-codecs.md) |
| `ClientboundBundlePacket` | [synched-entity-data](../systems/entities/synched-entity-data.md), [packets-and-stream-codecs](../systems/networking/packets-and-stream-codecs.md), [what-the-client-is-told](../systems/networking/what-the-client-is-told.md) |
| `ClientboundChangeDifficultyPacket` | [level-data-and-rules](../reference/level-data-and-rules.md) |
| `ClientboundChunkBatchFinishedPacket` | [what-the-client-is-told](../systems/networking/what-the-client-is-told.md), [tickets-and-loading](../systems/world/tickets-and-loading.md) |
| `ClientboundChunkBatchStartPacket` | [what-the-client-is-told](../systems/networking/what-the-client-is-told.md), [tickets-and-loading](../systems/world/tickets-and-loading.md) |
| `ClientboundChunksBiomesPacket` | [what-the-client-is-told](../systems/networking/what-the-client-is-told.md), [chunk-anatomy](../systems/world/chunk-anatomy.md), [biomes](../systems/worldgen/biomes.md) |
| `ClientboundClearDialogPacket` | [dialogs](../systems/commands/dialogs.md) |
| `ClientboundCommandsPacket` | [brigadier-and-commands](../systems/commands/brigadier-and-commands.md), [permissions](../systems/commands/permissions.md) |
| `ClientboundContainerClosePacket` | [containers-and-menus](../systems/items/containers-and-menus.md) |
| `ClientboundContainerSetContentPacket` | [data-components](../systems/foundations/data-components.md), [containers-and-menus](../systems/items/containers-and-menus.md), [loot-tables](../systems/items/loot-tables.md) |
| `ClientboundContainerSetDataPacket` | [block-entities](../systems/blocks/block-entities.md), [containers-and-menus](../systems/items/containers-and-menus.md), [enchanting](../systems/items/enchanting.md) |
| `ClientboundContainerSetSlotPacket` | [naming-drift](../reference/naming-drift.md), [codecs-nbt-json](../systems/foundations/codecs-nbt-json.md), [data-components](../systems/foundations/data-components.md), [containers-and-menus](../systems/items/containers-and-menus.md), [recipes](../systems/items/recipes.md) |
| `ClientboundCookieRequestPacket` | [protocol-phases](../systems/networking/protocol-phases.md) |
| `ClientboundCooldownPacket` | [prediction-and-acks](../systems/client/prediction-and-acks.md), [using-an-item](../systems/items/using-an-item.md) |
| `ClientboundCustomChatCompletionsPacket` | [brigadier-and-commands](../systems/commands/brigadier-and-commands.md) |
| `ClientboundCustomPayloadPacket` | [packets-and-stream-codecs](../systems/networking/packets-and-stream-codecs.md) |
| `ClientboundCustomQueryPacket` | [protocol-phases](../systems/networking/protocol-phases.md) |
| `ClientboundDamageEventPacket` | [damage-and-death](../systems/entities/damage-and-death.md), [what-the-client-is-told](../systems/networking/what-the-client-is-told.md), [the-sword-swing](../systems/player/the-sword-swing.md) |
| `ClientboundDebugBlockValuePacket` | [debugging-the-running-game](../systems/client/debugging-the-running-game.md) |
| `ClientboundDebugChunkValuePacket` | [debugging-the-running-game](../systems/client/debugging-the-running-game.md) |
| `ClientboundDebugEntityValuePacket` | [debugging-the-running-game](../systems/client/debugging-the-running-game.md) |
| `ClientboundDebugEventPacket` | [debugging-the-running-game](../systems/client/debugging-the-running-game.md) |
| `ClientboundDebugSamplePacket` | [debugging-the-running-game](../systems/client/debugging-the-running-game.md) |
| `ClientboundDeleteChatPacket` | [chat-and-signing](../systems/networking/chat-and-signing.md) |
| `ClientboundDisconnectPacket` | [text-components](../systems/foundations/text-components.md), [the-connection](../systems/networking/the-connection.md) |
| `ClientboundDisguisedChatPacket` | [chat-and-signing](../systems/networking/chat-and-signing.md), [packets-and-stream-codecs](../systems/networking/packets-and-stream-codecs.md) |
| `ClientboundEntityEventPacket` | [level-data-and-rules](../reference/level-data-and-rules.md), [permissions](../systems/commands/permissions.md), [damage-and-death](../systems/entities/damage-and-death.md), [entity-anatomy](../systems/entities/entity-anatomy.md), [synched-entity-data](../systems/entities/synched-entity-data.md), [using-an-item](../systems/items/using-an-item.md), [players-and-sessions](../systems/server/players-and-sessions.md) |
| `ClientboundEntityPositionSyncPacket` | [naming-drift](../reference/naming-drift.md), [authority](../systems/entities/authority.md), [entity-anatomy](../systems/entities/entity-anatomy.md), [movement-and-collision](../systems/entities/movement-and-collision.md), [synched-entity-data](../systems/entities/synched-entity-data.md), [what-the-client-is-told](../systems/networking/what-the-client-is-told.md) |
| `ClientboundExplodePacket` | [the-client-level](../systems/client/the-client-level.md), [what-makes-a-sound](../systems/client/what-makes-a-sound.md), [particles](../systems/rendering/particles.md) |
| `ClientboundForgetLevelChunkPacket` | [what-the-client-is-told](../systems/networking/what-the-client-is-told.md), [tickets-and-loading](../systems/world/tickets-and-loading.md) |
| `ClientboundGameEventPacket` | [level-data-and-rules](../reference/level-data-and-rules.md), [what-the-client-is-told](../systems/networking/what-the-client-is-told.md), [player-anatomy](../systems/player/player-anatomy.md), [server-level-tick](../systems/server/server-level-tick.md) |
| `ClientboundGameRuleValuesPacket` | [level-data-and-rules](../reference/level-data-and-rules.md) |
| `ClientboundGameTestHighlightPosPacket` | [game-tests](../systems/commands/game-tests.md) |
| `ClientboundHelloPacket` | [naming-drift](../reference/naming-drift.md), [protocol-phases](../systems/networking/protocol-phases.md), [the-connection](../systems/networking/the-connection.md) |
| `ClientboundHurtAnimationPacket` | [damage-and-death](../systems/entities/damage-and-death.md) |
| `ClientboundInitializeBorderPacket` | [level-data-and-rules](../reference/level-data-and-rules.md) |
| `ClientboundKeepAlivePacket` | [packets-and-stream-codecs](../systems/networking/packets-and-stream-codecs.md) |
| `ClientboundLevelChunkPacketData` | [chunk-anatomy](../systems/world/chunk-anatomy.md) |
| `ClientboundLevelChunkWithLightPacket` | [naming-drift](../reference/naming-drift.md), [what-the-client-is-told](../systems/networking/what-the-client-is-told.md), [chunk-anatomy](../systems/world/chunk-anatomy.md), [lighting](../systems/world/lighting.md), [tickets-and-loading](../systems/world/tickets-and-loading.md) |
| `ClientboundLevelEventPacket` | [what-makes-a-sound](../systems/client/what-makes-a-sound.md), [particles](../systems/rendering/particles.md) |
| `ClientboundLevelParticlesPacket` | [particles](../systems/rendering/particles.md) |
| `ClientboundLightUpdatePacket` | [what-the-client-is-told](../systems/networking/what-the-client-is-told.md), [server-level-tick](../systems/server/server-level-tick.md), [lighting](../systems/world/lighting.md) |
| `ClientboundLightUpdatePacketData` | [chunk-anatomy](../systems/world/chunk-anatomy.md), [lighting](../systems/world/lighting.md) |
| `ClientboundLoginCompressionPacket` | [naming-drift](../reference/naming-drift.md), [the-connection](../systems/networking/the-connection.md) |
| `ClientboundLoginDisconnectPacket` | [codecs-nbt-json](../systems/foundations/codecs-nbt-json.md), [packets-and-stream-codecs](../systems/networking/packets-and-stream-codecs.md), [protocol-phases](../systems/networking/protocol-phases.md), [the-connection](../systems/networking/the-connection.md) |
| `ClientboundLoginFinishedPacket` | [naming-drift](../reference/naming-drift.md), [protocol-phases](../systems/networking/protocol-phases.md) |
| `ClientboundLoginPacket` | [level-data-and-rules](../reference/level-data-and-rules.md), [naming-drift](../reference/naming-drift.md), [chat-and-signing](../systems/networking/chat-and-signing.md), [protocol-phases](../systems/networking/protocol-phases.md), [player-anatomy](../systems/player/player-anatomy.md), [players-and-sessions](../systems/server/players-and-sessions.md) |
| `ClientboundMapItemDataPacket` | [level-data-and-rules](../reference/level-data-and-rules.md) |
| `ClientboundMountScreenOpenPacket` | [naming-drift](../reference/naming-drift.md), [gui-and-screens](../systems/client/gui-and-screens.md), [containers-and-menus](../systems/items/containers-and-menus.md) |
| `ClientboundMoveEntityPacket` | [authority](../systems/entities/authority.md), [movement-and-collision](../systems/entities/movement-and-collision.md), [synched-entity-data](../systems/entities/synched-entity-data.md), [what-the-client-is-told](../systems/networking/what-the-client-is-told.md) |
| `ClientboundMoveMinecartPacket` | [synched-entity-data](../systems/entities/synched-entity-data.md), [what-the-client-is-told](../systems/networking/what-the-client-is-told.md) |
| `ClientboundMoveVehiclePacket` | [authority](../systems/entities/authority.md), [input-to-movement](../systems/player/input-to-movement.md) |
| `ClientboundOpenScreenPacket` | [gui-and-screens](../systems/client/gui-and-screens.md), [containers-and-menus](../systems/items/containers-and-menus.md), [loot-tables](../systems/items/loot-tables.md) |
| `ClientboundPlaceGhostRecipePacket` | [recipes](../systems/items/recipes.md) |
| `ClientboundPlayerAbilitiesPacket` | [player-anatomy](../systems/player/player-anatomy.md) |
| `ClientboundPlayerChatPacket` | [packets-and-stream-codecs](../systems/networking/packets-and-stream-codecs.md) |
| `ClientboundPlayerCombatKillPacket` | [damage-and-death](../systems/entities/damage-and-death.md), [text-components](../systems/foundations/text-components.md), [packets-and-stream-codecs](../systems/networking/packets-and-stream-codecs.md) |
| `ClientboundPlayerInfoRemovePacket` | [players-and-sessions](../systems/server/players-and-sessions.md) |
| `ClientboundPlayerInfoUpdatePacket` | [player-anatomy](../systems/player/player-anatomy.md), [players-and-sessions](../systems/server/players-and-sessions.md), [server-tick](../systems/server/server-tick.md) |
| `ClientboundPlayerPositionPacket` | [input-to-movement](../systems/player/input-to-movement.md) |
| `ClientboundPlayerRotationPacket` | [input-to-movement](../systems/player/input-to-movement.md) |
| `ClientboundPongResponsePacket` | [protocol-phases](../systems/networking/protocol-phases.md) |
| `ClientboundProjectilePowerPacket` | [what-the-client-is-told](../systems/networking/what-the-client-is-told.md) |
| `ClientboundRecipeBookAddPacket` | [recipes](../systems/items/recipes.md) |
| `ClientboundRegistryDataPacket` | [identifiers-and-registries](../systems/foundations/identifiers-and-registries.md), [protocol-phases](../systems/networking/protocol-phases.md) |
| `ClientboundRemoveEntitiesPacket` | [entity-anatomy](../systems/entities/entity-anatomy.md), [entity-lifecycle](../systems/entities/entity-lifecycle.md) |
| `ClientboundRemoveMobEffectPacket` | [synched-entity-data](../systems/entities/synched-entity-data.md), [status-effects](../systems/player/status-effects.md) |
| `ClientboundResetChatPacket` | [protocol-phases](../systems/networking/protocol-phases.md) |
| `ClientboundResetScorePacket` | [scoreboard-and-data](../systems/commands/scoreboard-and-data.md) |
| `ClientboundResourcePackPopPacket` | [resource-system](../systems/foundations/resource-system.md) |
| `ClientboundResourcePackPushPacket` | [naming-drift](../reference/naming-drift.md), [resource-system](../systems/foundations/resource-system.md) |
| `ClientboundRespawnPacket` | [player-anatomy](../systems/player/player-anatomy.md), [players-and-sessions](../systems/server/players-and-sessions.md) |
| `ClientboundRotateHeadPacket` | [synched-entity-data](../systems/entities/synched-entity-data.md) |
| `ClientboundSectionBlocksUpdatePacket` | [block-entities](../systems/blocks/block-entities.md), [block-interaction](../systems/blocks/block-interaction.md), [blocks-and-states](../systems/blocks/blocks-and-states.md), [what-the-client-is-told](../systems/networking/what-the-client-is-told.md), [server-level-tick](../systems/server/server-level-tick.md), [fluids](../systems/world/fluids.md) |
| `ClientboundSelectKnownPacks` | [identifiers-and-registries](../systems/foundations/identifiers-and-registries.md), [protocol-phases](../systems/networking/protocol-phases.md) |
| `ClientboundServerDataPacket` | [text-components](../systems/foundations/text-components.md) |
| `ClientboundServerLinksPacket` | [protocol-phases](../systems/networking/protocol-phases.md) |
| `ClientboundSetBorderCenterPacket` | [level-data-and-rules](../reference/level-data-and-rules.md) |
| `ClientboundSetBorderLerpSizePacket` | [level-data-and-rules](../reference/level-data-and-rules.md) |
| `ClientboundSetBorderSizePacket` | [level-data-and-rules](../reference/level-data-and-rules.md) |
| `ClientboundSetBorderWarningDelayPacket` | [level-data-and-rules](../reference/level-data-and-rules.md) |
| `ClientboundSetBorderWarningDistancePacket` | [level-data-and-rules](../reference/level-data-and-rules.md) |
| `ClientboundSetChunkCacheCenterPacket` | [naming-drift](../reference/naming-drift.md), [what-the-client-is-told](../systems/networking/what-the-client-is-told.md), [tickets-and-loading](../systems/world/tickets-and-loading.md) |
| `ClientboundSetChunkCacheRadiusPacket` | [naming-drift](../reference/naming-drift.md), [options](../systems/client/options.md), [what-the-client-is-told](../systems/networking/what-the-client-is-told.md), [tickets-and-loading](../systems/world/tickets-and-loading.md) |
| `ClientboundSetCursorItemPacket` | [naming-drift](../reference/naming-drift.md), [data-components](../systems/foundations/data-components.md), [containers-and-menus](../systems/items/containers-and-menus.md) |
| `ClientboundSetDefaultSpawnPositionPacket` | [level-data-and-rules](../reference/level-data-and-rules.md) |
| `ClientboundSetDisplayObjectivePacket` | [scoreboard-and-data](../systems/commands/scoreboard-and-data.md) |
| `ClientboundSetEntityDataPacket` | [damage-and-death](../systems/entities/damage-and-death.md), [entity-anatomy](../systems/entities/entity-anatomy.md), [synched-entity-data](../systems/entities/synched-entity-data.md) |
| `ClientboundSetEntityMotionPacket` | [damage-and-death](../systems/entities/damage-and-death.md), [movement-and-collision](../systems/entities/movement-and-collision.md), [synched-entity-data](../systems/entities/synched-entity-data.md), [what-the-client-is-told](../systems/networking/what-the-client-is-told.md) |
| `ClientboundSetEquipmentPacket` | [synched-entity-data](../systems/entities/synched-entity-data.md), [data-components](../systems/foundations/data-components.md), [what-the-client-is-told](../systems/networking/what-the-client-is-told.md) |
| `ClientboundSetExperiencePacket` | [hunger-and-experience](../systems/player/hunger-and-experience.md), [the-two-phase-tick](../systems/player/the-two-phase-tick.md) |
| `ClientboundSetHealthPacket` | [damage-and-death](../systems/entities/damage-and-death.md), [using-an-item](../systems/items/using-an-item.md), [packets-and-stream-codecs](../systems/networking/packets-and-stream-codecs.md), [hunger-and-experience](../systems/player/hunger-and-experience.md), [the-two-phase-tick](../systems/player/the-two-phase-tick.md) |
| `ClientboundSetHeldSlotPacket` | [naming-drift](../reference/naming-drift.md), [containers-and-menus](../systems/items/containers-and-menus.md), [player-anatomy](../systems/player/player-anatomy.md) |
| `ClientboundSetObjectivePacket` | [level-data-and-rules](../reference/level-data-and-rules.md), [scoreboard-and-data](../systems/commands/scoreboard-and-data.md) |
| `ClientboundSetPlayerInventoryPacket` | [data-components](../systems/foundations/data-components.md), [containers-and-menus](../systems/items/containers-and-menus.md), [player-anatomy](../systems/player/player-anatomy.md) |
| `ClientboundSetPlayerTeamPacket` | [level-data-and-rules](../reference/level-data-and-rules.md), [scoreboard-and-data](../systems/commands/scoreboard-and-data.md) |
| `ClientboundSetScorePacket` | [level-data-and-rules](../reference/level-data-and-rules.md), [scoreboard-and-data](../systems/commands/scoreboard-and-data.md) |
| `ClientboundSetSimulationDistancePacket` | [options](../systems/client/options.md), [what-the-client-is-told](../systems/networking/what-the-client-is-told.md), [tickets-and-loading](../systems/world/tickets-and-loading.md) |
| `ClientboundSetTimePacket` | [level-data-and-rules](../reference/level-data-and-rules.md), [server-level-tick](../systems/server/server-level-tick.md), [server-tick](../systems/server/server-tick.md), [environment-attributes-and-timelines](../systems/world/environment-attributes-and-timelines.md) |
| `ClientboundShowDialogPacket` | [dialogs](../systems/commands/dialogs.md), [packets-and-stream-codecs](../systems/networking/packets-and-stream-codecs.md) |
| `ClientboundSoundEntityPacket` | [what-makes-a-sound](../systems/client/what-makes-a-sound.md) |
| `ClientboundSoundPacket` | [block-interaction](../systems/blocks/block-interaction.md), [pistons-and-block-events](../systems/blocks/pistons-and-block-events.md), [signal-and-dust](../systems/blocks/signal-and-dust.md), [what-makes-a-sound](../systems/client/what-makes-a-sound.md), [the-sword-swing](../systems/player/the-sword-swing.md) |
| `ClientboundStartConfigurationPacket` | [protocol-phases](../systems/networking/protocol-phases.md), [players-and-sessions](../systems/server/players-and-sessions.md) |
| `ClientboundStatusResponsePacket` | [codecs-nbt-json](../systems/foundations/codecs-nbt-json.md), [packets-and-stream-codecs](../systems/networking/packets-and-stream-codecs.md) |
| `ClientboundStopSoundPacket` | [what-makes-a-sound](../systems/client/what-makes-a-sound.md) |
| `ClientboundStoreCookiePacket` | [protocol-phases](../systems/networking/protocol-phases.md) |
| `ClientboundSystemChatPacket` | [text-components](../systems/foundations/text-components.md), [packets-and-stream-codecs](../systems/networking/packets-and-stream-codecs.md) |
| `ClientboundTagQueryPacket` | [packets-and-stream-codecs](../systems/networking/packets-and-stream-codecs.md) |
| `ClientboundTakeItemEntityPacket` | [hunger-and-experience](../systems/player/hunger-and-experience.md) |
| `ClientboundTeleportEntityPacket` | [synched-entity-data](../systems/entities/synched-entity-data.md), [what-the-client-is-told](../systems/networking/what-the-client-is-told.md) |
| `ClientboundTickingStatePacket` | [server-tick](../systems/server/server-tick.md) |
| `ClientboundTickingStepPacket` | [server-tick](../systems/server/server-tick.md) |
| `ClientboundTransferPacket` | [protocol-phases](../systems/networking/protocol-phases.md) |
| `ClientboundUpdateAttributesPacket` | [attributes](../systems/entities/attributes.md), [synched-entity-data](../systems/entities/synched-entity-data.md) |
| `ClientboundUpdateEnabledFeaturesPacket` | [protocol-phases](../systems/networking/protocol-phases.md) |
| `ClientboundUpdateMobEffectPacket` | [synched-entity-data](../systems/entities/synched-entity-data.md), [status-effects](../systems/player/status-effects.md) |
| `ClientboundUpdateRecipesPacket` | [resource-system](../systems/foundations/resource-system.md), [recipes](../systems/items/recipes.md) |
| `ClientboundUpdateTagsPacket` | [identifiers-and-registries](../systems/foundations/identifiers-and-registries.md), [resource-system](../systems/foundations/resource-system.md), [tags](../systems/foundations/tags.md), [enchantments](../systems/items/enchantments.md), [protocol-phases](../systems/networking/protocol-phases.md) |
| `ClientChunkCache` | [naming-drift](../reference/naming-drift.md), [the-client-level](../systems/client/the-client-level.md), [visibility-and-the-frame-graph](../systems/rendering/visibility-and-the-frame-graph.md), [chunk-anatomy](../systems/world/chunk-anatomy.md), [lighting](../systems/world/lighting.md) |
| `ClientClockManager` | [the-client-level](../systems/client/the-client-level.md), [environment-attributes-and-timelines](../systems/world/environment-attributes-and-timelines.md) |
| `ClientCommonPacketListenerImpl` | [biggest](../maps/biggest.md), [threads](../reference/threads.md), [the-client-loop](../systems/client/the-client-loop.md), [dialogs](../systems/commands/dialogs.md), [protocol-phases](../systems/networking/protocol-phases.md), [the-connection](../systems/networking/the-connection.md) |
| `ClientConfigurationPacketListenerImpl` | [data-components](../systems/foundations/data-components.md), [identifiers-and-registries](../systems/foundations/identifiers-and-registries.md), [tags](../systems/foundations/tags.md), [items-and-stacks](../systems/items/items-and-stacks.md), [protocol-phases](../systems/networking/protocol-phases.md) |
| `ClientDebugSubscriber` | [debugging-the-running-game](../systems/client/debugging-the-running-game.md) |
| `ClientExplosionTracker` | [the-client-level](../systems/client/the-client-level.md), [particles](../systems/rendering/particles.md) |
| `ClientHandshakePacketListenerImpl` | [anatomy](../systems/anatomy/anatomy.md), [options](../systems/client/options.md), [protocol-phases](../systems/networking/protocol-phases.md), [the-connection](../systems/networking/the-connection.md) |
| `ClientInformation` | [options](../systems/client/options.md), [players-and-sessions](../systems/server/players-and-sessions.md) |
| `ClientInput` | [glossary](../reference/glossary.md), [input-to-movement](../systems/player/input-to-movement.md), [player-anatomy](../systems/player/player-anatomy.md), [the-two-phase-tick](../systems/player/the-two-phase-tick.md) |
| `ClientIntent` | [protocol-phases](../systems/networking/protocol-phases.md) |
| `ClientIntentionPacket` | [protocol-phases](../systems/networking/protocol-phases.md), [the-connection](../systems/networking/the-connection.md) |
| `ClientItem` | [models-and-atlases](../systems/rendering/models-and-atlases.md) |
| `ClientItemInfoLoader` | [models-and-atlases](../systems/rendering/models-and-atlases.md) |
| `ClientLanguage` | [text-and-fonts](../systems/client/text-and-fonts.md), [text-components](../systems/foundations/text-components.md) |
| `ClientLevel` | [lectures](../lectures.md), [packages](../maps/packages.md), [glossary](../reference/glossary.md), [level-data-and-rules](../reference/level-data-and-rules.md), [math-and-primitives](../reference/math-and-primitives.md), [threads](../reference/threads.md), [anatomy](../systems/anatomy/anatomy.md), [block-breaking](../systems/blocks/block-breaking.md), [block-entities](../systems/blocks/block-entities.md), [block-interaction](../systems/blocks/block-interaction.md), [blocks-and-states](../systems/blocks/blocks-and-states.md), [diodes-and-observers](../systems/blocks/diodes-and-observers.md), [pistons-and-block-events](../systems/blocks/pistons-and-block-events.md), [signal-and-dust](../systems/blocks/signal-and-dust.md), [X · The client](../systems/client/README.md), [prediction-and-acks](../systems/client/prediction-and-acks.md), [the-client-level](../systems/client/the-client-level.md), [the-client-loop](../systems/client/the-client-loop.md), [what-makes-a-sound](../systems/client/what-makes-a-sound.md), [entity-anatomy](../systems/entities/entity-anatomy.md), [entity-lifecycle](../systems/entities/entity-lifecycle.md), [movement-and-collision](../systems/entities/movement-and-collision.md), [synched-entity-data](../systems/entities/synched-entity-data.md), [contexts-and-predicates](../systems/items/contexts-and-predicates.md), [loot-tables](../systems/items/loot-tables.md), [using-an-item](../systems/items/using-an-item.md), [what-the-client-is-told](../systems/networking/what-the-client-is-told.md), [input-to-movement](../systems/player/input-to-movement.md), [player-anatomy](../systems/player/player-anatomy.md), [the-sword-swing](../systems/player/the-sword-swing.md), [the-two-phase-tick](../systems/player/the-two-phase-tick.md), [block-entity-rendering](../systems/rendering/block-entity-rendering.md), [entity-rendering](../systems/rendering/entity-rendering.md), [lightmap-fog-and-sky](../systems/rendering/lightmap-fog-and-sky.md), [particles](../systems/rendering/particles.md), [section-meshing](../systems/rendering/section-meshing.md), [the-frame](../systems/rendering/the-frame.md), [visibility-and-the-frame-graph](../systems/rendering/visibility-and-the-frame-graph.md), [environment-attributes-and-timelines](../systems/world/environment-attributes-and-timelines.md), [fluids](../systems/world/fluids.md), [game-events-and-vibrations](../systems/world/game-events-and-vibrations.md), [lighting](../systems/world/lighting.md), [scheduled-ticks](../systems/world/scheduled-ticks.md), [biomes](../systems/worldgen/biomes.md) |
| `ClientMannequin` | [naming-drift](../reference/naming-drift.md), [entity-anatomy](../systems/entities/entity-anatomy.md), [player-anatomy](../systems/player/player-anatomy.md) |
| `ClientPacketListener` | [biggest](../maps/biggest.md), [packages](../maps/packages.md), [glossary](../reference/glossary.md), [threads](../reference/threads.md), [anatomy](../systems/anatomy/anatomy.md), [block-entities](../systems/blocks/block-entities.md), [block-interaction](../systems/blocks/block-interaction.md), [pistons-and-block-events](../systems/blocks/pistons-and-block-events.md), [debugging-the-running-game](../systems/client/debugging-the-running-game.md), [prediction-and-acks](../systems/client/prediction-and-acks.md), [the-client-level](../systems/client/the-client-level.md), [brigadier-and-commands](../systems/commands/brigadier-and-commands.md), [dialogs](../systems/commands/dialogs.md), [permissions](../systems/commands/permissions.md), [scoreboard-and-data](../systems/commands/scoreboard-and-data.md), [attributes](../systems/entities/attributes.md), [authority](../systems/entities/authority.md), [entity-anatomy](../systems/entities/entity-anatomy.md), [movement-and-collision](../systems/entities/movement-and-collision.md), [synched-entity-data](../systems/entities/synched-entity-data.md), [codecs-nbt-json](../systems/foundations/codecs-nbt-json.md), [data-components](../systems/foundations/data-components.md), [identifiers-and-registries](../systems/foundations/identifiers-and-registries.md), [tags](../systems/foundations/tags.md), [text-components](../systems/foundations/text-components.md), [items-and-stacks](../systems/items/items-and-stacks.md), [chat-and-signing](../systems/networking/chat-and-signing.md), [packets-and-stream-codecs](../systems/networking/packets-and-stream-codecs.md), [protocol-phases](../systems/networking/protocol-phases.md), [what-the-client-is-told](../systems/networking/what-the-client-is-told.md), [hunger-and-experience](../systems/player/hunger-and-experience.md), [input-to-movement](../systems/player/input-to-movement.md), [player-anatomy](../systems/player/player-anatomy.md), [particles](../systems/rendering/particles.md), [chunk-anatomy](../systems/world/chunk-anatomy.md), [lighting](../systems/world/lighting.md) |
| `ClientPackSource` | [resource-system](../systems/foundations/resource-system.md) |
| `ClientRecipeBook` | [recipes](../systems/items/recipes.md), [player-anatomy](../systems/player/player-anatomy.md) |
| `ClientRecipeContainer` | [recipes](../systems/items/recipes.md) |
| `ClientRegistryLayer` | [identifiers-and-registries](../systems/foundations/identifiers-and-registries.md) |
| `ClientShutdownWatchdog` | [threads](../reference/threads.md), [the-client-loop](../systems/client/the-client-loop.md), [the-window](../systems/rendering/the-window.md) |
| `ClientSuggestionProvider` | [brigadier-and-commands](../systems/commands/brigadier-and-commands.md), [entity-selectors](../systems/commands/entity-selectors.md) |
| `ClientTelemetryManager` | [what-this-book-skips](../systems/anatomy/what-this-book-skips.md) |
| `ClientWaypointManager` | [hud](../systems/client/hud.md) |
| `Climate` | [biomes](../systems/worldgen/biomes.md), [density-functions](../systems/worldgen/density-functions.md) |
| `ClipBlockStateContext` | [game-events-and-vibrations](../systems/world/game-events-and-vibrations.md) |
| `ClipboardManager` | [the-window](../systems/rendering/the-window.md) |
| `ClockNetworkState` | [environment-attributes-and-timelines](../systems/world/environment-attributes-and-timelines.md) |
| `ClockTimeMarker` | [environment-attributes-and-timelines](../systems/world/environment-attributes-and-timelines.md) |
| `ClockTimeMarkers` | [server-level-tick](../systems/server/server-level-tick.md), [environment-attributes-and-timelines](../systems/world/environment-attributes-and-timelines.md) |
| `CloneCommands` | [scheduled-ticks](../systems/world/scheduled-ticks.md) |
| `CloudRenderer` | [lightmap-fog-and-sky](../systems/rendering/lightmap-fog-and-sky.md) |
| `CloudStatus` | [lightmap-fog-and-sky](../systems/rendering/lightmap-fog-and-sky.md) |
| `CocoaDecorator` | [trees](../systems/worldgen/trees.md) |
| `Codec` | [fanin](../maps/fanin.md), [glossary](../reference/glossary.md), [level-data-and-rules](../reference/level-data-and-rules.md), [codecs-nbt-json](../systems/foundations/codecs-nbt-json.md), [data-driven-types](../systems/foundations/data-driven-types.md), [text-components](../systems/foundations/text-components.md), [packets-and-stream-codecs](../systems/networking/packets-and-stream-codecs.md) |
| `CodecModifier` | [packets-and-stream-codecs](../systems/networking/packets-and-stream-codecs.md) |
| `CodepointMap` | [text-and-fonts](../systems/client/text-and-fonts.md) |
| `CollectFields` | [codecs-nbt-json](../systems/foundations/codecs-nbt-json.md) |
| `CollectingNeighborUpdater` | [block-interaction](../systems/blocks/block-interaction.md), [blocks-and-states](../systems/blocks/blocks-and-states.md), [signal-and-dust](../systems/blocks/signal-and-dust.md), [server-level-tick](../systems/server/server-level-tick.md) |
| `CollectionContentsPredicate` | [advancements](../systems/commands/advancements.md) |
| `CollectionCountsPredicate` | [advancements](../systems/commands/advancements.md) |
| `CollectionPredicate` | [advancements](../systems/commands/advancements.md) |
| `CollectionTag` | [codecs-nbt-json](../systems/foundations/codecs-nbt-json.md) |
| `CollisionContext` | [math-and-primitives](../reference/math-and-primitives.md) |
| `CollisionGetter` | [movement-and-collision](../systems/entities/movement-and-collision.md) |
| `ColoredRectangleRenderState` | [the-gui-render-tree](../systems/client/the-gui-render-tree.md) |
| `ColorModifier` | [environment-attributes-and-timelines](../systems/world/environment-attributes-and-timelines.md) |
| `ColorResolver` | [lightmap-fog-and-sky](../systems/rendering/lightmap-fog-and-sky.md) |
| `ColorRGBA` | [math-and-primitives](../reference/math-and-primitives.md) |
| `ColorTargetState` | [blaze3d](../systems/rendering/blaze3d.md) |
| `CombatEntry` | [damage-and-death](../systems/entities/damage-and-death.md), [text-components](../systems/foundations/text-components.md) |
| `CombatRules` | [damage-and-death](../systems/entities/damage-and-death.md) |
| `CombatTracker` | [non-living-damage](../reference/non-living-damage.md), [damage-and-death](../systems/entities/damage-and-death.md), [text-components](../systems/foundations/text-components.md) |
| `CommandBuildContext` | [brigadier-and-commands](../systems/commands/brigadier-and-commands.md), [identifiers-and-registries](../systems/foundations/identifiers-and-registries.md) |
| `CommandDispatcher` | [brigadier-and-commands](../systems/commands/brigadier-and-commands.md) |
| `CommandEncoder` | [glossary](../reference/glossary.md), [blaze3d](../systems/rendering/blaze3d.md) |
| `CommandEncoderBackend` | [blaze3d](../systems/rendering/blaze3d.md) |
| `CommandFunction` | [glossary](../reference/glossary.md), [functions-and-macros](../systems/commands/functions-and-macros.md), [tags](../systems/foundations/tags.md) |
| `CommandQueueEntry` | [the-execution-engine](../systems/commands/the-execution-engine.md) |
| `CommandResultCallback` | [naming-drift](../reference/naming-drift.md), [the-execution-engine](../systems/commands/the-execution-engine.md) |
| `Commands` | [naming-drift](../reference/naming-drift.md), [what-this-book-skips](../systems/anatomy/what-this-book-skips.md), [brigadier-and-commands](../systems/commands/brigadier-and-commands.md), [functions-and-macros](../systems/commands/functions-and-macros.md), [game-tests](../systems/commands/game-tests.md), [permissions](../systems/commands/permissions.md), [the-execution-engine](../systems/commands/the-execution-engine.md), [resource-system](../systems/foundations/resource-system.md), [how-a-server-dies](../systems/server/how-a-server-dies.md), [players-and-sessions](../systems/server/players-and-sessions.md) |
| `CommandSigningContext` | [brigadier-and-commands](../systems/commands/brigadier-and-commands.md) |
| `CommandSource` | [the-execution-engine](../systems/commands/the-execution-engine.md) |
| `CommandSourceStack` | [naming-drift](../reference/naming-drift.md), [brigadier-and-commands](../systems/commands/brigadier-and-commands.md), [entity-selectors](../systems/commands/entity-selectors.md), [functions-and-macros](../systems/commands/functions-and-macros.md), [permissions](../systems/commands/permissions.md), [the-execution-engine](../systems/commands/the-execution-engine.md), [starting-a-server](../systems/server/starting-a-server.md) |
| `CommandStorage` | [level-data-and-rules](../reference/level-data-and-rules.md), [scoreboard-and-data](../systems/commands/scoreboard-and-data.md), [starting-a-server](../systems/server/starting-a-server.md) |
| `CommandSuggestions` | [brigadier-and-commands](../systems/commands/brigadier-and-commands.md) |
| `CommandSyntaxException` | [scoreboard-and-data](../systems/commands/scoreboard-and-data.md), [the-execution-engine](../systems/commands/the-execution-engine.md), [codecs-nbt-json](../systems/foundations/codecs-nbt-json.md) |
| `CommandTemplate` | [dialogs](../systems/commands/dialogs.md) |
| `CommonButtonData` | [dialogs](../systems/commands/dialogs.md) |
| `CommonColors` | [math-and-primitives](../reference/math-and-primitives.md) |
| `CommonComponents` | [text-components](../systems/foundations/text-components.md) |
| `CommonDialogData` | [dialogs](../systems/commands/dialogs.md) |
| `CommonListenerCookie` | [identifiers-and-registries](../systems/foundations/identifiers-and-registries.md), [protocol-phases](../systems/networking/protocol-phases.md), [players-and-sessions](../systems/server/players-and-sessions.md) |
| `CommonPlayerSpawnInfo` | [level-data-and-rules](../reference/level-data-and-rules.md), [player-anatomy](../systems/player/player-anatomy.md), [players-and-sessions](../systems/server/players-and-sessions.md) |
| `ComparatorBlock` | [diodes-and-observers](../systems/blocks/diodes-and-observers.md), [pistons-and-block-events](../systems/blocks/pistons-and-block-events.md), [scheduled-ticks](../systems/world/scheduled-ticks.md) |
| `ComparatorBlockEntity` | [diodes-and-observers](../systems/blocks/diodes-and-observers.md), [pistons-and-block-events](../systems/blocks/pistons-and-block-events.md) |
| `ComparatorMode` | [diodes-and-observers](../systems/blocks/diodes-and-observers.md) |
| `CompassItem` | [items-and-stacks](../systems/items/items-and-stacks.md) |
| `CompiledSectionMesh` | [glossary](../reference/glossary.md), [naming-drift](../reference/naming-drift.md), [section-meshing](../systems/rendering/section-meshing.md) |
| `Component` | [lectures](../lectures.md), [fanin](../maps/fanin.md), [packages](../maps/packages.md), [glossary](../reference/glossary.md), [X · The client](../systems/client/README.md), [gui-and-screens](../systems/client/gui-and-screens.md), [text-and-fonts](../systems/client/text-and-fonts.md), [brigadier-and-commands](../systems/commands/brigadier-and-commands.md), [functions-and-macros](../systems/commands/functions-and-macros.md), [text-components](../systems/foundations/text-components.md), [enchantments](../systems/items/enchantments.md), [chat-and-signing](../systems/networking/chat-and-signing.md), [packets-and-stream-codecs](../systems/networking/packets-and-stream-codecs.md), [entity-rendering](../systems/rendering/entity-rendering.md), [players-and-sessions](../systems/server/players-and-sessions.md) |
| `ComponentArgument` | [brigadier-and-commands](../systems/commands/brigadier-and-commands.md), [text-components](../systems/foundations/text-components.md) |
| `ComponentCollector` | [text-and-fonts](../systems/client/text-and-fonts.md) |
| `ComponentContents` | [text-components](../systems/foundations/text-components.md) |
| `ComponentPath` | [gui-and-screens](../systems/client/gui-and-screens.md) |
| `ComponentPredicateParser` | [brigadier-and-commands](../systems/commands/brigadier-and-commands.md) |
| `ComponentRenderUtils` | [text-and-fonts](../systems/client/text-and-fonts.md) |
| `ComponentSerialization` | [naming-drift](../reference/naming-drift.md), [entity-selectors](../systems/commands/entity-selectors.md), [codecs-nbt-json](../systems/foundations/codecs-nbt-json.md), [text-components](../systems/foundations/text-components.md), [packets-and-stream-codecs](../systems/networking/packets-and-stream-codecs.md) |
| `ComponentUtils` | [naming-drift](../reference/naming-drift.md), [text-components](../systems/foundations/text-components.md) |
| `ComposableEntryContainer` | [loot-tables](../systems/items/loot-tables.md) |
| `CompositeEntryBase` | [loot-tables](../systems/items/loot-tables.md) |
| `CompositePackResources` | [resource-system](../systems/foundations/resource-system.md) |
| `CompoundContainer` | [containers-and-menus](../systems/items/containers-and-menus.md), [loot-tables](../systems/items/loot-tables.md) |
| `CompoundTag` | [glossary](../reference/glossary.md), [block-entities](../systems/blocks/block-entities.md), [codecs-nbt-json](../systems/foundations/codecs-nbt-json.md), [chunk-anatomy](../systems/world/chunk-anatomy.md) |
| `CompressionDecoder` | [packets-and-stream-codecs](../systems/networking/packets-and-stream-codecs.md), [the-connection](../systems/networking/the-connection.md) |
| `CompressionEncoder` | [the-connection](../systems/networking/the-connection.md) |
| `ConcentricRingsStructurePlacement` | [structure-placement](../systems/worldgen/structure-placement.md) |
| `ConcurrentHolderGetter` | [identifiers-and-registries](../systems/foundations/identifiers-and-registries.md) |
| `ConditionalBlockModel` | [block-entity-rendering](../systems/rendering/block-entity-rendering.md) |
| `ConditionalEffect` | [enchantments](../systems/items/enchantments.md) |
| `ConditionalItemModel` | [naming-drift](../reference/naming-drift.md) |
| `ConditionReference` | [contexts-and-predicates](../systems/items/contexts-and-predicates.md) |
| `Configuration` | [naming-drift](../reference/naming-drift.md), [chunk-anatomy](../systems/world/chunk-anatomy.md) |
| `ConfigurationProtocols` | [packets-and-stream-codecs](../systems/networking/packets-and-stream-codecs.md), [protocol-phases](../systems/networking/protocol-phases.md) |
| `ConfigurationTask` | [protocol-phases](../systems/networking/protocol-phases.md), [players-and-sessions](../systems/server/players-and-sessions.md) |
| `ConfiguredFeature` | [data-driven-types](../systems/foundations/data-driven-types.md), [features-and-placement](../systems/worldgen/features-and-placement.md) |
| `ConfiguredWorldCarver` | [data-driven-types](../systems/foundations/data-driven-types.md), [terrain](../systems/worldgen/terrain.md) |
| `ConfirmationDialog` | [dialogs](../systems/commands/dialogs.md) |
| `ConfirmExperimentalFeaturesScreen` | [creating-a-world](../systems/worldgen/creating-a-world.md) |
| `Connection` | [naming-drift](../reference/naming-drift.md), [threads](../reference/threads.md), [anatomy](../systems/anatomy/anatomy.md), [debugging-the-running-game](../systems/client/debugging-the-running-game.md), [codecs-nbt-json](../systems/foundations/codecs-nbt-json.md), [data-components](../systems/foundations/data-components.md), [packets-and-stream-codecs](../systems/networking/packets-and-stream-codecs.md), [protocol-phases](../systems/networking/protocol-phases.md), [the-connection](../systems/networking/the-connection.md), [the-two-phase-tick](../systems/player/the-two-phase-tick.md), [players-and-sessions](../systems/server/players-and-sessions.md), [server-tick](../systems/server/server-tick.md) |
| `ConnectionProtocol` | [packets-and-stream-codecs](../systems/networking/packets-and-stream-codecs.md), [protocol-phases](../systems/networking/protocol-phases.md), [the-connection](../systems/networking/the-connection.md) |
| `ConnectScreen` | [protocol-phases](../systems/networking/protocol-phases.md), [the-connection](../systems/networking/the-connection.md) |
| `ConsecutiveExecutor` | [anatomy](../systems/anatomy/anatomy.md), [resource-system](../systems/foundations/resource-system.md), [chunk-generation-pipeline](../systems/world/chunk-generation-pipeline.md), [chunk-storage](../systems/world/chunk-storage.md), [lighting](../systems/world/lighting.md), [features-and-placement](../systems/worldgen/features-and-placement.md) |
| `ConsoleInput` | [starting-a-server](../systems/server/starting-a-server.md) |
| `ConstantValue` | [contexts-and-predicates](../systems/items/contexts-and-predicates.md) |
| `Consumable` | [naming-drift](../reference/naming-drift.md), [data-components](../systems/foundations/data-components.md), [data-driven-types](../systems/foundations/data-driven-types.md), [using-an-item](../systems/items/using-an-item.md), [hunger-and-experience](../systems/player/hunger-and-experience.md) |
| `ConsumableListener` | [using-an-item](../systems/items/using-an-item.md), [hunger-and-experience](../systems/player/hunger-and-experience.md) |
| `Consumables` | [using-an-item](../systems/items/using-an-item.md) |
| `ConsumeEffect` | [data-driven-types](../systems/foundations/data-driven-types.md), [using-an-item](../systems/items/using-an-item.md), [packets-and-stream-codecs](../systems/networking/packets-and-stream-codecs.md), [hunger-and-experience](../systems/player/hunger-and-experience.md) |
| `ConsumeItemTrigger` | [contexts-and-predicates](../systems/items/contexts-and-predicates.md) |
| `Container` | [naming-drift](../reference/naming-drift.md), [block-entities](../systems/blocks/block-entities.md), [containers-and-menus](../systems/items/containers-and-menus.md), [loot-tables](../systems/items/loot-tables.md), [player-anatomy](../systems/player/player-anatomy.md) |
| `ContainerData` | [containers-and-menus](../systems/items/containers-and-menus.md) |
| `ContainerEntity` | [contexts-and-predicates](../systems/items/contexts-and-predicates.md) |
| `ContainerEventHandler` | [gui-and-screens](../systems/client/gui-and-screens.md) |
| `ContainerHelper` | [codecs-nbt-json](../systems/foundations/codecs-nbt-json.md) |
| `ContainerInput` | [naming-drift](../reference/naming-drift.md), [gui-and-screens](../systems/client/gui-and-screens.md), [containers-and-menus](../systems/items/containers-and-menus.md) |
| `ContainerLevelAccess` | [data-components](../systems/foundations/data-components.md), [containers-and-menus](../systems/items/containers-and-menus.md), [enchanting](../systems/items/enchanting.md), [recipes](../systems/items/recipes.md) |
| `ContainerListener` | [containers-and-menus](../systems/items/containers-and-menus.md) |
| `ContainerObjectSelectionList` | [gui-and-screens](../systems/client/gui-and-screens.md) |
| `Containers` | [block-entities](../systems/blocks/block-entities.md) |
| `ContainerSingleItem` | [scheduled-ticks](../systems/world/scheduled-ticks.md) |
| `ContainerSynchronizer` | [containers-and-menus](../systems/items/containers-and-menus.md) |
| `ContainerUser` | [entity-anatomy](../systems/entities/entity-anatomy.md), [player-anatomy](../systems/player/player-anatomy.md) |
| `ContextAwarePredicate` | [advancements](../systems/commands/advancements.md), [contexts-and-predicates](../systems/items/contexts-and-predicates.md) |
| `ContextChain` | [scoreboard-and-data](../systems/commands/scoreboard-and-data.md) |
| `ContextKey` | [naming-drift](../reference/naming-drift.md), [contexts-and-predicates](../systems/items/contexts-and-predicates.md) |
| `ContextKeySet` | [naming-drift](../reference/naming-drift.md), [contexts-and-predicates](../systems/items/contexts-and-predicates.md) |
| `ContextMap` | [contexts-and-predicates](../systems/items/contexts-and-predicates.md) |
| `ContextualBar` | [hud-elements](../reference/hud-elements.md), [hud](../systems/client/hud.md) |
| `ContinuationTask` | [functions-and-macros](../systems/commands/functions-and-macros.md), [the-execution-engine](../systems/commands/the-execution-engine.md) |
| `ContinuousProfiler` | [the-client-loop](../systems/client/the-client-loop.md) |
| `Coordinates` | [brigadier-and-commands](../systems/commands/brigadier-and-commands.md) |
| `CopperChestBlock` | [block-entities](../systems/blocks/block-entities.md) |
| `CopperGolem` | [entity-anatomy](../systems/entities/entity-anatomy.md), [pathfinding](../systems/entities/pathfinding.md), [synched-entity-data](../systems/entities/synched-entity-data.md) |
| `CopperGolemStatueBlock` | [block-entities](../systems/blocks/block-entities.md) |
| `CopperGolemStatueBlockEntity` | [block-entities](../systems/blocks/block-entities.md) |
| `CopperGolemStatueSpecialRenderer` | [block-entity-rendering](../systems/rendering/block-entity-rendering.md) |
| `CopyComponentsFunction` | [data-components](../systems/foundations/data-components.md) |
| `CountOnEveryLayerPlacement` | [features-and-placement](../systems/worldgen/features-and-placement.md) |
| `CountPlacement` | [features-and-placement](../systems/worldgen/features-and-placement.md) |
| `CrafterBlock` | [recipes](../systems/items/recipes.md) |
| `CrafterSlot` | [containers-and-menus](../systems/items/containers-and-menus.md) |
| `CraftingContainer` | [recipes](../systems/items/recipes.md) |
| `CraftingInput` | [recipes](../systems/items/recipes.md) |
| `CraftingMenu` | [containers-and-menus](../systems/items/containers-and-menus.md), [recipes](../systems/items/recipes.md) |
| `CraftingRecipe` | [recipes](../systems/items/recipes.md) |
| `CrashReport` | [anatomy](../systems/anatomy/anatomy.md), [the-window](../systems/rendering/the-window.md), [starting-a-server](../systems/server/starting-a-server.md) |
| `Creaking` | [pathfinding](../systems/entities/pathfinding.md) |
| `CreakingHeartDecorator` | [trees](../systems/worldgen/trees.md) |
| `CreateBuffetWorldScreen` | [creating-a-world](../systems/worldgen/creating-a-world.md) |
| `CreateFlatWorldScreen` | [creating-a-world](../systems/worldgen/creating-a-world.md) |
| `CreateWorldCallback` | [creating-a-world](../systems/worldgen/creating-a-world.md) |
| `CreateWorldScreen` | [creating-a-world](../systems/worldgen/creating-a-world.md) |
| `CreativeModeInventoryScreen` | [containers-and-menus](../systems/items/containers-and-menus.md) |
| `CreativeModeTabs` | [biggest](../maps/biggest.md), [enchanting](../systems/items/enchanting.md) |
| `Creeper` | [what-the-client-is-told](../systems/networking/what-the-client-is-told.md), [post-processing](../systems/rendering/post-processing.md) |
| `CriteriaTriggers` | [naming-drift](../reference/naming-drift.md), [block-interaction](../systems/blocks/block-interaction.md), [advancements](../systems/commands/advancements.md), [containers-and-menus](../systems/items/containers-and-menus.md), [items-and-stacks](../systems/items/items-and-stacks.md), [loot-tables](../systems/items/loot-tables.md), [recipes](../systems/items/recipes.md), [using-an-item](../systems/items/using-an-item.md), [the-spear](../systems/player/the-spear.md), [game-events-and-vibrations](../systems/world/game-events-and-vibrations.md) |
| `Criterion` | [advancements](../systems/commands/advancements.md), [data-driven-types](../systems/foundations/data-driven-types.md) |
| `CriterionTrigger` | [glossary](../reference/glossary.md), [advancements](../systems/commands/advancements.md), [data-driven-types](../systems/foundations/data-driven-types.md) |
| `CriterionTriggerInstance` | [naming-drift](../reference/naming-drift.md), [advancements](../systems/commands/advancements.md) |
| `CrossbowAttack` | [using-an-item](../systems/items/using-an-item.md) |
| `CrossbowItem` | [enchantments](../systems/items/enchantments.md), [using-an-item](../systems/items/using-an-item.md) |
| `CrossbowPull` | [enchantments](../systems/items/enchantments.md), [using-an-item](../systems/items/using-an-item.md) |
| `CrossCollisionBlock` | [movement-and-collision](../systems/entities/movement-and-collision.md) |
| `CrossFrameResourcePool` | [blaze3d](../systems/rendering/blaze3d.md), [post-processing](../systems/rendering/post-processing.md) |
| `CrudeIncrementalIntIdentityHashBiMap` | [synched-entity-data](../systems/entities/synched-entity-data.md), [chunk-anatomy](../systems/world/chunk-anatomy.md) |
| `Crypt` | [protocol-phases](../systems/networking/protocol-phases.md) |
| `CubeDefinition` | [entity-rendering](../systems/rendering/entity-rendering.md) |
| `CubeDeformation` | [entity-rendering](../systems/rendering/entity-rendering.md) |
| `CubeListBuilder` | [entity-rendering](../systems/rendering/entity-rendering.md) |
| `CubeVoxelShape` | [math-and-primitives](../reference/math-and-primitives.md) |
| `CubicSpline` | [density-function-nodes](../reference/density-function-nodes.md), [math-and-primitives](../reference/math-and-primitives.md) |
| `CuboidFace` | [naming-drift](../reference/naming-drift.md), [models-and-atlases](../systems/rendering/models-and-atlases.md) |
| `CuboidModel` | [models-and-atlases](../systems/rendering/models-and-atlases.md) |
| `CuboidModelElement` | [naming-drift](../reference/naming-drift.md), [models-and-atlases](../systems/rendering/models-and-atlases.md) |
| `CuboidRotation` | [models-and-atlases](../systems/rendering/models-and-atlases.md) |
| `Cursor3D` | [math-and-primitives](../reference/math-and-primitives.md), [movement-and-collision](../systems/entities/movement-and-collision.md) |
| `CursorType` | [the-window](../systems/rendering/the-window.md) |
| `CursorTypes` | [the-window](../systems/rendering/the-window.md) |
| `CustomAll` | [dialogs](../systems/commands/dialogs.md) |
| `CustomBossEvents` | [level-data-and-rules](../reference/level-data-and-rules.md) |
| `CustomCommandExecutor` | [scoreboard-and-data](../systems/commands/scoreboard-and-data.md), [the-execution-engine](../systems/commands/the-execution-engine.md) |
| `CustomData` | [codecs-nbt-json](../systems/foundations/codecs-nbt-json.md), [data-components](../systems/foundations/data-components.md) |
| `CustomFeatureRenderer` | [submit-phases](../reference/submit-phases.md) |
| `CustomHeadLayer` | [entity-rendering](../systems/rendering/entity-rendering.md) |
| `CustomModifierExecutor` | [the-execution-engine](../systems/commands/the-execution-engine.md) |
| `CustomPacketPayload` | [packets-and-stream-codecs](../systems/networking/packets-and-stream-codecs.md) |
| `CustomRecipe` | [recipes](../systems/items/recipes.md) |
| `CustomSpawner` | [entity-lifecycle](../systems/entities/entity-lifecycle.md) |
| `CycleButton` | [gui-and-screens](../systems/client/gui-and-screens.md) |
| `DamageCommand` | [damage-and-death](../systems/entities/damage-and-death.md) |
| `DamageEffects` | [damage-and-death](../systems/entities/damage-and-death.md) |
| `DamageEntity` | [enchantments](../systems/items/enchantments.md) |
| `DamageScaling` | [damage-and-death](../systems/entities/damage-and-death.md) |
| `DamageSource` | [damage-and-death](../systems/entities/damage-and-death.md), [text-components](../systems/foundations/text-components.md), [enchantments](../systems/items/enchantments.md), [hunger-and-experience](../systems/player/hunger-and-experience.md) |
| `DamageSources` | [damage-and-death](../systems/entities/damage-and-death.md) |
| `DamageType` | [damage-and-death](../systems/entities/damage-and-death.md), [text-components](../systems/foundations/text-components.md) |
| `DamageTypes` | [damage-and-death](../systems/entities/damage-and-death.md), [hunger-and-experience](../systems/player/hunger-and-experience.md), [the-spear](../systems/player/the-spear.md) |
| `DamageTypeTags` | [non-living-damage](../reference/non-living-damage.md), [damage-and-death](../systems/entities/damage-and-death.md), [tags](../systems/foundations/tags.md), [items-and-stacks](../systems/items/items-and-stacks.md) |
| `DarknessFogEnvironment` | [lightmap-fog-and-sky](../systems/rendering/lightmap-fog-and-sky.md) |
| `DarkOakFoliagePlacer` | [trees](../systems/worldgen/trees.md) |
| `DarkOakTrunkPlacer` | [trees](../systems/worldgen/trees.md) |
| `DataAccessor` | [scoreboard-and-data](../systems/commands/scoreboard-and-data.md) |
| `DataCommands` | [brigadier-and-commands](../systems/commands/brigadier-and-commands.md), [scoreboard-and-data](../systems/commands/scoreboard-and-data.md) |
| `DataComponentExactPredicate` | [data-components](../systems/foundations/data-components.md) |
| `DataComponentGetter` | [entity-anatomy](../systems/entities/entity-anatomy.md), [data-components](../systems/foundations/data-components.md), [items-and-stacks](../systems/items/items-and-stacks.md) |
| `DataComponentHolder` | [data-components](../systems/foundations/data-components.md) |
| `DataComponentInitializers` | [data-components](../systems/foundations/data-components.md), [items-and-stacks](../systems/items/items-and-stacks.md) |
| `DataComponentLookup` | [data-components](../systems/foundations/data-components.md), [identifiers-and-registries](../systems/foundations/identifiers-and-registries.md) |
| `DataComponentMap` | [codecs-nbt-json](../systems/foundations/codecs-nbt-json.md), [data-components](../systems/foundations/data-components.md), [identifiers-and-registries](../systems/foundations/identifiers-and-registries.md), [enchantments](../systems/items/enchantments.md), [items-and-stacks](../systems/items/items-and-stacks.md) |
| `DataComponentMatchers` | [advancements](../systems/commands/advancements.md), [data-components](../systems/foundations/data-components.md) |
| `DataComponentPatch` | [codecs-nbt-json](../systems/foundations/codecs-nbt-json.md), [data-components](../systems/foundations/data-components.md), [containers-and-menus](../systems/items/containers-and-menus.md), [items-and-stacks](../systems/items/items-and-stacks.md), [packets-and-stream-codecs](../systems/networking/packets-and-stream-codecs.md) |
| `DataComponentPredicate` | [data-components](../systems/foundations/data-components.md), [data-driven-types](../systems/foundations/data-driven-types.md) |
| `DataComponentPredicates` | [data-components](../systems/foundations/data-components.md) |
| `DataComponents` | [fanin](../maps/fanin.md), [naming-drift](../reference/naming-drift.md), [V · Blocks](../systems/blocks/README.md), [block-breaking](../systems/blocks/block-breaking.md), [attributes](../systems/entities/attributes.md), [damage-and-death](../systems/entities/damage-and-death.md), [entity-anatomy](../systems/entities/entity-anatomy.md), [synched-entity-data](../systems/entities/synched-entity-data.md), [data-components](../systems/foundations/data-components.md), [text-components](../systems/foundations/text-components.md), [enchanting](../systems/items/enchanting.md), [enchantments](../systems/items/enchantments.md), [items-and-stacks](../systems/items/items-and-stacks.md), [loot-tables](../systems/items/loot-tables.md), [using-an-item](../systems/items/using-an-item.md), [hunger-and-experience](../systems/player/hunger-and-experience.md), [the-spear](../systems/player/the-spear.md), [the-sword-swing](../systems/player/the-sword-swing.md), [models-and-atlases](../systems/rendering/models-and-atlases.md) |
| `DataComponentType` | [Reference](../reference/README.md), [glossary](../reference/glossary.md), [naming-drift](../reference/naming-drift.md), [II · Foundations](../systems/foundations/README.md), [codecs-nbt-json](../systems/foundations/codecs-nbt-json.md), [data-components](../systems/foundations/data-components.md), [items-and-stacks](../systems/items/items-and-stacks.md) |
| `DataFixer` | [codecs-nbt-json](../systems/foundations/codecs-nbt-json.md) |
| `DataFixers` | [biggest](../maps/biggest.md), [anatomy](../systems/anatomy/anatomy.md), [what-this-book-skips](../systems/anatomy/what-this-book-skips.md), [codecs-nbt-json](../systems/foundations/codecs-nbt-json.md), [starting-a-server](../systems/server/starting-a-server.md) |
| `DataFixTypes` | [level-data-and-rules](../reference/level-data-and-rules.md), [what-this-book-skips](../systems/anatomy/what-this-book-skips.md), [advancements](../systems/commands/advancements.md), [codecs-nbt-json](../systems/foundations/codecs-nbt-json.md), [players-and-sessions](../systems/server/players-and-sessions.md), [points-of-interest](../systems/world/points-of-interest.md) |
| `DataLayer` | [chunk-storage](../systems/world/chunk-storage.md), [lighting](../systems/world/lighting.md) |
| `DataLayerStorageMap` | [lighting](../systems/world/lighting.md) |
| `DataPackConfig` | [resource-system](../systems/foundations/resource-system.md) |
| `DatapackStructureReport` | [data-driven-types](../systems/foundations/data-driven-types.md) |
| `DataResult` | [identifiers-and-registries](../systems/foundations/identifiers-and-registries.md), [chunk-anatomy](../systems/world/chunk-anatomy.md) |
| `DataSlot` | [containers-and-menus](../systems/items/containers-and-menus.md), [enchanting](../systems/items/enchanting.md) |
| `DataSource` | [text-components](../systems/foundations/text-components.md) |
| `DaylightDetectorBlockEntity` | [diodes-and-observers](../systems/blocks/diodes-and-observers.md) |
| `DeathMessageType` | [damage-and-death](../systems/entities/damage-and-death.md), [text-components](../systems/foundations/text-components.md) |
| `DeathProtection` | [damage-and-death](../systems/entities/damage-and-death.md), [data-driven-types](../systems/foundations/data-driven-types.md) |
| `DeathScreen` | [damage-and-death](../systems/entities/damage-and-death.md), [text-components](../systems/foundations/text-components.md) |
| `DebugBeeInfo` | [debugging-the-running-game](../systems/client/debugging-the-running-game.md) |
| `DebugBrainDump` | [debugging-the-running-game](../systems/client/debugging-the-running-game.md) |
| `DebugBreezeInfo` | [debugging-the-running-game](../systems/client/debugging-the-running-game.md) |
| `DebugCommand` | [functions-and-macros](../systems/commands/functions-and-macros.md), [the-execution-engine](../systems/commands/the-execution-engine.md) |
| `DebugConfigCommand` | [brigadier-and-commands](../systems/commands/brigadier-and-commands.md), [dialogs](../systems/commands/dialogs.md), [protocol-phases](../systems/networking/protocol-phases.md), [players-and-sessions](../systems/server/players-and-sessions.md) |
| `DebugEntityBlockIntersection` | [debugging-the-running-game](../systems/client/debugging-the-running-game.md) |
| `DebugEntryLight` | [lighting](../systems/world/lighting.md) |
| `DebugEntryParticleRenderStats` | [particles](../systems/rendering/particles.md) |
| `DebugEntryPostEffect` | [post-processing](../systems/rendering/post-processing.md) |
| `DebugGameEventInfo` | [debugging-the-running-game](../systems/client/debugging-the-running-game.md) |
| `DebugGameEventListenerInfo` | [debugging-the-running-game](../systems/client/debugging-the-running-game.md) |
| `DebugGoalInfo` | [debugging-the-running-game](../systems/client/debugging-the-running-game.md) |
| `DebugHiveInfo` | [debugging-the-running-game](../systems/client/debugging-the-running-game.md) |
| `DebugLevelSource` | [chunk-generation-pipeline](../systems/world/chunk-generation-pipeline.md), [biomes](../systems/worldgen/biomes.md), [terrain](../systems/worldgen/terrain.md) |
| `DebugMemoryUntracker` | [the-window](../systems/rendering/the-window.md) |
| `DebugMobSpawningCommand` | [brigadier-and-commands](../systems/commands/brigadier-and-commands.md) |
| `DebugOptionsScreen` | [hud-elements](../reference/hud-elements.md) |
| `DebugPathCommand` | [brigadier-and-commands](../systems/commands/brigadier-and-commands.md) |
| `DebugPathInfo` | [debugging-the-running-game](../systems/client/debugging-the-running-game.md) |
| `DebugPoiInfo` | [debugging-the-running-game](../systems/client/debugging-the-running-game.md) |
| `DebugRenderer` | [debugging-the-running-game](../systems/client/debugging-the-running-game.md) |
| `DebugScreenDisplayer` | [hud](../systems/client/hud.md) |
| `DebugScreenEntries` | [hud](../systems/client/hud.md) |
| `DebugScreenEntry` | [hud](../systems/client/hud.md) |
| `DebugScreenEntryList` | [what-this-book-skips](../systems/anatomy/what-this-book-skips.md), [hud](../systems/client/hud.md) |
| `DebugScreenEntryStatus` | [hud](../systems/client/hud.md) |
| `DebugScreenOverlay` | [threads](../reference/threads.md), [debugging-the-running-game](../systems/client/debugging-the-running-game.md) |
| `DebugScreenProfile` | [hud](../systems/client/hud.md) |
| `DebugStructureInfo` | [debugging-the-running-game](../systems/client/debugging-the-running-game.md) |
| `DebugSubscription` | [debugging-the-running-game](../systems/client/debugging-the-running-game.md) |
| `DebugSubscriptions` | [debugging-the-running-game](../systems/client/debugging-the-running-game.md), [ai-goals-and-brains](../systems/entities/ai-goals-and-brains.md), [pathfinding](../systems/entities/pathfinding.md), [server-level-tick](../systems/server/server-level-tick.md), [server-tick](../systems/server/server-tick.md), [game-events-and-vibrations](../systems/world/game-events-and-vibrations.md), [points-of-interest](../systems/world/points-of-interest.md), [jigsaw-and-templates](../systems/worldgen/jigsaw-and-templates.md) |
| `DebugValueAccess` | [debugging-the-running-game](../systems/client/debugging-the-running-game.md) |
| `DebugValueSource` | [debugging-the-running-game](../systems/client/debugging-the-running-game.md), [entity-anatomy](../systems/entities/entity-anatomy.md) |
| `DecoratedPotPatterns` | [what-this-book-skips](../systems/anatomy/what-this-book-skips.md) |
| `DecoratedPotRecipe` | [recipes](../systems/items/recipes.md) |
| `DecoratedPotSpecialRenderer` | [block-entity-rendering](../systems/rendering/block-entity-rendering.md) |
| `DedicatedPlayerList` | [players-and-sessions](../systems/server/players-and-sessions.md), [starting-a-server](../systems/server/starting-a-server.md) |
| `DedicatedServer` | [level-data-and-rules](../reference/level-data-and-rules.md), [threads](../reference/threads.md), [anatomy](../systems/anatomy/anatomy.md), [what-this-book-skips](../systems/anatomy/what-this-book-skips.md), [block-interaction](../systems/blocks/block-interaction.md), [what-the-client-is-told](../systems/networking/what-the-client-is-told.md), [how-a-server-dies](../systems/server/how-a-server-dies.md), [players-and-sessions](../systems/server/players-and-sessions.md), [server-tick](../systems/server/server-tick.md), [starting-a-server](../systems/server/starting-a-server.md), [chunk-storage](../systems/world/chunk-storage.md) |
| `DedicatedServerProperties` | [threads](../reference/threads.md), [anatomy](../systems/anatomy/anatomy.md), [block-interaction](../systems/blocks/block-interaction.md), [how-a-server-dies](../systems/server/how-a-server-dies.md), [server-tick](../systems/server/server-tick.md), [starting-a-server](../systems/server/starting-a-server.md), [chunk-storage](../systems/world/chunk-storage.md), [creating-a-world](../systems/worldgen/creating-a-world.md) |
| `DedicatedServerSettings` | [starting-a-server](../systems/server/starting-a-server.md) |
| `DefaultAttributes` | [attributes](../systems/entities/attributes.md) |
| `DefaultBlockInteractionTrigger` | [contexts-and-predicates](../systems/items/contexts-and-predicates.md) |
| `DefaultedMappedRegistry` | [entity-anatomy](../systems/entities/entity-anatomy.md), [identifiers-and-registries](../systems/foundations/identifiers-and-registries.md), [game-events-and-vibrations](../systems/world/game-events-and-vibrations.md) |
| `DefaultedRegistry` | [identifiers-and-registries](../systems/foundations/identifiers-and-registries.md) |
| `DefaultPlayerSkin` | [entity-rendering](../systems/rendering/entity-rendering.md) |
| `DefaultRedstoneWireEvaluator` | [signal-and-dust](../systems/blocks/signal-and-dust.md) |
| `DefaultVertexFormat` | [blaze3d](../systems/rendering/blaze3d.md) |
| `DelegatingOps` | [codecs-nbt-json](../systems/foundations/codecs-nbt-json.md) |
| `DeltaTracker` | [introduction](../introduction.md), [naming-drift](../reference/naming-drift.md), [anatomy](../systems/anatomy/anatomy.md), [the-client-loop](../systems/client/the-client-loop.md), [block-entity-rendering](../systems/rendering/block-entity-rendering.md), [models-and-atlases](../systems/rendering/models-and-atlases.md), [the-frame](../systems/rendering/the-frame.md) |
| `Density` | [density-functions](../systems/worldgen/density-functions.md) |
| `DensityFunction` | [density-function-nodes](../reference/density-function-nodes.md), [data-driven-types](../systems/foundations/data-driven-types.md), [XII · World generation](../systems/worldgen/README.md), [density-functions](../systems/worldgen/density-functions.md) |
| `DensityFunctions` | [biggest](../maps/biggest.md), [density-function-nodes](../reference/density-function-nodes.md), [naming-drift](../reference/naming-drift.md), [data-driven-types](../systems/foundations/data-driven-types.md), [blending](../systems/worldgen/blending.md), [density-functions](../systems/worldgen/density-functions.md), [terrain](../systems/worldgen/terrain.md) |
| `DependencySorter` | [tags](../systems/foundations/tags.md) |
| `DepthStencilState` | [blaze3d](../systems/rendering/blaze3d.md) |
| `DerivedLevelData` | [level-data-and-rules](../reference/level-data-and-rules.md), [starting-a-server](../systems/server/starting-a-server.md) |
| `DesertPyramidPiece` | [hand-built-structures](../systems/worldgen/hand-built-structures.md) |
| `DestructionQueue` | [what-this-book-skips](../systems/anatomy/what-this-book-skips.md) |
| `DeviceFeatures` | [blaze3d](../systems/rendering/blaze3d.md) |
| `DeviceInfo` | [blaze3d](../systems/rendering/blaze3d.md) |
| `DeviceLimits` | [blaze3d](../systems/rendering/blaze3d.md) |
| `DeviceList` | [sound-engine](../systems/client/sound-engine.md) |
| `DeviceType` | [blaze3d](../systems/rendering/blaze3d.md) |
| `Dialog` | [dialogs](../systems/commands/dialogs.md), [data-driven-types](../systems/foundations/data-driven-types.md), [text-components](../systems/foundations/text-components.md) |
| `DialogAction` | [dialogs](../systems/commands/dialogs.md) |
| `DialogBody` | [dialogs](../systems/commands/dialogs.md), [data-driven-types](../systems/foundations/data-driven-types.md) |
| `DialogBodyHandlers` | [dialogs](../systems/commands/dialogs.md) |
| `DialogConnectionAccess` | [dialogs](../systems/commands/dialogs.md) |
| `DialogControlSet` | [dialogs](../systems/commands/dialogs.md) |
| `DialogListDialog` | [dialogs](../systems/commands/dialogs.md) |
| `Dialogs` | [dialogs](../systems/commands/dialogs.md) |
| `DialogScreen` | [dialogs](../systems/commands/dialogs.md) |
| `DialogScreens` | [gui-and-screens](../systems/client/gui-and-screens.md), [dialogs](../systems/commands/dialogs.md) |
| `DialogTags` | [dialogs](../systems/commands/dialogs.md) |
| `DialogTypes` | [dialogs](../systems/commands/dialogs.md) |
| `Dictionary` | [brigadier-and-commands](../systems/commands/brigadier-and-commands.md) |
| `Difficulty` | [level-data-and-rules](../reference/level-data-and-rules.md) |
| `DifficultyInstance` | [level-data-and-rules](../reference/level-data-and-rules.md) |
| `DimensionDefaults` | [level-data-and-rules](../reference/level-data-and-rules.md) |
| `DimensionType` | [glossary](../reference/glossary.md), [level-data-and-rules](../reference/level-data-and-rules.md), [math-and-primitives](../reference/math-and-primitives.md), [naming-drift](../reference/naming-drift.md), [entity-lifecycle](../systems/entities/entity-lifecycle.md), [lightmap-fog-and-sky](../systems/rendering/lightmap-fog-and-sky.md), [visibility-and-the-frame-graph](../systems/rendering/visibility-and-the-frame-graph.md), [chunk-storage](../systems/world/chunk-storage.md), [environment-attributes-and-timelines](../systems/world/environment-attributes-and-timelines.md), [lighting](../systems/world/lighting.md) |
| `DiodeBlock` | [diodes-and-observers](../systems/blocks/diodes-and-observers.md), [scheduled-ticks](../systems/world/scheduled-ticks.md) |
| `Direction` | [fanin](../maps/fanin.md), [math-and-primitives](../reference/math-and-primitives.md), [submit-phases](../reference/submit-phases.md), [V · Blocks](../systems/blocks/README.md), [block-interaction](../systems/blocks/block-interaction.md), [blocks-and-states](../systems/blocks/blocks-and-states.md), [diodes-and-observers](../systems/blocks/diodes-and-observers.md), [pistons-and-block-events](../systems/blocks/pistons-and-block-events.md), [signal-and-dust](../systems/blocks/signal-and-dust.md), [movement-and-collision](../systems/entities/movement-and-collision.md), [models-and-atlases](../systems/rendering/models-and-atlases.md), [game-events-and-vibrations](../systems/world/game-events-and-vibrations.md) |
| `Direction8` | [math-and-primitives](../reference/math-and-primitives.md), [blending](../systems/worldgen/blending.md) |
| `DirectionalBlock` | [diodes-and-observers](../systems/blocks/diodes-and-observers.md) |
| `DirectoryLister` | [models-and-atlases](../systems/rendering/models-and-atlases.md) |
| `DirectoryLock` | [how-a-server-dies](../systems/server/how-a-server-dies.md), [starting-a-server](../systems/server/starting-a-server.md) |
| `DirectoryValidator` | [resource-system](../systems/foundations/resource-system.md) |
| `DiscardedPayload` | [packets-and-stream-codecs](../systems/networking/packets-and-stream-codecs.md) |
| `DiscardedQueryPayload` | [protocol-phases](../systems/networking/protocol-phases.md) |
| `DisconnectionDetails` | [the-connection](../systems/networking/the-connection.md) |
| `DiscreteVoxelShape` | [math-and-primitives](../reference/math-and-primitives.md) |
| `DispenserBlock` | [pistons-and-block-events](../systems/blocks/pistons-and-block-events.md) |
| `Display` | [non-living-damage](../reference/non-living-damage.md), [the-client-level](../systems/client/the-client-level.md), [damage-and-death](../systems/entities/damage-and-death.md), [synched-entity-data](../systems/entities/synched-entity-data.md), [entity-rendering](../systems/rendering/entity-rendering.md) |
| `DisplayData` | [the-window](../systems/rendering/the-window.md) |
| `DisplayInfo` | [advancements](../systems/commands/advancements.md) |
| `DisplaySlot` | [hud-elements](../reference/hud-elements.md), [scoreboard-and-data](../systems/commands/scoreboard-and-data.md) |
| `DistanceManager` | [block-entities](../systems/blocks/block-entities.md), [entity-lifecycle](../systems/entities/entity-lifecycle.md), [what-the-client-is-told](../systems/networking/what-the-client-is-told.md), [how-a-server-dies](../systems/server/how-a-server-dies.md), [players-and-sessions](../systems/server/players-and-sessions.md), [server-level-tick](../systems/server/server-level-tick.md), [chunk-generation-pipeline](../systems/world/chunk-generation-pipeline.md), [scheduled-ticks](../systems/world/scheduled-ticks.md), [tickets-and-loading](../systems/world/tickets-and-loading.md) |
| `DistanceTrigger` | [contexts-and-predicates](../systems/items/contexts-and-predicates.md) |
| `DoorBlock` | [block-interaction](../systems/blocks/block-interaction.md), [blocks-and-states](../systems/blocks/blocks-and-states.md), [pistons-and-block-events](../systems/blocks/pistons-and-block-events.md) |
| `DownloadCacheCleaner` | [what-this-book-skips](../systems/anatomy/what-this-book-skips.md), [resource-system](../systems/foundations/resource-system.md) |
| `DownloadedPackSource` | [resource-system](../systems/foundations/resource-system.md) |
| `DownloadQueue` | [what-this-book-skips](../systems/anatomy/what-this-book-skips.md), [resource-system](../systems/foundations/resource-system.md) |
| `DrawableGizmoPrimitives` | [submit-phases](../reference/submit-phases.md) |
| `DriedGhastBlock` | [scheduled-ticks](../systems/world/scheduled-ticks.md) |
| `DropperBlock` | [pistons-and-block-events](../systems/blocks/pistons-and-block-events.md) |
| `DryFoliageColor` | [biomes](../systems/worldgen/biomes.md) |
| `DryFoliageColorReloadListener` | [resource-system](../systems/foundations/resource-system.md) |
| `DSL` | [fanin](../maps/fanin.md) |
| `DyeColor` | [synched-entity-data](../systems/entities/synched-entity-data.md) |
| `DyeRecipe` | [recipes](../systems/items/recipes.md) |
| `DynamicGameEventListener` | [entity-lifecycle](../systems/entities/entity-lifecycle.md), [game-events-and-vibrations](../systems/world/game-events-and-vibrations.md) |
| `DynamicGraphMinFixedPoint` | [lighting](../systems/world/lighting.md), [points-of-interest](../systems/world/points-of-interest.md), [tickets-and-loading](../systems/world/tickets-and-loading.md) |
| `DynamicLoot` | [loot-tables](../systems/items/loot-tables.md) |
| `DynamicOps` | [glossary](../reference/glossary.md), [codecs-nbt-json](../systems/foundations/codecs-nbt-json.md), [data-driven-types](../systems/foundations/data-driven-types.md), [containers-and-menus](../systems/items/containers-and-menus.md) |
| `DynamicTexture` | [lightmap-fog-and-sky](../systems/rendering/lightmap-fog-and-sky.md) |
| `DynamicUniforms` | [blaze3d](../systems/rendering/blaze3d.md) |
| `DynamicUniformStorage` | [blaze3d](../systems/rendering/blaze3d.md) |
| `EasingType` | [environment-attributes-and-timelines](../systems/world/environment-attributes-and-timelines.md) |
| `EditBox` | [gui-and-screens](../systems/client/gui-and-screens.md) |
| `EditWorldScreen` | [creating-a-world](../systems/worldgen/creating-a-world.md) |
| `EffectGlyph` | [naming-drift](../reference/naming-drift.md), [text-and-fonts](../systems/client/text-and-fonts.md) |
| `EmoteCommands` | [entity-selectors](../systems/commands/entity-selectors.md) |
| `EmptyLevelChunk` | [chunk-anatomy](../systems/world/chunk-anatomy.md) |
| `EmptyLootItem` | [loot-tables](../systems/items/loot-tables.md) |
| `EmptyPoolElement` | [jigsaw-and-templates](../systems/worldgen/jigsaw-and-templates.md) |
| `Enchantable` | [data-components](../systems/foundations/data-components.md), [enchanting](../systems/items/enchanting.md) |
| `EnchantCommand` | [brigadier-and-commands](../systems/commands/brigadier-and-commands.md), [enchanting](../systems/items/enchanting.md) |
| `EnchantedCountIncreaseFunction` | [naming-drift](../reference/naming-drift.md), [enchanting](../systems/items/enchanting.md), [enchantments](../systems/items/enchantments.md), [loot-tables](../systems/items/loot-tables.md) |
| `EnchantedItemInUse` | [enchantments](../systems/items/enchantments.md) |
| `EnchantingTableBlock` | [enchanting](../systems/items/enchanting.md) |
| `Enchantment` | [naming-drift](../reference/naming-drift.md), [contexts-and-predicates](../systems/items/contexts-and-predicates.md), [enchanting](../systems/items/enchanting.md), [enchantments](../systems/items/enchantments.md) |
| `EnchantmentActiveCheck` | [contexts-and-predicates](../systems/items/contexts-and-predicates.md) |
| `EnchantmentAttributeEffect` | [attributes](../systems/entities/attributes.md), [enchantments](../systems/items/enchantments.md) |
| `EnchantmentEffectComponents` | [naming-drift](../reference/naming-drift.md), [attributes](../systems/entities/attributes.md), [damage-and-death](../systems/entities/damage-and-death.md), [contexts-and-predicates](../systems/items/contexts-and-predicates.md), [enchantments](../systems/items/enchantments.md), [hunger-and-experience](../systems/player/hunger-and-experience.md) |
| `EnchantmentEntityEffect` | [data-driven-types](../systems/foundations/data-driven-types.md), [enchantments](../systems/items/enchantments.md) |
| `EnchantmentHelper` | [Reference](../reference/README.md), [attributes](../systems/entities/attributes.md), [damage-and-death](../systems/entities/damage-and-death.md), [data-components](../systems/foundations/data-components.md), [VII · Items and inventories](../systems/items/README.md), [enchanting](../systems/items/enchanting.md), [enchantments](../systems/items/enchantments.md), [items-and-stacks](../systems/items/items-and-stacks.md), [loot-tables](../systems/items/loot-tables.md), [using-an-item](../systems/items/using-an-item.md), [the-spear](../systems/player/the-spear.md), [the-sword-swing](../systems/player/the-sword-swing.md) |
| `EnchantmentInstance` | [data-components](../systems/foundations/data-components.md), [enchantments](../systems/items/enchantments.md) |
| `EnchantmentLevelProvider` | [contexts-and-predicates](../systems/items/contexts-and-predicates.md) |
| `EnchantmentLocationBasedEffect` | [data-driven-types](../systems/foundations/data-driven-types.md), [enchantments](../systems/items/enchantments.md) |
| `EnchantmentMenu` | [data-components](../systems/foundations/data-components.md), [enchanting](../systems/items/enchanting.md) |
| `EnchantmentNames` | [enchanting](../systems/items/enchanting.md) |
| `EnchantmentProvider` | [data-driven-types](../systems/foundations/data-driven-types.md), [enchanting](../systems/items/enchanting.md) |
| `Enchantments` | [block-breaking](../systems/blocks/block-breaking.md), [enchantments](../systems/items/enchantments.md) |
| `EnchantmentsByCost` | [enchanting](../systems/items/enchanting.md) |
| `EnchantmentsByCostWithDifficulty` | [enchanting](../systems/items/enchanting.md) |
| `EnchantmentScreen` | [enchanting](../systems/items/enchanting.md) |
| `EnchantmentTags` | [tags](../systems/foundations/tags.md), [enchanting](../systems/items/enchanting.md), [enchantments](../systems/items/enchantments.md) |
| `EnchantmentTarget` | [enchantments](../systems/items/enchantments.md) |
| `EnchantmentValueEffect` | [data-driven-types](../systems/foundations/data-driven-types.md), [enchantments](../systems/items/enchantments.md) |
| `EnchantRandomlyFunction` | [enchanting](../systems/items/enchanting.md), [loot-tables](../systems/items/loot-tables.md) |
| `EnchantWithLevelsFunction` | [enchanting](../systems/items/enchanting.md), [loot-tables](../systems/items/loot-tables.md) |
| `EncoderCache` | [data-components](../systems/foundations/data-components.md) |
| `EndCityPieces` | [hand-built-structures](../systems/worldgen/hand-built-structures.md) |
| `EndCrystal` | [non-living-damage](../reference/non-living-damage.md), [damage-and-death](../systems/entities/damage-and-death.md) |
| `EnderDragon` | [non-living-damage](../reference/non-living-damage.md), [damage-and-death](../systems/entities/damage-and-death.md), [entity-lifecycle](../systems/entities/entity-lifecycle.md) |
| `EnderDragonFight` | [level-data-and-rules](../reference/level-data-and-rules.md), [what-this-book-skips](../systems/anatomy/what-this-book-skips.md), [lighting](../systems/world/lighting.md), [tickets-and-loading](../systems/world/tickets-and-loading.md) |
| `EnderDragonPart` | [non-living-damage](../reference/non-living-damage.md), [entity-selectors](../systems/commands/entity-selectors.md), [damage-and-death](../systems/entities/damage-and-death.md), [entity-lifecycle](../systems/entities/entity-lifecycle.md), [what-the-client-is-told](../systems/networking/what-the-client-is-told.md), [chunk-storage](../systems/world/chunk-storage.md) |
| `EnderMan` | [post-processing](../systems/rendering/post-processing.md) |
| `EndFlashState` | [lightmap-fog-and-sky](../systems/rendering/lightmap-fog-and-sky.md), [environment-attributes-and-timelines](../systems/world/environment-attributes-and-timelines.md) |
| `EndPodiumFeature` | [features-and-placement](../systems/worldgen/features-and-placement.md) |
| `EndTag` | [codecs-nbt-json](../systems/foundations/codecs-nbt-json.md) |
| `Enemy` | [entity-anatomy](../systems/entities/entity-anatomy.md) |
| `Entity` | [biggest](../maps/biggest.md), [fanin](../maps/fanin.md), [hierarchy](../maps/hierarchy.md), [glossary](../reference/glossary.md), [hud-elements](../reference/hud-elements.md), [math-and-primitives](../reference/math-and-primitives.md), [naming-drift](../reference/naming-drift.md), [non-living-damage](../reference/non-living-damage.md), [blocks-and-states](../systems/blocks/blocks-and-states.md), [debugging-the-running-game](../systems/client/debugging-the-running-game.md), [prediction-and-acks](../systems/client/prediction-and-acks.md), [the-client-level](../systems/client/the-client-level.md), [entity-selectors](../systems/commands/entity-selectors.md), [scoreboard-and-data](../systems/commands/scoreboard-and-data.md), [attributes](../systems/entities/attributes.md), [authority](../systems/entities/authority.md), [damage-and-death](../systems/entities/damage-and-death.md), [entity-anatomy](../systems/entities/entity-anatomy.md), [entity-lifecycle](../systems/entities/entity-lifecycle.md), [movement-and-collision](../systems/entities/movement-and-collision.md), [pathfinding](../systems/entities/pathfinding.md), [synched-entity-data](../systems/entities/synched-entity-data.md), [codecs-nbt-json](../systems/foundations/codecs-nbt-json.md), [data-components](../systems/foundations/data-components.md), [tags](../systems/foundations/tags.md), [text-components](../systems/foundations/text-components.md), [enchantments](../systems/items/enchantments.md), [what-the-client-is-told](../systems/networking/what-the-client-is-told.md), [hunger-and-experience](../systems/player/hunger-and-experience.md), [input-to-movement](../systems/player/input-to-movement.md), [player-anatomy](../systems/player/player-anatomy.md), [the-spear](../systems/player/the-spear.md), [the-sword-swing](../systems/player/the-sword-swing.md), [the-two-phase-tick](../systems/player/the-two-phase-tick.md), [block-entity-rendering](../systems/rendering/block-entity-rendering.md), [entity-rendering](../systems/rendering/entity-rendering.md), [players-and-sessions](../systems/server/players-and-sessions.md), [server-level-tick](../systems/server/server-level-tick.md), [chunk-storage](../systems/world/chunk-storage.md), [environment-attributes-and-timelines](../systems/world/environment-attributes-and-timelines.md), [game-events-and-vibrations](../systems/world/game-events-and-vibrations.md), [tickets-and-loading](../systems/world/tickets-and-loading.md) |
| `EntityAccess` | [entity-anatomy](../systems/entities/entity-anatomy.md), [chunk-storage](../systems/world/chunk-storage.md) |
| `EntityAnchorArgument` | [brigadier-and-commands](../systems/commands/brigadier-and-commands.md) |
| `EntityArgument` | [brigadier-and-commands](../systems/commands/brigadier-and-commands.md), [entity-selectors](../systems/commands/entity-selectors.md) |
| `EntityAttachment` | [entity-anatomy](../systems/entities/entity-anatomy.md) |
| `EntityAttachments` | [entity-anatomy](../systems/entities/entity-anatomy.md) |
| `EntityBlock` | [block-entities](../systems/blocks/block-entities.md), [game-events-and-vibrations](../systems/world/game-events-and-vibrations.md) |
| `EntityBoundSoundInstance` | [sound-engine](../systems/client/sound-engine.md) |
| `EntityCollisionContext` | [math-and-primitives](../reference/math-and-primitives.md) |
| `EntityDataAccessor` | [scoreboard-and-data](../systems/commands/scoreboard-and-data.md), [synched-entity-data](../systems/entities/synched-entity-data.md) |
| `EntityDataSerializer` | [synched-entity-data](../systems/entities/synched-entity-data.md) |
| `EntityDataSerializers` | [blocks-and-states](../systems/blocks/blocks-and-states.md), [synched-entity-data](../systems/entities/synched-entity-data.md), [text-components](../systems/foundations/text-components.md) |
| `EntityDataSource` | [text-components](../systems/foundations/text-components.md) |
| `EntityDimensions` | [entity-anatomy](../systems/entities/entity-anatomy.md) |
| `EntityEquipment` | [items-and-stacks](../systems/items/items-and-stacks.md), [player-anatomy](../systems/player/player-anatomy.md), [the-two-phase-tick](../systems/player/the-two-phase-tick.md) |
| `EntityEvent` | [synched-entity-data](../systems/entities/synched-entity-data.md), [using-an-item](../systems/items/using-an-item.md) |
| `EntityFluidInteraction` | [naming-drift](../reference/naming-drift.md), [movement-and-collision](../systems/entities/movement-and-collision.md) |
| `EntityGetter` | [movement-and-collision](../systems/entities/movement-and-collision.md) |
| `EntityHitResult` | [math-and-primitives](../reference/math-and-primitives.md) |
| `EntityInLevelCallback` | [entity-anatomy](../systems/entities/entity-anatomy.md), [entity-lifecycle](../systems/entities/entity-lifecycle.md) |
| `EntityLookup` | [entity-selectors](../systems/commands/entity-selectors.md), [entity-lifecycle](../systems/entities/entity-lifecycle.md) |
| `EntityModel` | [entity-rendering](../systems/rendering/entity-rendering.md) |
| `EntityModelSet` | [entity-rendering](../systems/rendering/entity-rendering.md) |
| `EntityPositionSource` | [game-events-and-vibrations](../systems/world/game-events-and-vibrations.md) |
| `EntityPredicate` | [advancements](../systems/commands/advancements.md), [data-driven-types](../systems/foundations/data-driven-types.md), [contexts-and-predicates](../systems/items/contexts-and-predicates.md) |
| `EntityReference` | [entity-anatomy](../systems/entities/entity-anatomy.md), [entity-lifecycle](../systems/entities/entity-lifecycle.md) |
| `EntityRenderDispatcher` | [synched-entity-data](../systems/entities/synched-entity-data.md), [resource-system](../systems/foundations/resource-system.md), [block-entity-rendering](../systems/rendering/block-entity-rendering.md), [entity-rendering](../systems/rendering/entity-rendering.md), [particles](../systems/rendering/particles.md) |
| `EntityRenderer` | [naming-drift](../reference/naming-drift.md), [block-entity-rendering](../systems/rendering/block-entity-rendering.md), [entity-rendering](../systems/rendering/entity-rendering.md), [lightmap-fog-and-sky](../systems/rendering/lightmap-fog-and-sky.md) |
| `EntityRenderState` | [submit-phases](../reference/submit-phases.md), [block-entity-rendering](../systems/rendering/block-entity-rendering.md), [entity-rendering](../systems/rendering/entity-rendering.md), [particles](../systems/rendering/particles.md) |
| `EntitySection` | [entity-lifecycle](../systems/entities/entity-lifecycle.md) |
| `EntitySectionStorage` | [entity-selectors](../systems/commands/entity-selectors.md), [entity-lifecycle](../systems/entities/entity-lifecycle.md) |
| `EntitySelector` | [glossary](../reference/glossary.md), [brigadier-and-commands](../systems/commands/brigadier-and-commands.md), [entity-selectors](../systems/commands/entity-selectors.md), [permissions](../systems/commands/permissions.md), [scoreboard-and-data](../systems/commands/scoreboard-and-data.md), [text-components](../systems/foundations/text-components.md), [chat-and-signing](../systems/networking/chat-and-signing.md) |
| `EntitySelectorOptions` | [brigadier-and-commands](../systems/commands/brigadier-and-commands.md), [entity-selectors](../systems/commands/entity-selectors.md), [contexts-and-predicates](../systems/items/contexts-and-predicates.md) |
| `EntitySelectorParser` | [brigadier-and-commands](../systems/commands/brigadier-and-commands.md), [entity-selectors](../systems/commands/entity-selectors.md), [permissions](../systems/commands/permissions.md), [text-components](../systems/foundations/text-components.md) |
| `EntitySpawnReason` | [naming-drift](../reference/naming-drift.md), [entity-lifecycle](../systems/entities/entity-lifecycle.md) |
| `EntitySpawnRequest` | [naming-drift](../reference/naming-drift.md), [entity-anatomy](../systems/entities/entity-anatomy.md) |
| `EntityStorage` | [entity-lifecycle](../systems/entities/entity-lifecycle.md), [chunk-storage](../systems/world/chunk-storage.md) |
| `EntitySubPredicate` | [advancements](../systems/commands/advancements.md) |
| `EntityTickList` | [the-client-level](../systems/client/the-client-level.md), [entity-lifecycle](../systems/entities/entity-lifecycle.md), [server-level-tick](../systems/server/server-level-tick.md), [tickets-and-loading](../systems/world/tickets-and-loading.md) |
| `EntityType` | [fanin](../maps/fanin.md), [hierarchy](../maps/hierarchy.md), [VI · Entities](../systems/entities/README.md), [attributes](../systems/entities/attributes.md), [entity-anatomy](../systems/entities/entity-anatomy.md), [entity-lifecycle](../systems/entities/entity-lifecycle.md), [synched-entity-data](../systems/entities/synched-entity-data.md), [data-components](../systems/foundations/data-components.md), [identifiers-and-registries](../systems/foundations/identifiers-and-registries.md), [text-components](../systems/foundations/text-components.md), [what-the-client-is-told](../systems/networking/what-the-client-is-told.md), [entity-rendering](../systems/rendering/entity-rendering.md), [chunk-storage](../systems/world/chunk-storage.md) |
| `EntityTypeIds` | [naming-drift](../reference/naming-drift.md), [entity-anatomy](../systems/entities/entity-anatomy.md) |
| `EntityTypes` | [naming-drift](../reference/naming-drift.md), [entity-anatomy](../systems/entities/entity-anatomy.md), [synched-entity-data](../systems/entities/synched-entity-data.md), [identifiers-and-registries](../systems/foundations/identifiers-and-registries.md) |
| `EntityTypeTags` | [non-living-damage](../reference/non-living-damage.md), [damage-and-death](../systems/entities/damage-and-death.md), [entity-anatomy](../systems/entities/entity-anatomy.md), [movement-and-collision](../systems/entities/movement-and-collision.md), [tags](../systems/foundations/tags.md) |
| `EntityTypeTest` | [entity-selectors](../systems/commands/entity-selectors.md) |
| `EntryAction` | [the-execution-engine](../systems/commands/the-execution-engine.md) |
| `EntryGroup` | [loot-tables](../systems/items/loot-tables.md) |
| `EnumProperty` | [blocks-and-states](../systems/blocks/blocks-and-states.md) |
| `EnvironmentAttribute` | [naming-drift](../reference/naming-drift.md), [the-client-level](../systems/client/the-client-level.md), [what-makes-a-sound](../systems/client/what-makes-a-sound.md), [ai-goals-and-brains](../systems/entities/ai-goals-and-brains.md), [environment-attributes-and-timelines](../systems/world/environment-attributes-and-timelines.md), [fluids](../systems/world/fluids.md) |
| `EnvironmentAttributeCheck` | [data-driven-types](../systems/foundations/data-driven-types.md), [environment-attributes-and-timelines](../systems/world/environment-attributes-and-timelines.md) |
| `EnvironmentAttributeLayer` | [environment-attributes-and-timelines](../systems/world/environment-attributes-and-timelines.md) |
| `EnvironmentAttributeMap` | [level-data-and-rules](../reference/level-data-and-rules.md), [naming-drift](../reference/naming-drift.md), [lightmap-fog-and-sky](../systems/rendering/lightmap-fog-and-sky.md), [environment-attributes-and-timelines](../systems/world/environment-attributes-and-timelines.md), [biomes](../systems/worldgen/biomes.md) |
| `EnvironmentAttributeProbe` | [glossary](../reference/glossary.md), [naming-drift](../reference/naming-drift.md), [lightmap-fog-and-sky](../systems/rendering/lightmap-fog-and-sky.md), [the-frame](../systems/rendering/the-frame.md), [environment-attributes-and-timelines](../systems/world/environment-attributes-and-timelines.md), [biomes](../systems/worldgen/biomes.md) |
| `EnvironmentAttributeReader` | [environment-attributes-and-timelines](../systems/world/environment-attributes-and-timelines.md), [fluids](../systems/world/fluids.md) |
| `EnvironmentAttributes` | [level-data-and-rules](../reference/level-data-and-rules.md), [naming-drift](../reference/naming-drift.md), [what-makes-a-sound](../systems/client/what-makes-a-sound.md), [ai-goals-and-brains](../systems/entities/ai-goals-and-brains.md), [lightmap-fog-and-sky](../systems/rendering/lightmap-fog-and-sky.md), [particles](../systems/rendering/particles.md), [server-level-tick](../systems/server/server-level-tick.md), [environment-attributes-and-timelines](../systems/world/environment-attributes-and-timelines.md), [fluids](../systems/world/fluids.md), [points-of-interest](../systems/world/points-of-interest.md) |
| `EnvironmentAttributeSystem` | [naming-drift](../reference/naming-drift.md), [ai-goals-and-brains](../systems/entities/ai-goals-and-brains.md), [lightmap-fog-and-sky](../systems/rendering/lightmap-fog-and-sky.md), [server-level-tick](../systems/server/server-level-tick.md), [environment-attributes-and-timelines](../systems/world/environment-attributes-and-timelines.md), [biomes](../systems/worldgen/biomes.md) |
| `EnvironmentAttributeValue` | [environment-attributes-and-timelines](../systems/world/environment-attributes-and-timelines.md) |
| `EnvironmentScanPlacement` | [features-and-placement](../systems/worldgen/features-and-placement.md) |
| `EqualSpacingLayout` | [gui-and-screens](../systems/client/gui-and-screens.md) |
| `EquipmentAssetManager` | [resource-system](../systems/foundations/resource-system.md), [entity-rendering](../systems/rendering/entity-rendering.md) |
| `EquipmentClientInfo` | [entity-rendering](../systems/rendering/entity-rendering.md) |
| `EquipmentLayerRenderer` | [entity-rendering](../systems/rendering/entity-rendering.md) |
| `EquipmentSlot` | [enchantments](../systems/items/enchantments.md), [player-anatomy](../systems/player/player-anatomy.md) |
| `EquipmentSlotGroup` | [enchantments](../systems/items/enchantments.md) |
| `EquipmentUser` | [entity-anatomy](../systems/entities/entity-anatomy.md), [contexts-and-predicates](../systems/items/contexts-and-predicates.md), [loot-tables](../systems/items/loot-tables.md) |
| `Equippable` | [hud-elements](../reference/hud-elements.md), [damage-and-death](../systems/entities/damage-and-death.md), [data-components](../systems/foundations/data-components.md) |
| `ErrorCollector` | [brigadier-and-commands](../systems/commands/brigadier-and-commands.md) |
| `EuclideanGameEventListenerRegistry` | [chunk-anatomy](../systems/world/chunk-anatomy.md), [game-events-and-vibrations](../systems/world/game-events-and-vibrations.md) |
| `Eula` | [starting-a-server](../systems/server/starting-a-server.md) |
| `EventLoopGroupHolder` | [naming-drift](../reference/naming-drift.md), [threads](../reference/threads.md), [anatomy](../systems/anatomy/anatomy.md), [the-connection](../systems/networking/the-connection.md) |
| `EvokerFangs` | [non-living-damage](../reference/non-living-damage.md), [damage-and-death](../systems/entities/damage-and-death.md) |
| `ExecuteCommand` | [brigadier-and-commands](../systems/commands/brigadier-and-commands.md), [scoreboard-and-data](../systems/commands/scoreboard-and-data.md), [the-execution-engine](../systems/commands/the-execution-engine.md), [contexts-and-predicates](../systems/items/contexts-and-predicates.md) |
| `ExecutionCommandSource` | [the-execution-engine](../systems/commands/the-execution-engine.md) |
| `ExecutionContext` | [naming-drift](../reference/naming-drift.md), [functions-and-macros](../systems/commands/functions-and-macros.md), [the-execution-engine](../systems/commands/the-execution-engine.md) |
| `ExperienceBar` | [hud-elements](../reference/hud-elements.md), [hud](../systems/client/hud.md) |
| `ExperienceCommand` | [brigadier-and-commands](../systems/commands/brigadier-and-commands.md) |
| `ExperienceOrb` | [non-living-damage](../reference/non-living-damage.md), [the-client-level](../systems/client/the-client-level.md), [damage-and-death](../systems/entities/damage-and-death.md), [enchanting](../systems/items/enchanting.md), [enchantments](../systems/items/enchantments.md), [hunger-and-experience](../systems/player/hunger-and-experience.md), [the-sword-swing](../systems/player/the-sword-swing.md) |
| `ExperimentalRedstoneWireEvaluator` | [signal-and-dust](../systems/blocks/signal-and-dust.md) |
| `ExperimentsScreen` | [creating-a-world](../systems/worldgen/creating-a-world.md) |
| `ExplodeEffect` | [damage-and-death](../systems/entities/damage-and-death.md) |
| `ExplorationMapFunction` | [structure-placement](../systems/worldgen/structure-placement.md) |
| `ExplosionParticleInfo` | [particles](../systems/rendering/particles.md) |
| `ExtraCodecs` | [codecs-nbt-json](../systems/foundations/codecs-nbt-json.md), [data-driven-types](../systems/foundations/data-driven-types.md), [text-components](../systems/foundations/text-components.md) |
| `EyeOfEnder` | [non-living-damage](../reference/non-living-damage.md), [damage-and-death](../systems/entities/damage-and-death.md) |
| `EyesLayer` | [entity-rendering](../systems/rendering/entity-rendering.md) |
| `FaceBakery` | [models-and-atlases](../systems/rendering/models-and-atlases.md) |
| `FallbackResourceManager` | [resource-system](../systems/foundations/resource-system.md) |
| `FallenTreeFeature` | [trees](../systems/worldgen/trees.md) |
| `FallingBlockEntity` | [non-living-damage](../reference/non-living-damage.md), [the-client-level](../systems/client/the-client-level.md), [damage-and-death](../systems/entities/damage-and-death.md), [server-level-tick](../systems/server/server-level-tick.md) |
| `FallingBlockRenderState` | [block-entity-rendering](../systems/rendering/block-entity-rendering.md) |
| `FallLocation` | [damage-and-death](../systems/entities/damage-and-death.md) |
| `FallthroughTask` | [the-execution-engine](../systems/commands/the-execution-engine.md) |
| `FancyFoliagePlacer` | [trees](../systems/worldgen/trees.md) |
| `FancyTrunkPlacer` | [trees](../systems/worldgen/trees.md) |
| `Feature` | [naming-drift](../reference/naming-drift.md), [data-driven-types](../systems/foundations/data-driven-types.md), [creating-a-world](../systems/worldgen/creating-a-world.md), [features-and-placement](../systems/worldgen/features-and-placement.md), [trees](../systems/worldgen/trees.md) |
| `FeatureConfiguration` | [features-and-placement](../systems/worldgen/features-and-placement.md) |
| `FeatureElement` | [hierarchy](../maps/hierarchy.md), [block-interaction](../systems/blocks/block-interaction.md), [identifiers-and-registries](../systems/foundations/identifiers-and-registries.md) |
| `FeatureFlag` | [glossary](../reference/glossary.md) |
| `FeatureFlags` | [signal-and-dust](../systems/blocks/signal-and-dust.md), [identifiers-and-registries](../systems/foundations/identifiers-and-registries.md), [creating-a-world](../systems/worldgen/creating-a-world.md) |
| `FeatureFlagSet` | [identifiers-and-registries](../systems/foundations/identifiers-and-registries.md), [resource-system](../systems/foundations/resource-system.md) |
| `FeatureFlagsMetadataSection` | [identifiers-and-registries](../systems/foundations/identifiers-and-registries.md), [resource-system](../systems/foundations/resource-system.md) |
| `FeatureFlagUniverse` | [identifiers-and-registries](../systems/foundations/identifiers-and-registries.md) |
| `FeaturePoolElement` | [jigsaw-and-templates](../systems/worldgen/jigsaw-and-templates.md) |
| `FeatureRenderDispatcher` | [naming-drift](../reference/naming-drift.md), [submit-phases](../reference/submit-phases.md), [blaze3d](../systems/rendering/blaze3d.md), [block-entity-rendering](../systems/rendering/block-entity-rendering.md), [entity-rendering](../systems/rendering/entity-rendering.md), [models-and-atlases](../systems/rendering/models-and-atlases.md), [post-processing](../systems/rendering/post-processing.md), [the-frame](../systems/rendering/the-frame.md), [visibility-and-the-frame-graph](../systems/rendering/visibility-and-the-frame-graph.md) |
| `FeatureSize` | [data-driven-types](../systems/foundations/data-driven-types.md), [trees](../systems/worldgen/trees.md) |
| `FeatureSizeType` | [data-driven-types](../systems/foundations/data-driven-types.md) |
| `FeatureSorter` | [features-and-placement](../systems/worldgen/features-and-placement.md) |
| `FeatureTags` | [tags](../systems/foundations/tags.md) |
| `FieldSelector` | [codecs-nbt-json](../systems/foundations/codecs-nbt-json.md) |
| `FileFixerProgressScreen` | [creating-a-world](../systems/worldgen/creating-a-world.md) |
| `FileFixerUpper` | [what-this-book-skips](../systems/anatomy/what-this-book-skips.md) |
| `FilePackResources` | [resource-system](../systems/foundations/resource-system.md) |
| `FileToIdConverter` | [data-driven-types](../systems/foundations/data-driven-types.md), [identifiers-and-registries](../systems/foundations/identifiers-and-registries.md), [resource-system](../systems/foundations/resource-system.md), [tags](../systems/foundations/tags.md) |
| `FillBiomeCommand` | [brigadier-and-commands](../systems/commands/brigadier-and-commands.md) |
| `FilterMask` | [chat-and-signing](../systems/networking/chat-and-signing.md) |
| `FiniteAudioStream` | [sound-engine](../systems/client/sound-engine.md) |
| `FireBlock` | [identifiers-and-registries](../systems/foundations/identifiers-and-registries.md) |
| `FishingHook` | [the-client-level](../systems/client/the-client-level.md), [contexts-and-predicates](../systems/items/contexts-and-predicates.md) |
| `FixedFormat` | [scoreboard-and-data](../systems/commands/scoreboard-and-data.md) |
| `FixedPlacement` | [features-and-placement](../systems/worldgen/features-and-placement.md) |
| `FlameFeatureRenderer` | [submit-phases](../reference/submit-phases.md) |
| `FlatLevelGeneratorPreset` | [glossary](../reference/glossary.md) |
| `FlatLevelGeneratorPresets` | [creating-a-world](../systems/worldgen/creating-a-world.md) |
| `FlatLevelGeneratorSettings` | [glossary](../reference/glossary.md), [creating-a-world](../systems/worldgen/creating-a-world.md) |
| `FlatLevelSource` | [chunk-generation-pipeline](../systems/world/chunk-generation-pipeline.md), [biomes](../systems/worldgen/biomes.md), [creating-a-world](../systems/worldgen/creating-a-world.md), [terrain](../systems/worldgen/terrain.md) |
| `FloatModifier` | [environment-attributes-and-timelines](../systems/world/environment-attributes-and-timelines.md) |
| `FloatProvider` | [data-driven-types](../systems/foundations/data-driven-types.md) |
| `FloatProviders` | [data-driven-types](../systems/foundations/data-driven-types.md) |
| `FloatSampleSource` | [sound-engine](../systems/client/sound-engine.md) |
| `FloatWithAlpha` | [environment-attributes-and-timelines](../systems/world/environment-attributes-and-timelines.md) |
| `FlowingFluid` | [glossary](../reference/glossary.md), [fluids](../systems/world/fluids.md) |
| `Fluid` | [server-level-tick](../systems/server/server-level-tick.md), [fluids](../systems/world/fluids.md), [scheduled-ticks](../systems/world/scheduled-ticks.md) |
| `FluidModel` | [naming-drift](../reference/naming-drift.md), [section-meshing](../systems/rendering/section-meshing.md) |
| `FluidRenderer` | [naming-drift](../reference/naming-drift.md), [section-meshing](../systems/rendering/section-meshing.md) |
| `Fluids` | [blocks-and-states](../systems/blocks/blocks-and-states.md), [fluids](../systems/world/fluids.md), [scheduled-ticks](../systems/world/scheduled-ticks.md) |
| `FluidState` | [glossary](../reference/glossary.md), [V · Blocks](../systems/blocks/README.md), [blocks-and-states](../systems/blocks/blocks-and-states.md), [movement-and-collision](../systems/entities/movement-and-collision.md), [tags](../systems/foundations/tags.md), [server-level-tick](../systems/server/server-level-tick.md), [fluids](../systems/world/fluids.md), [scheduled-ticks](../systems/world/scheduled-ticks.md) |
| `FluidStateModelSet` | [models-and-atlases](../systems/rendering/models-and-atlases.md) |
| `FluidTags` | [block-breaking](../systems/blocks/block-breaking.md), [tags](../systems/foundations/tags.md), [fluids](../systems/world/fluids.md) |
| `FlyingPathNavigation` | [pathfinding](../systems/entities/pathfinding.md) |
| `FlyNodeEvaluator` | [pathfinding](../systems/entities/pathfinding.md) |
| `FocusNavigationEvent` | [gui-and-screens](../systems/client/gui-and-screens.md) |
| `FogData` | [naming-drift](../reference/naming-drift.md), [lightmap-fog-and-sky](../systems/rendering/lightmap-fog-and-sky.md) |
| `FogEnvironment` | [lightmap-fog-and-sky](../systems/rendering/lightmap-fog-and-sky.md) |
| `FogRenderer` | [lightmap-fog-and-sky](../systems/rendering/lightmap-fog-and-sky.md) |
| `FogType` | [lightmap-fog-and-sky](../systems/rendering/lightmap-fog-and-sky.md) |
| `FolderRepositorySource` | [resource-system](../systems/foundations/resource-system.md) |
| `FoliageColor` | [biomes](../systems/worldgen/biomes.md) |
| `FoliageColorReloadListener` | [resource-system](../systems/foundations/resource-system.md) |
| `FoliagePlacer` | [data-driven-types](../systems/foundations/data-driven-types.md), [trees](../systems/worldgen/trees.md) |
| `FoliagePlacerType` | [data-driven-types](../systems/foundations/data-driven-types.md) |
| `Font` | [naming-drift](../reference/naming-drift.md), [submit-phases](../reference/submit-phases.md), [text-and-fonts](../systems/client/text-and-fonts.md), [block-entity-rendering](../systems/rendering/block-entity-rendering.md) |
| `FontDescription` | [naming-drift](../reference/naming-drift.md), [text-and-fonts](../systems/client/text-and-fonts.md), [text-components](../systems/foundations/text-components.md) |
| `FontManager` | [text-and-fonts](../systems/client/text-and-fonts.md), [resource-system](../systems/foundations/resource-system.md) |
| `FontSet` | [naming-drift](../reference/naming-drift.md), [text-and-fonts](../systems/client/text-and-fonts.md) |
| `FontTexture` | [text-and-fonts](../systems/client/text-and-fonts.md) |
| `FoodConstants` | [hunger-and-experience](../systems/player/hunger-and-experience.md) |
| `FoodData` | [naming-drift](../reference/naming-drift.md), [VIII · The player](../systems/player/README.md), [hunger-and-experience](../systems/player/hunger-and-experience.md), [the-two-phase-tick](../systems/player/the-two-phase-tick.md) |
| `FoodProperties` | [naming-drift](../reference/naming-drift.md), [using-an-item](../systems/items/using-an-item.md), [hunger-and-experience](../systems/player/hunger-and-experience.md) |
| `Foods` | [hunger-and-experience](../systems/player/hunger-and-experience.md) |
| `ForbiddenSymlinkInfo` | [resource-system](../systems/foundations/resource-system.md) |
| `ForkingTrunkPlacer` | [trees](../systems/worldgen/trees.md) |
| `FormattedBidiReorder` | [text-and-fonts](../systems/client/text-and-fonts.md) |
| `FormattedCharSequence` | [text-and-fonts](../systems/client/text-and-fonts.md), [text-components](../systems/foundations/text-components.md) |
| `FormattedCharSink` | [text-and-fonts](../systems/client/text-and-fonts.md) |
| `FormattedText` | [text-and-fonts](../systems/client/text-and-fonts.md) |
| `Fox` | [biggest](../maps/biggest.md), [hierarchy](../maps/hierarchy.md), [pathfinding](../systems/entities/pathfinding.md) |
| `FpsDebugChart` | [hud](../systems/client/hud.md) |
| `Frame` | [the-execution-engine](../systems/commands/the-execution-engine.md) |
| `FrameGraphBuilder` | [blaze3d](../systems/rendering/blaze3d.md), [post-processing](../systems/rendering/post-processing.md), [visibility-and-the-frame-graph](../systems/rendering/visibility-and-the-frame-graph.md) |
| `FrameLayout` | [gui-and-screens](../systems/client/gui-and-screens.md) |
| `FramePass` | [post-processing](../systems/rendering/post-processing.md), [visibility-and-the-frame-graph](../systems/rendering/visibility-and-the-frame-graph.md) |
| `FramerateLimiter` | [the-client-loop](../systems/client/the-client-loop.md) |
| `FramerateLimitTracker` | [the-client-loop](../systems/client/the-client-loop.md), [the-frame](../systems/rendering/the-frame.md), [the-window](../systems/rendering/the-window.md) |
| `FriendlyByteBuf` | [biggest](../maps/biggest.md), [codecs-nbt-json](../systems/foundations/codecs-nbt-json.md), [packets-and-stream-codecs](../systems/networking/packets-and-stream-codecs.md), [protocol-phases](../systems/networking/protocol-phases.md) |
| `Frog` | [ai-goals-and-brains](../systems/entities/ai-goals-and-brains.md), [pathfinding](../systems/entities/pathfinding.md) |
| `FrogAi` | [ai-goals-and-brains](../systems/entities/ai-goals-and-brains.md) |
| `FrontAndTop` | [math-and-primitives](../reference/math-and-primitives.md) |
| `Frustum` | [particles](../systems/rendering/particles.md), [visibility-and-the-frame-graph](../systems/rendering/visibility-and-the-frame-graph.md) |
| `FuelValues` | [block-entities](../systems/blocks/block-entities.md) |
| `FullChunkStatus` | [block-entities](../systems/blocks/block-entities.md), [blocks-and-states](../systems/blocks/blocks-and-states.md), [entity-lifecycle](../systems/entities/entity-lifecycle.md), [what-the-client-is-told](../systems/networking/what-the-client-is-told.md), [server-level-tick](../systems/server/server-level-tick.md), [chunk-anatomy](../systems/world/chunk-anatomy.md), [chunk-generation-pipeline](../systems/world/chunk-generation-pipeline.md), [tickets-and-loading](../systems/world/tickets-and-loading.md) |
| `FunctionArgument` | [brigadier-and-commands](../systems/commands/brigadier-and-commands.md), [functions-and-macros](../systems/commands/functions-and-macros.md) |
| `FunctionCommand` | [brigadier-and-commands](../systems/commands/brigadier-and-commands.md), [functions-and-macros](../systems/commands/functions-and-macros.md), [the-execution-engine](../systems/commands/the-execution-engine.md) |
| `FunctionGameTestInstance` | [game-tests](../systems/commands/game-tests.md) |
| `FunctionInstantiationException` | [functions-and-macros](../systems/commands/functions-and-macros.md) |
| `FurnaceMenu` | [block-entities](../systems/blocks/block-entities.md) |
| `FurnaceResultSlot` | [block-entities](../systems/blocks/block-entities.md) |
| `FutureChain` | [chat-and-signing](../systems/networking/chat-and-signing.md) |
| `GameConfig` | [anatomy](../systems/anatomy/anatomy.md), [the-client-loop](../systems/client/the-client-loop.md) |
| `GameEvent` | [non-living-damage](../reference/non-living-damage.md), [block-breaking](../systems/blocks/block-breaking.md), [block-interaction](../systems/blocks/block-interaction.md), [authority](../systems/entities/authority.md), [movement-and-collision](../systems/entities/movement-and-collision.md), [synched-entity-data](../systems/entities/synched-entity-data.md), [using-an-item](../systems/items/using-an-item.md), [game-events-and-vibrations](../systems/world/game-events-and-vibrations.md) |
| `GameEventDispatcher` | [game-events-and-vibrations](../systems/world/game-events-and-vibrations.md) |
| `GameEventListener` | [game-events-and-vibrations](../systems/world/game-events-and-vibrations.md) |
| `GameEventListenerRegistry` | [game-events-and-vibrations](../systems/world/game-events-and-vibrations.md) |
| `GameEventTags` | [game-events-and-vibrations](../systems/world/game-events-and-vibrations.md) |
| `GameMasterBlock` | [block-breaking](../systems/blocks/block-breaking.md) |
| `GameModeCommand` | [permissions](../systems/commands/permissions.md) |
| `GameModeSwitcherScreen` | [permissions](../systems/commands/permissions.md) |
| `GamePacketTypes` | [packets-and-stream-codecs](../systems/networking/packets-and-stream-codecs.md) |
| `GameProfile` | [players-and-sessions](../systems/server/players-and-sessions.md) |
| `GameProfileArgument` | [brigadier-and-commands](../systems/commands/brigadier-and-commands.md) |
| `GameProtocols` | [packets-and-stream-codecs](../systems/networking/packets-and-stream-codecs.md), [protocol-phases](../systems/networking/protocol-phases.md) |
| `GameRenderer` | [hud-elements](../reference/hud-elements.md), [math-and-primitives](../reference/math-and-primitives.md), [hud](../systems/client/hud.md), [sound-engine](../systems/client/sound-engine.md), [the-client-loop](../systems/client/the-client-loop.md), [the-gui-render-tree](../systems/client/the-gui-render-tree.md), [block-entity-rendering](../systems/rendering/block-entity-rendering.md), [lightmap-fog-and-sky](../systems/rendering/lightmap-fog-and-sky.md), [post-processing](../systems/rendering/post-processing.md), [the-frame](../systems/rendering/the-frame.md), [visibility-and-the-frame-graph](../systems/rendering/visibility-and-the-frame-graph.md) |
| `GameRenderState` | [the-gui-render-tree](../systems/client/the-gui-render-tree.md), [post-processing](../systems/rendering/post-processing.md), [the-frame](../systems/rendering/the-frame.md) |
| `GameRule` | [hierarchy](../maps/hierarchy.md), [level-data-and-rules](../reference/level-data-and-rules.md), [naming-drift](../reference/naming-drift.md), [identifiers-and-registries](../systems/foundations/identifiers-and-registries.md), [hunger-and-experience](../systems/player/hunger-and-experience.md) |
| `GameRuleCategory` | [level-data-and-rules](../reference/level-data-and-rules.md), [naming-drift](../reference/naming-drift.md) |
| `GameRuleCommand` | [level-data-and-rules](../reference/level-data-and-rules.md) |
| `GameRuleMap` | [level-data-and-rules](../reference/level-data-and-rules.md), [naming-drift](../reference/naming-drift.md), [data-driven-types](../systems/foundations/data-driven-types.md) |
| `GameRuleRegistryFix` | [naming-drift](../reference/naming-drift.md), [starting-a-server](../systems/server/starting-a-server.md) |
| `GameRules` | [glossary](../reference/glossary.md), [level-data-and-rules](../reference/level-data-and-rules.md), [naming-drift](../reference/naming-drift.md), [non-living-damage](../reference/non-living-damage.md), [block-breaking](../systems/blocks/block-breaking.md), [advancements](../systems/commands/advancements.md), [the-execution-engine](../systems/commands/the-execution-engine.md), [damage-and-death](../systems/entities/damage-and-death.md), [movement-and-collision](../systems/entities/movement-and-collision.md), [identifiers-and-registries](../systems/foundations/identifiers-and-registries.md), [text-components](../systems/foundations/text-components.md), [recipes](../systems/items/recipes.md), [what-the-client-is-told](../systems/networking/what-the-client-is-told.md), [hunger-and-experience](../systems/player/hunger-and-experience.md), [input-to-movement](../systems/player/input-to-movement.md), [players-and-sessions](../systems/server/players-and-sessions.md), [server-level-tick](../systems/server/server-level-tick.md), [server-tick](../systems/server/server-tick.md), [starting-a-server](../systems/server/starting-a-server.md), [environment-attributes-and-timelines](../systems/world/environment-attributes-and-timelines.md), [fluids](../systems/world/fluids.md), [scheduled-ticks](../systems/world/scheduled-ticks.md), [tickets-and-loading](../systems/world/tickets-and-loading.md), [creating-a-world](../systems/worldgen/creating-a-world.md) |
| `GameRuleType` | [level-data-and-rules](../reference/level-data-and-rules.md), [naming-drift](../reference/naming-drift.md) |
| `GameRuleTypeVisitor` | [naming-drift](../reference/naming-drift.md) |
| `GameTestBatch` | [game-tests](../systems/commands/game-tests.md) |
| `GameTestBatchFactory` | [game-tests](../systems/commands/game-tests.md) |
| `GameTestBlockHighlightRenderer` | [game-tests](../systems/commands/game-tests.md) |
| `GameTestEnvironments` | [game-tests](../systems/commands/game-tests.md) |
| `GameTestHelper` | [game-tests](../systems/commands/game-tests.md) |
| `GameTestInfo` | [game-tests](../systems/commands/game-tests.md), [scheduled-ticks](../systems/world/scheduled-ticks.md) |
| `GameTestInstance` | [naming-drift](../reference/naming-drift.md), [game-tests](../systems/commands/game-tests.md), [data-driven-types](../systems/foundations/data-driven-types.md) |
| `GameTestInstances` | [game-tests](../systems/commands/game-tests.md) |
| `GameTestMainUtil` | [game-tests](../systems/commands/game-tests.md) |
| `GameTestRunner` | [game-tests](../systems/commands/game-tests.md) |
| `GameTestSequence` | [game-tests](../systems/commands/game-tests.md) |
| `GameTestServer` | [anatomy](../systems/anatomy/anatomy.md), [game-tests](../systems/commands/game-tests.md), [how-a-server-dies](../systems/server/how-a-server-dies.md), [chunk-storage](../systems/world/chunk-storage.md) |
| `GameTestTicker` | [server-tick](../systems/server/server-tick.md) |
| `GameType` | [level-data-and-rules](../reference/level-data-and-rules.md), [player-anatomy](../systems/player/player-anatomy.md) |
| `GateBehavior` | [ai-goals-and-brains](../systems/entities/ai-goals-and-brains.md) |
| `GaussianSampler` | [environment-attributes-and-timelines](../systems/world/environment-attributes-and-timelines.md), [biomes](../systems/worldgen/biomes.md) |
| `GeneratingChunkMap` | [chunk-generation-pipeline](../systems/world/chunk-generation-pipeline.md) |
| `GenerationChunkHolder` | [how-a-server-dies](../systems/server/how-a-server-dies.md), [server-tick](../systems/server/server-tick.md), [chunk-generation-pipeline](../systems/world/chunk-generation-pipeline.md), [chunk-storage](../systems/world/chunk-storage.md), [tickets-and-loading](../systems/world/tickets-and-loading.md) |
| `GenerationStep` | [features-and-placement](../systems/worldgen/features-and-placement.md) |
| `GenericMessageScreen` | [how-a-server-dies](../systems/server/how-a-server-dies.md) |
| `GenericThread` | [how-a-server-dies](../systems/server/how-a-server-dies.md), [starting-a-server](../systems/server/starting-a-server.md) |
| `Ghast` | [entity-anatomy](../systems/entities/entity-anatomy.md), [pathfinding](../systems/entities/pathfinding.md) |
| `GiantTrunkPlacer` | [trees](../systems/worldgen/trees.md) |
| `GiveCommand` | [naming-drift](../reference/naming-drift.md), [codecs-nbt-json](../systems/foundations/codecs-nbt-json.md) |
| `GizmoCollector` | [what-this-book-skips](../systems/anatomy/what-this-book-skips.md) |
| `GizmoFeatureRenderer` | [submit-phases](../reference/submit-phases.md) |
| `Gizmos` | [what-this-book-skips](../systems/anatomy/what-this-book-skips.md), [debugging-the-running-game](../systems/client/debugging-the-running-game.md) |
| `GlBackend` | [anatomy](../systems/anatomy/anatomy.md) |
| `GlBuffer` | [blaze3d](../systems/rendering/blaze3d.md) |
| `GlCommandEncoder` | [blaze3d](../systems/rendering/blaze3d.md) |
| `GlDevice` | [blaze3d](../systems/rendering/blaze3d.md) |
| `GlHeuristics` | [blaze3d](../systems/rendering/blaze3d.md) |
| `GlobalPalette` | [chunk-anatomy](../systems/world/chunk-anatomy.md) |
| `GlobalPos` | [level-data-and-rules](../reference/level-data-and-rules.md), [math-and-primitives](../reference/math-and-primitives.md), [points-of-interest](../systems/world/points-of-interest.md) |
| `GlobalSettingsUniform` | [blaze3d](../systems/rendering/blaze3d.md), [post-processing](../systems/rendering/post-processing.md) |
| `GlobalTestReporter` | [game-tests](../systems/commands/game-tests.md) |
| `GlRenderPass` | [blaze3d](../systems/rendering/blaze3d.md) |
| `GlslCompiler` | [what-this-book-skips](../systems/anatomy/what-this-book-skips.md), [blaze3d](../systems/rendering/blaze3d.md) |
| `GlslPreprocessor` | [blaze3d](../systems/rendering/blaze3d.md), [post-processing](../systems/rendering/post-processing.md) |
| `GlStateManager` | [blaze3d](../systems/rendering/blaze3d.md) |
| `GLX` | [the-window](../systems/rendering/the-window.md) |
| `GlyphBitmap` | [naming-drift](../reference/naming-drift.md), [text-and-fonts](../systems/client/text-and-fonts.md) |
| `GlyphProvider` | [text-and-fonts](../systems/client/text-and-fonts.md) |
| `GlyphProviderDefinition` | [naming-drift](../reference/naming-drift.md), [text-and-fonts](../systems/client/text-and-fonts.md) |
| `GlyphProviderType` | [naming-drift](../reference/naming-drift.md), [text-and-fonts](../systems/client/text-and-fonts.md) |
| `GlyphRenderState` | [text-and-fonts](../systems/client/text-and-fonts.md), [the-gui-render-tree](../systems/client/the-gui-render-tree.md) |
| `GlyphRenderTypes` | [text-and-fonts](../systems/client/text-and-fonts.md) |
| `GlyphSource` | [naming-drift](../reference/naming-drift.md), [text-and-fonts](../systems/client/text-and-fonts.md) |
| `GlyphStitcher` | [text-and-fonts](../systems/client/text-and-fonts.md) |
| `Goal` | [hierarchy](../maps/hierarchy.md), [glossary](../reference/glossary.md), [ai-goals-and-brains](../systems/entities/ai-goals-and-brains.md) |
| `GoalSelector` | [ai-goals-and-brains](../systems/entities/ai-goals-and-brains.md), [entity-anatomy](../systems/entities/entity-anatomy.md) |
| `Goat` | [ai-goals-and-brains](../systems/entities/ai-goals-and-brains.md) |
| `GolemSensor` | [ai-goals-and-brains](../systems/entities/ai-goals-and-brains.md) |
| `GoToPotentialJobSite` | [points-of-interest](../systems/world/points-of-interest.md) |
| `GpuBackend` | [anatomy](../systems/anatomy/anatomy.md), [blaze3d](../systems/rendering/blaze3d.md), [the-window](../systems/rendering/the-window.md) |
| `GpuBuffer` | [naming-drift](../reference/naming-drift.md), [blaze3d](../systems/rendering/blaze3d.md), [post-processing](../systems/rendering/post-processing.md) |
| `GpuBufferSlice` | [naming-drift](../reference/naming-drift.md), [blaze3d](../systems/rendering/blaze3d.md) |
| `GpuDevice` | [lectures](../lectures.md), [packages](../maps/packages.md), [glossary](../reference/glossary.md), [anatomy](../systems/anatomy/anatomy.md), [XI · Rendering](../systems/rendering/README.md), [blaze3d](../systems/rendering/blaze3d.md), [the-window](../systems/rendering/the-window.md) |
| `GpuDeviceBackend` | [glossary](../reference/glossary.md), [blaze3d](../systems/rendering/blaze3d.md) |
| `GpuFence` | [blaze3d](../systems/rendering/blaze3d.md) |
| `GpuFormat` | [naming-drift](../reference/naming-drift.md), [blaze3d](../systems/rendering/blaze3d.md) |
| `GpuOutOfMemoryException` | [blaze3d](../systems/rendering/blaze3d.md) |
| `GpuSampler` | [blaze3d](../systems/rendering/blaze3d.md) |
| `GpuSurface` | [naming-drift](../reference/naming-drift.md), [blaze3d](../systems/rendering/blaze3d.md), [the-frame](../systems/rendering/the-frame.md), [the-window](../systems/rendering/the-window.md) |
| `GpuSurfaceBackend` | [blaze3d](../systems/rendering/blaze3d.md) |
| `GpuTexture` | [blaze3d](../systems/rendering/blaze3d.md), [lightmap-fog-and-sky](../systems/rendering/lightmap-fog-and-sky.md) |
| `GpuTextureView` | [blaze3d](../systems/rendering/blaze3d.md) |
| `GpuWarnlistManager` | [resource-system](../systems/foundations/resource-system.md) |
| `GraphicsPreset` | [post-processing](../systems/rendering/post-processing.md) |
| `GraphicsResourceAllocator` | [blaze3d](../systems/rendering/blaze3d.md) |
| `GrassBlock` | [what-this-book-skips](../systems/anatomy/what-this-book-skips.md), [biomes](../systems/worldgen/biomes.md) |
| `GrassColor` | [biomes](../systems/worldgen/biomes.md) |
| `GrassColorReloadListener` | [resource-system](../systems/foundations/resource-system.md) |
| `GravityProcessor` | [jigsaw-and-templates](../systems/worldgen/jigsaw-and-templates.md) |
| `GridLayout` | [gui-and-screens](../systems/client/gui-and-screens.md) |
| `GrindstoneMenu` | [enchanting](../systems/items/enchanting.md) |
| `GroundPathNavigation` | [pathfinding](../systems/entities/pathfinding.md) |
| `GroupSlotSource` | [data-driven-types](../systems/foundations/data-driven-types.md) |
| `Gui` | [biggest](../maps/biggest.md), [glossary](../reference/glossary.md), [hud-elements](../reference/hud-elements.md), [naming-drift](../reference/naming-drift.md), [gui-and-screens](../systems/client/gui-and-screens.md), [hud](../systems/client/hud.md), [input-and-keybinds](../systems/client/input-and-keybinds.md), [the-client-loop](../systems/client/the-client-loop.md), [the-gui-render-tree](../systems/client/the-gui-render-tree.md), [resource-system](../systems/foundations/resource-system.md), [how-a-server-dies](../systems/server/how-a-server-dies.md) |
| `GuiBannerResultRenderState` | [the-gui-render-tree](../systems/client/the-gui-render-tree.md) |
| `GuiBookModelRenderState` | [the-gui-render-tree](../systems/client/the-gui-render-tree.md) |
| `GuiElementRenderState` | [the-gui-render-tree](../systems/client/the-gui-render-tree.md) |
| `GuiEntityRenderState` | [the-gui-render-tree](../systems/client/the-gui-render-tree.md) |
| `GuiEventListener` | [gui-and-screens](../systems/client/gui-and-screens.md) |
| `GuiGraphicsExtractor` | [hud-elements](../reference/hud-elements.md), [naming-drift](../reference/naming-drift.md), [hud](../systems/client/hud.md), [text-and-fonts](../systems/client/text-and-fonts.md), [the-gui-render-tree](../systems/client/the-gui-render-tree.md), [items-and-stacks](../systems/items/items-and-stacks.md), [post-processing](../systems/rendering/post-processing.md) |
| `GuiItemAtlas` | [the-gui-render-tree](../systems/client/the-gui-render-tree.md) |
| `GuiItemRenderState` | [the-gui-render-tree](../systems/client/the-gui-render-tree.md) |
| `GuiProfilerChartRenderState` | [the-gui-render-tree](../systems/client/the-gui-render-tree.md) |
| `GuiRenderer` | [text-and-fonts](../systems/client/text-and-fonts.md), [the-gui-render-tree](../systems/client/the-gui-render-tree.md), [blaze3d](../systems/rendering/blaze3d.md), [post-processing](../systems/rendering/post-processing.md), [the-frame](../systems/rendering/the-frame.md) |
| `GuiRenderState` | [hud-elements](../reference/hud-elements.md), [naming-drift](../reference/naming-drift.md), [hud](../systems/client/hud.md), [the-gui-render-tree](../systems/client/the-gui-render-tree.md) |
| `GuiSkinRenderState` | [the-gui-render-tree](../systems/client/the-gui-render-tree.md) |
| `GuiTextRenderState` | [text-and-fonts](../systems/client/text-and-fonts.md), [the-gui-render-tree](../systems/client/the-gui-render-tree.md) |
| `Half` | [blocks-and-states](../systems/blocks/blocks-and-states.md) |
| `HandlerNames` | [the-connection](../systems/networking/the-connection.md) |
| `HandshakeProtocols` | [packets-and-stream-codecs](../systems/networking/packets-and-stream-codecs.md), [protocol-phases](../systems/networking/protocol-phases.md), [the-connection](../systems/networking/the-connection.md) |
| `HangingSignBlockEntity` | [block-entities](../systems/blocks/block-entities.md) |
| `HappyGhast` | [pathfinding](../systems/entities/pathfinding.md) |
| `HarvestFarmland` | [ai-goals-and-brains](../systems/entities/ai-goals-and-brains.md) |
| `HasCustomInventoryScreen` | [gui-and-screens](../systems/client/gui-and-screens.md) |
| `HashedPatchMap` | [codecs-nbt-json](../systems/foundations/codecs-nbt-json.md), [containers-and-menus](../systems/items/containers-and-menus.md), [packets-and-stream-codecs](../systems/networking/packets-and-stream-codecs.md) |
| `HashedStack` | [codecs-nbt-json](../systems/foundations/codecs-nbt-json.md), [data-components](../systems/foundations/data-components.md), [containers-and-menus](../systems/items/containers-and-menus.md), [packets-and-stream-codecs](../systems/networking/packets-and-stream-codecs.md) |
| `HashMapPalette` | [chunk-anatomy](../systems/world/chunk-anatomy.md) |
| `HashOps` | [codecs-nbt-json](../systems/foundations/codecs-nbt-json.md), [containers-and-menus](../systems/items/containers-and-menus.md) |
| `HeaderAndFooterLayout` | [gui-and-screens](../systems/client/gui-and-screens.md) |
| `Heightmap` | [chunk-anatomy](../systems/world/chunk-anatomy.md), [chunk-generation-pipeline](../systems/world/chunk-generation-pipeline.md), [creating-a-world](../systems/worldgen/creating-a-world.md), [terrain](../systems/worldgen/terrain.md) |
| `HeightmapPlacement` | [features-and-placement](../systems/worldgen/features-and-placement.md) |
| `HeightProvider` | [data-driven-types](../systems/foundations/data-driven-types.md), [features-and-placement](../systems/worldgen/features-and-placement.md) |
| `HeightProviderType` | [data-driven-types](../systems/foundations/data-driven-types.md) |
| `HeightRangePlacement` | [features-and-placement](../systems/worldgen/features-and-placement.md) |
| `HiddenByteBuf` | [the-connection](../systems/networking/the-connection.md) |
| `HintsAndWorkarounds` | [blaze3d](../systems/rendering/blaze3d.md) |
| `HitResult` | [math-and-primitives](../reference/math-and-primitives.md) |
| `HoeItem` | [data-components](../systems/foundations/data-components.md) |
| `Hoglin` | [ai-goals-and-brains](../systems/entities/ai-goals-and-brains.md), [damage-and-death](../systems/entities/damage-and-death.md) |
| `Holder` | [fanin](../maps/fanin.md), [glossary](../reference/glossary.md), [naming-drift](../reference/naming-drift.md), [synched-entity-data](../systems/entities/synched-entity-data.md), [II · Foundations](../systems/foundations/README.md), [codecs-nbt-json](../systems/foundations/codecs-nbt-json.md), [data-components](../systems/foundations/data-components.md), [data-driven-types](../systems/foundations/data-driven-types.md), [identifiers-and-registries](../systems/foundations/identifiers-and-registries.md), [tags](../systems/foundations/tags.md), [text-components](../systems/foundations/text-components.md), [contexts-and-predicates](../systems/items/contexts-and-predicates.md), [items-and-stacks](../systems/items/items-and-stacks.md), [creating-a-world](../systems/worldgen/creating-a-world.md) |
| `HolderGetter` | [identifiers-and-registries](../systems/foundations/identifiers-and-registries.md), [contexts-and-predicates](../systems/items/contexts-and-predicates.md) |
| `HolderLookup` | [codecs-nbt-json](../systems/foundations/codecs-nbt-json.md), [data-driven-types](../systems/foundations/data-driven-types.md), [identifiers-and-registries](../systems/foundations/identifiers-and-registries.md), [items-and-stacks](../systems/items/items-and-stacks.md) |
| `HolderOwner` | [identifiers-and-registries](../systems/foundations/identifiers-and-registries.md) |
| `HolderSet` | [glossary](../reference/glossary.md), [naming-drift](../reference/naming-drift.md), [identifiers-and-registries](../systems/foundations/identifiers-and-registries.md), [tags](../systems/foundations/tags.md), [enchantments](../systems/items/enchantments.md), [items-and-stacks](../systems/items/items-and-stacks.md), [recipes](../systems/items/recipes.md), [terrain](../systems/worldgen/terrain.md) |
| `HolderSetCodec` | [codecs-nbt-json](../systems/foundations/codecs-nbt-json.md), [identifiers-and-registries](../systems/foundations/identifiers-and-registries.md), [tags](../systems/foundations/tags.md) |
| `HorizontalDirectionalBlock` | [diodes-and-observers](../systems/blocks/diodes-and-observers.md) |
| `Horse` | [damage-and-death](../systems/entities/damage-and-death.md) |
| `HoverEvent` | [text-components](../systems/foundations/text-components.md), [items-and-stacks](../systems/items/items-and-stacks.md) |
| `Hud` | [biggest](../maps/biggest.md), [glossary](../reference/glossary.md), [hud-elements](../reference/hud-elements.md), [naming-drift](../reference/naming-drift.md), [gui-and-screens](../systems/client/gui-and-screens.md), [hud](../systems/client/hud.md), [scoreboard-and-data](../systems/commands/scoreboard-and-data.md), [resource-system](../systems/foundations/resource-system.md), [items-and-stacks](../systems/items/items-and-stacks.md), [lightmap-fog-and-sky](../systems/rendering/lightmap-fog-and-sky.md) |
| `HumanoidArmorLayer` | [entity-rendering](../systems/rendering/entity-rendering.md) |
| `HumanoidMobRenderer` | [entity-rendering](../systems/rendering/entity-rendering.md) |
| `HumanoidModel` | [entity-rendering](../systems/rendering/entity-rendering.md) |
| `HumanoidRenderState` | [entity-rendering](../systems/rendering/entity-rendering.md) |
| `HurtByTargetGoal` | [ai-goals-and-brains](../systems/entities/ai-goals-and-brains.md) |
| `IconSet` | [the-window](../systems/rendering/the-window.md) |
| `IdDispatchCodec` | [naming-drift](../reference/naming-drift.md), [codecs-nbt-json](../systems/foundations/codecs-nbt-json.md), [packets-and-stream-codecs](../systems/networking/packets-and-stream-codecs.md) |
| `Identifier` | [introduction](../introduction.md), [fanin](../maps/fanin.md), [glossary](../reference/glossary.md), [level-data-and-rules](../reference/level-data-and-rules.md), [math-and-primitives](../reference/math-and-primitives.md), [naming-drift](../reference/naming-drift.md), [hud](../systems/client/hud.md), [sound-engine](../systems/client/sound-engine.md), [what-makes-a-sound](../systems/client/what-makes-a-sound.md), [entity-selectors](../systems/commands/entity-selectors.md), [permissions](../systems/commands/permissions.md), [attributes](../systems/entities/attributes.md), [II · Foundations](../systems/foundations/README.md), [identifiers-and-registries](../systems/foundations/identifiers-and-registries.md), [tags](../systems/foundations/tags.md), [text-components](../systems/foundations/text-components.md), [contexts-and-predicates](../systems/items/contexts-and-predicates.md), [enchantments](../systems/items/enchantments.md), [recipes](../systems/items/recipes.md), [using-an-item](../systems/items/using-an-item.md), [packets-and-stream-codecs](../systems/networking/packets-and-stream-codecs.md), [blaze3d](../systems/rendering/blaze3d.md), [models-and-atlases](../systems/rendering/models-and-atlases.md), [post-processing](../systems/rendering/post-processing.md) |
| `IdentifierArgument` | [naming-drift](../reference/naming-drift.md) |
| `IdentifierException` | [identifiers-and-registries](../systems/foundations/identifiers-and-registries.md) |
| `IdMap` | [blocks-and-states](../systems/blocks/blocks-and-states.md), [entity-anatomy](../systems/entities/entity-anatomy.md), [identifiers-and-registries](../systems/foundations/identifiers-and-registries.md), [chunk-anatomy](../systems/world/chunk-anatomy.md) |
| `IdMapper` | [blocks-and-states](../systems/blocks/blocks-and-states.md), [identifiers-and-registries](../systems/foundations/identifiers-and-registries.md) |
| `IglooPieces` | [hand-built-structures](../systems/worldgen/hand-built-structures.md) |
| `Ignite` | [enchantments](../systems/items/enchantments.md) |
| `ImbueRecipe` | [recipes](../systems/items/recipes.md) |
| `ImpossibleTrigger` | [advancements](../systems/commands/advancements.md) |
| `ImposterProtoChunk` | [chunk-anatomy](../systems/world/chunk-anatomy.md), [chunk-generation-pipeline](../systems/world/chunk-generation-pipeline.md), [chunk-storage](../systems/world/chunk-storage.md), [scheduled-ticks](../systems/world/scheduled-ticks.md) |
| `ImprovedNoise` | [density-functions](../systems/worldgen/density-functions.md) |
| `InactiveProfiler` | [the-client-loop](../systems/client/the-client-loop.md) |
| `InclusiveRange` | [math-and-primitives](../reference/math-and-primitives.md) |
| `IndexMerger` | [math-and-primitives](../reference/math-and-primitives.md) |
| `IndexType` | [naming-drift](../reference/naming-drift.md), [blaze3d](../systems/rendering/blaze3d.md) |
| `Ingredient` | [naming-drift](../reference/naming-drift.md), [recipes](../systems/items/recipes.md) |
| `Input` | [input-and-keybinds](../systems/client/input-and-keybinds.md), [dialogs](../systems/commands/dialogs.md), [input-to-movement](../systems/player/input-to-movement.md), [player-anatomy](../systems/player/player-anatomy.md) |
| `InputConstants` | [input-and-keybinds](../systems/client/input-and-keybinds.md), [the-window](../systems/rendering/the-window.md) |
| `InputControl` | [dialogs](../systems/commands/dialogs.md), [data-driven-types](../systems/foundations/data-driven-types.md) |
| `InputControlHandlers` | [dialogs](../systems/commands/dialogs.md) |
| `InputQuirks` | [input-and-keybinds](../systems/client/input-and-keybinds.md) |
| `InputType` | [input-and-keybinds](../systems/client/input-and-keybinds.md) |
| `InputWithModifiers` | [input-and-keybinds](../systems/client/input-and-keybinds.md) |
| `InsideBlockEffectApplier` | [movement-and-collision](../systems/entities/movement-and-collision.md) |
| `InsideBlockEffectType` | [movement-and-collision](../systems/entities/movement-and-collision.md) |
| `InSquarePlacement` | [naming-drift](../reference/naming-drift.md), [features-and-placement](../systems/worldgen/features-and-placement.md) |
| `InstantiatedFunction` | [functions-and-macros](../systems/commands/functions-and-macros.md) |
| `IntArrayTag` | [codecs-nbt-json](../systems/foundations/codecs-nbt-json.md) |
| `IntegerModifier` | [environment-attributes-and-timelines](../systems/world/environment-attributes-and-timelines.md) |
| `IntegerProperty` | [blocks-and-states](../systems/blocks/blocks-and-states.md) |
| `IntegratedServer` | [introduction](../introduction.md), [anatomy](../systems/anatomy/anatomy.md), [debugging-the-running-game](../systems/client/debugging-the-running-game.md), [options](../systems/client/options.md), [identifiers-and-registries](../systems/foundations/identifiers-and-registries.md), [what-the-client-is-told](../systems/networking/what-the-client-is-told.md), [how-a-server-dies](../systems/server/how-a-server-dies.md), [server-tick](../systems/server/server-tick.md), [starting-a-server](../systems/server/starting-a-server.md), [chunk-storage](../systems/world/chunk-storage.md) |
| `Interaction` | [non-living-damage](../reference/non-living-damage.md), [damage-and-death](../systems/entities/damage-and-death.md), [the-spear](../systems/player/the-spear.md) |
| `InteractionHand` | [block-interaction](../systems/blocks/block-interaction.md) |
| `InteractionResult` | [naming-drift](../reference/naming-drift.md), [block-interaction](../systems/blocks/block-interaction.md), [synched-entity-data](../systems/entities/synched-entity-data.md), [using-an-item](../systems/items/using-an-item.md) |
| `InteractWithDoor` | [block-interaction](../systems/blocks/block-interaction.md) |
| `IntermediaryShaderModule` | [blaze3d](../systems/rendering/blaze3d.md) |
| `InterpolationHandler` | [naming-drift](../reference/naming-drift.md), [the-client-level](../systems/client/the-client-level.md), [authority](../systems/entities/authority.md), [movement-and-collision](../systems/entities/movement-and-collision.md) |
| `IntProvider` | [data-driven-types](../systems/foundations/data-driven-types.md), [features-and-placement](../systems/worldgen/features-and-placement.md) |
| `IntProviders` | [data-driven-types](../systems/foundations/data-driven-types.md) |
| `Inventory` | [naming-drift](../reference/naming-drift.md), [block-breaking](../systems/blocks/block-breaking.md), [brigadier-and-commands](../systems/commands/brigadier-and-commands.md), [scoreboard-and-data](../systems/commands/scoreboard-and-data.md), [entity-anatomy](../systems/entities/entity-anatomy.md), [containers-and-menus](../systems/items/containers-and-menus.md), [items-and-stacks](../systems/items/items-and-stacks.md), [recipes](../systems/items/recipes.md), [player-anatomy](../systems/player/player-anatomy.md), [the-two-phase-tick](../systems/player/the-two-phase-tick.md) |
| `InventoryChangeTrigger` | [advancements](../systems/commands/advancements.md) |
| `InventoryMenu` | [gui-and-screens](../systems/client/gui-and-screens.md), [containers-and-menus](../systems/items/containers-and-menus.md), [recipes](../systems/items/recipes.md) |
| `InventoryScreen` | [gui-and-screens](../systems/client/gui-and-screens.md) |
| `InvertableSetOptionState` | [entity-selectors](../systems/commands/entity-selectors.md) |
| `InWorldGameRulesScreen` | [level-data-and-rules](../reference/level-data-and-rules.md) |
| `IoSupplier` | [resource-system](../systems/foundations/resource-system.md) |
| `IOWorker` | [threads](../reference/threads.md), [anatomy](../systems/anatomy/anatomy.md), [codecs-nbt-json](../systems/foundations/codecs-nbt-json.md), [how-a-server-dies](../systems/server/how-a-server-dies.md), [chunk-storage](../systems/world/chunk-storage.md), [blending](../systems/worldgen/blending.md) |
| `IsolatedCall` | [the-execution-engine](../systems/commands/the-execution-engine.md) |
| `IsXmas` | [block-entity-rendering](../systems/rendering/block-entity-rendering.md) |
| `Item` | [hierarchy](../maps/hierarchy.md), [naming-drift](../reference/naming-drift.md), [block-breaking](../systems/blocks/block-breaking.md), [attributes](../systems/entities/attributes.md), [II · Foundations](../systems/foundations/README.md), [codecs-nbt-json](../systems/foundations/codecs-nbt-json.md), [data-components](../systems/foundations/data-components.md), [identifiers-and-registries](../systems/foundations/identifiers-and-registries.md), [VII · Items and inventories](../systems/items/README.md), [items-and-stacks](../systems/items/items-and-stacks.md), [recipes](../systems/items/recipes.md), [using-an-item](../systems/items/using-an-item.md), [the-spear](../systems/player/the-spear.md), [the-sword-swing](../systems/player/the-sword-swing.md) |
| `ItemArgument` | [brigadier-and-commands](../systems/commands/brigadier-and-commands.md), [codecs-nbt-json](../systems/foundations/codecs-nbt-json.md) |
| `ItemAttributeModifiers` | [attributes](../systems/entities/attributes.md) |
| `ItemBody` | [dialogs](../systems/commands/dialogs.md) |
| `ItemClusterRenderState` | [block-entity-rendering](../systems/rendering/block-entity-rendering.md) |
| `ItemCombinerMenu` | [containers-and-menus](../systems/items/containers-and-menus.md) |
| `ItemCombinerScreen` | [containers-and-menus](../systems/items/containers-and-menus.md) |
| `ItemCommands` | [brigadier-and-commands](../systems/commands/brigadier-and-commands.md), [contexts-and-predicates](../systems/items/contexts-and-predicates.md) |
| `ItemContainerContents` | [data-components](../systems/foundations/data-components.md), [items-and-stacks](../systems/items/items-and-stacks.md) |
| `ItemCooldowns` | [block-interaction](../systems/blocks/block-interaction.md), [using-an-item](../systems/items/using-an-item.md) |
| `ItemEnchantments` | [data-components](../systems/foundations/data-components.md), [enchanting](../systems/items/enchanting.md), [enchantments](../systems/items/enchantments.md), [items-and-stacks](../systems/items/items-and-stacks.md) |
| `ItemEntity` | [hierarchy](../maps/hierarchy.md), [non-living-damage](../reference/non-living-damage.md), [block-breaking](../systems/blocks/block-breaking.md), [the-client-level](../systems/client/the-client-level.md), [damage-and-death](../systems/entities/damage-and-death.md), [movement-and-collision](../systems/entities/movement-and-collision.md), [VII · Items and inventories](../systems/items/README.md), [items-and-stacks](../systems/items/items-and-stacks.md), [the-sword-swing](../systems/player/the-sword-swing.md), [game-events-and-vibrations](../systems/world/game-events-and-vibrations.md) |
| `ItemEntityRenderer` | [block-entity-rendering](../systems/rendering/block-entity-rendering.md) |
| `ItemFeatureRenderer` | [submit-phases](../reference/submit-phases.md) |
| `ItemFrame` | [non-living-damage](../reference/non-living-damage.md), [diodes-and-observers](../systems/blocks/diodes-and-observers.md), [damage-and-death](../systems/entities/damage-and-death.md), [synched-entity-data](../systems/entities/synched-entity-data.md), [what-the-client-is-told](../systems/networking/what-the-client-is-told.md) |
| `ItemIds` | [what-this-book-skips](../systems/anatomy/what-this-book-skips.md), [identifiers-and-registries](../systems/foundations/identifiers-and-registries.md) |
| `ItemInHandLayer` | [entity-rendering](../systems/rendering/entity-rendering.md) |
| `ItemInHandRenderer` | [enchantments](../systems/items/enchantments.md), [items-and-stacks](../systems/items/items-and-stacks.md), [using-an-item](../systems/items/using-an-item.md), [entity-rendering](../systems/rendering/entity-rendering.md) |
| `ItemInput` | [naming-drift](../reference/naming-drift.md), [brigadier-and-commands](../systems/commands/brigadier-and-commands.md), [codecs-nbt-json](../systems/foundations/codecs-nbt-json.md), [items-and-stacks](../systems/items/items-and-stacks.md) |
| `ItemInstance` | [data-components](../systems/foundations/data-components.md), [contexts-and-predicates](../systems/items/contexts-and-predicates.md), [items-and-stacks](../systems/items/items-and-stacks.md) |
| `ItemLike` | [hierarchy](../maps/hierarchy.md) |
| `ItemLore` | [data-components](../systems/foundations/data-components.md) |
| `ItemModel` | [naming-drift](../reference/naming-drift.md), [block-entity-rendering](../systems/rendering/block-entity-rendering.md), [models-and-atlases](../systems/rendering/models-and-atlases.md) |
| `ItemModelGenerator` | [models-and-atlases](../systems/rendering/models-and-atlases.md) |
| `ItemModelResolver` | [naming-drift](../reference/naming-drift.md), [block-entity-rendering](../systems/rendering/block-entity-rendering.md), [entity-rendering](../systems/rendering/entity-rendering.md), [models-and-atlases](../systems/rendering/models-and-atlases.md) |
| `ItemModels` | [models-and-atlases](../systems/rendering/models-and-atlases.md) |
| `ItemOwner` | [entity-anatomy](../systems/entities/entity-anatomy.md) |
| `ItemParser` | [brigadier-and-commands](../systems/commands/brigadier-and-commands.md), [codecs-nbt-json](../systems/foundations/codecs-nbt-json.md) |
| `ItemParticleOption` | [items-and-stacks](../systems/items/items-and-stacks.md) |
| `ItemPickupParticle` | [particles](../systems/rendering/particles.md) |
| `ItemPredicate` | [advancements](../systems/commands/advancements.md), [data-components](../systems/foundations/data-components.md), [contexts-and-predicates](../systems/items/contexts-and-predicates.md) |
| `ItemPredicateArgument` | [brigadier-and-commands](../systems/commands/brigadier-and-commands.md) |
| `Items` | [biggest](../maps/biggest.md), [hierarchy](../maps/hierarchy.md), [what-this-book-skips](../systems/anatomy/what-this-book-skips.md), [synched-entity-data](../systems/entities/synched-entity-data.md), [identifiers-and-registries](../systems/foundations/identifiers-and-registries.md), [enchanting](../systems/items/enchanting.md), [enchantments](../systems/items/enchantments.md), [items-and-stacks](../systems/items/items-and-stacks.md), [the-spear](../systems/player/the-spear.md) |
| `ItemStack` | [fanin](../maps/fanin.md), [glossary](../reference/glossary.md), [naming-drift](../reference/naming-drift.md), [non-living-damage](../reference/non-living-damage.md), [what-this-book-skips](../systems/anatomy/what-this-book-skips.md), [block-breaking](../systems/blocks/block-breaking.md), [block-interaction](../systems/blocks/block-interaction.md), [prediction-and-acks](../systems/client/prediction-and-acks.md), [attributes](../systems/entities/attributes.md), [damage-and-death](../systems/entities/damage-and-death.md), [synched-entity-data](../systems/entities/synched-entity-data.md), [II · Foundations](../systems/foundations/README.md), [codecs-nbt-json](../systems/foundations/codecs-nbt-json.md), [data-components](../systems/foundations/data-components.md), [data-driven-types](../systems/foundations/data-driven-types.md), [tags](../systems/foundations/tags.md), [VII · Items and inventories](../systems/items/README.md), [containers-and-menus](../systems/items/containers-and-menus.md), [enchanting](../systems/items/enchanting.md), [enchantments](../systems/items/enchantments.md), [items-and-stacks](../systems/items/items-and-stacks.md), [recipes](../systems/items/recipes.md), [using-an-item](../systems/items/using-an-item.md), [packets-and-stream-codecs](../systems/networking/packets-and-stream-codecs.md), [hunger-and-experience](../systems/player/hunger-and-experience.md), [the-spear](../systems/player/the-spear.md), [the-sword-swing](../systems/player/the-sword-swing.md), [models-and-atlases](../systems/rendering/models-and-atlases.md) |
| `ItemStackRenderState` | [block-entity-rendering](../systems/rendering/block-entity-rendering.md), [entity-rendering](../systems/rendering/entity-rendering.md), [models-and-atlases](../systems/rendering/models-and-atlases.md) |
| `ItemStackTemplate` | [data-components](../systems/foundations/data-components.md), [text-components](../systems/foundations/text-components.md), [items-and-stacks](../systems/items/items-and-stacks.md), [recipes](../systems/items/recipes.md) |
| `ItemStackWithSlot` | [codecs-nbt-json](../systems/foundations/codecs-nbt-json.md) |
| `ItemSteerable` | [entity-anatomy](../systems/entities/entity-anatomy.md) |
| `ItemTags` | [tags](../systems/foundations/tags.md), [the-sword-swing](../systems/player/the-sword-swing.md), [game-events-and-vibrations](../systems/world/game-events-and-vibrations.md) |
| `ItemTintSource` | [naming-drift](../reference/naming-drift.md), [models-and-atlases](../systems/rendering/models-and-atlases.md) |
| `ItemUseAnimation` | [naming-drift](../reference/naming-drift.md), [using-an-item](../systems/items/using-an-item.md), [hunger-and-experience](../systems/player/hunger-and-experience.md) |
| `ItemUsedOnLocationTrigger` | [contexts-and-predicates](../systems/items/contexts-and-predicates.md) |
| `JigsawBlock` | [jigsaw-and-templates](../systems/worldgen/jigsaw-and-templates.md) |
| `JigsawBlockEntity` | [jigsaw-and-templates](../systems/worldgen/jigsaw-and-templates.md) |
| `JigsawJunction` | [jigsaw-and-templates](../systems/worldgen/jigsaw-and-templates.md) |
| `JigsawPlacement` | [jigsaw-and-templates](../systems/worldgen/jigsaw-and-templates.md) |
| `JigsawReplacementProcessor` | [jigsaw-and-templates](../systems/worldgen/jigsaw-and-templates.md) |
| `JigsawStructure` | [jigsaw-and-templates](../systems/worldgen/jigsaw-and-templates.md), [structure-placement](../systems/worldgen/structure-placement.md) |
| `JoinWorldTask` | [protocol-phases](../systems/networking/protocol-phases.md), [players-and-sessions](../systems/server/players-and-sessions.md) |
| `JOrbisAudioStream` | [sound-engine](../systems/client/sound-engine.md) |
| `JsonEventLog` | [resource-system](../systems/foundations/resource-system.md) |
| `JsonOps` | [data-driven-types](../systems/foundations/data-driven-types.md), [packets-and-stream-codecs](../systems/networking/packets-and-stream-codecs.md) |
| `JsonRpc` | [threads](../reference/threads.md), [what-this-book-skips](../systems/anatomy/what-this-book-skips.md), [starting-a-server](../systems/server/starting-a-server.md) |
| `JsonRpcNotificationService` | [starting-a-server](../systems/server/starting-a-server.md) |
| `JumpableVehicleBar` | [hud-elements](../reference/hud-elements.md), [hud](../systems/client/hud.md) |
| `JumpControl` | [pathfinding](../systems/entities/pathfinding.md) |
| `JungleTemplePiece` | [hand-built-structures](../systems/worldgen/hand-built-structures.md) |
| `JUnitLikeTestReporter` | [game-tests](../systems/commands/game-tests.md) |
| `JvmProfiler` | [server-tick](../systems/server/server-tick.md) |
| `KeybindContents` | [text-components](../systems/foundations/text-components.md) |
| `KeybindResolver` | [text-components](../systems/foundations/text-components.md) |
| `KeyboardHandler` | [glossary](../reference/glossary.md), [input-and-keybinds](../systems/client/input-and-keybinds.md), [the-client-loop](../systems/client/the-client-loop.md), [permissions](../systems/commands/permissions.md), [resource-system](../systems/foundations/resource-system.md), [text-components](../systems/foundations/text-components.md), [input-to-movement](../systems/player/input-to-movement.md), [the-window](../systems/rendering/the-window.md) |
| `KeyboardInput` | [input-and-keybinds](../systems/client/input-and-keybinds.md), [input-to-movement](../systems/player/input-to-movement.md), [player-anatomy](../systems/player/player-anatomy.md), [the-two-phase-tick](../systems/player/the-two-phase-tick.md) |
| `KeyDispatchCodec` | [data-driven-types](../systems/foundations/data-driven-types.md) |
| `KeyEvent` | [naming-drift](../reference/naming-drift.md), [input-and-keybinds](../systems/client/input-and-keybinds.md), [input-to-movement](../systems/player/input-to-movement.md) |
| `Keyframe` | [entity-rendering](../systems/rendering/entity-rendering.md) |
| `KeyframeAnimation` | [entity-rendering](../systems/rendering/entity-rendering.md) |
| `KeyframeTrack` | [environment-attributes-and-timelines](../systems/world/environment-attributes-and-timelines.md) |
| `KeyframeTrackSampler` | [environment-attributes-and-timelines](../systems/world/environment-attributes-and-timelines.md) |
| `KeyMapping` | [packages](../maps/packages.md), [glossary](../reference/glossary.md), [naming-drift](../reference/naming-drift.md), [input-and-keybinds](../systems/client/input-and-keybinds.md), [text-components](../systems/foundations/text-components.md), [input-to-movement](../systems/player/input-to-movement.md), [the-window](../systems/rendering/the-window.md) |
| `KineticWeapon` | [data-components](../systems/foundations/data-components.md), [enchantments](../systems/items/enchantments.md), [the-spear](../systems/player/the-spear.md), [the-sword-swing](../systems/player/the-sword-swing.md) |
| `KnownPack` | [identifiers-and-registries](../systems/foundations/identifiers-and-registries.md), [resource-system](../systems/foundations/resource-system.md), [protocol-phases](../systems/networking/protocol-phases.md) |
| `KnownPacksManager` | [identifiers-and-registries](../systems/foundations/identifiers-and-registries.md), [protocol-phases](../systems/networking/protocol-phases.md) |
| `Language` | [text-and-fonts](../systems/client/text-and-fonts.md), [text-components](../systems/foundations/text-components.md) |
| `LanguageManager` | [resource-system](../systems/foundations/resource-system.md), [text-components](../systems/foundations/text-components.md) |
| `LanServerPinger` | [threads](../reference/threads.md), [anatomy](../systems/anatomy/anatomy.md) |
| `LastSeenMessages` | [chat-and-signing](../systems/networking/chat-and-signing.md) |
| `LastSeenMessagesTracker` | [chat-and-signing](../systems/networking/chat-and-signing.md) |
| `LastSeenMessagesValidator` | [chat-and-signing](../systems/networking/chat-and-signing.md) |
| `LavaFluid` | [environment-attributes-and-timelines](../systems/world/environment-attributes-and-timelines.md), [fluids](../systems/world/fluids.md), [scheduled-ticks](../systems/world/scheduled-ticks.md) |
| `LavaFogEnvironment` | [lightmap-fog-and-sky](../systems/rendering/lightmap-fog-and-sky.md) |
| `LavaSubmergedBlockProcessor` | [hand-built-structures](../systems/worldgen/hand-built-structures.md) |
| `LayerDefinition` | [entity-rendering](../systems/rendering/entity-rendering.md) |
| `LayerDefinitions` | [entity-rendering](../systems/rendering/entity-rendering.md) |
| `LayeredRegistryAccess` | [data-driven-types](../systems/foundations/data-driven-types.md), [identifiers-and-registries](../systems/foundations/identifiers-and-registries.md) |
| `LayeringTransform` | [blaze3d](../systems/rendering/blaze3d.md) |
| `LayerLightEventListener` | [lighting](../systems/world/lighting.md) |
| `LayerLightSectionStorage` | [lighting](../systems/world/lighting.md) |
| `Layout` | [gui-and-screens](../systems/client/gui-and-screens.md) |
| `LayoutElement` | [gui-and-screens](../systems/client/gui-and-screens.md) |
| `LayoutSettings` | [gui-and-screens](../systems/client/gui-and-screens.md) |
| `Leashable` | [entity-anatomy](../systems/entities/entity-anatomy.md) |
| `LeashFeatureRenderer` | [submit-phases](../reference/submit-phases.md) |
| `LeavesBlock` | [chunk-anatomy](../systems/world/chunk-anatomy.md) |
| `LeaveVineDecorator` | [trees](../systems/worldgen/trees.md) |
| `LecternScreen` | [containers-and-menus](../systems/items/containers-and-menus.md) |
| `LegacyQueryHandler` | [what-this-book-skips](../systems/anatomy/what-this-book-skips.md), [the-connection](../systems/networking/the-connection.md) |
| `LegacyRandomSource` | [math-and-primitives](../reference/math-and-primitives.md), [density-functions](../systems/worldgen/density-functions.md), [terrain](../systems/worldgen/terrain.md) |
| `LegacySinglePoolElement` | [jigsaw-and-templates](../systems/worldgen/jigsaw-and-templates.md) |
| `LenientJsonParser` | [codecs-nbt-json](../systems/foundations/codecs-nbt-json.md) |
| `LerpFunction` | [environment-attributes-and-timelines](../systems/world/environment-attributes-and-timelines.md) |
| `LerpingBossEvent` | [hud](../systems/client/hud.md) |
| `Level` | [lectures](../lectures.md), [fanin](../maps/fanin.md), [Reference](../reference/README.md), [block-update-flags](../reference/block-update-flags.md), [glossary](../reference/glossary.md), [level-data-and-rules](../reference/level-data-and-rules.md), [math-and-primitives](../reference/math-and-primitives.md), [naming-drift](../reference/naming-drift.md), [threads](../reference/threads.md), [V · Blocks](../systems/blocks/README.md), [block-breaking](../systems/blocks/block-breaking.md), [block-entities](../systems/blocks/block-entities.md), [block-interaction](../systems/blocks/block-interaction.md), [blocks-and-states](../systems/blocks/blocks-and-states.md), [diodes-and-observers](../systems/blocks/diodes-and-observers.md), [pistons-and-block-events](../systems/blocks/pistons-and-block-events.md), [signal-and-dust](../systems/blocks/signal-and-dust.md), [X · The client](../systems/client/README.md), [the-client-level](../systems/client/the-client-level.md), [the-client-loop](../systems/client/the-client-loop.md), [what-makes-a-sound](../systems/client/what-makes-a-sound.md), [entity-selectors](../systems/commands/entity-selectors.md), [entity-anatomy](../systems/entities/entity-anatomy.md), [entity-lifecycle](../systems/entities/entity-lifecycle.md), [movement-and-collision](../systems/entities/movement-and-collision.md), [pathfinding](../systems/entities/pathfinding.md), [containers-and-menus](../systems/items/containers-and-menus.md), [contexts-and-predicates](../systems/items/contexts-and-predicates.md), [using-an-item](../systems/items/using-an-item.md), [XI · Rendering](../systems/rendering/README.md), [entity-rendering](../systems/rendering/entity-rendering.md), [lightmap-fog-and-sky](../systems/rendering/lightmap-fog-and-sky.md), [particles](../systems/rendering/particles.md), [section-meshing](../systems/rendering/section-meshing.md), [III · The server](../systems/server/README.md), [server-level-tick](../systems/server/server-level-tick.md), [chunk-anatomy](../systems/world/chunk-anatomy.md), [chunk-generation-pipeline](../systems/world/chunk-generation-pipeline.md), [environment-attributes-and-timelines](../systems/world/environment-attributes-and-timelines.md), [fluids](../systems/world/fluids.md), [game-events-and-vibrations](../systems/world/game-events-and-vibrations.md), [lighting](../systems/world/lighting.md), [points-of-interest](../systems/world/points-of-interest.md), [scheduled-ticks](../systems/world/scheduled-ticks.md) |
| `LevelAccessor` | [block-interaction](../systems/blocks/block-interaction.md), [the-client-level](../systems/client/the-client-level.md), [scheduled-ticks](../systems/world/scheduled-ticks.md) |
| `LevelBasedPermissionSet` | [naming-drift](../reference/naming-drift.md), [entity-selectors](../systems/commands/entity-selectors.md), [functions-and-macros](../systems/commands/functions-and-macros.md), [permissions](../systems/commands/permissions.md), [players-and-sessions](../systems/server/players-and-sessions.md) |
| `LevelBasedValue` | [data-driven-types](../systems/foundations/data-driven-types.md), [enchantments](../systems/items/enchantments.md) |
| `LevelCallback` | [entity-lifecycle](../systems/entities/entity-lifecycle.md) |
| `LevelChunk` | [lectures](../lectures.md), [block-entities](../systems/blocks/block-entities.md), [blocks-and-states](../systems/blocks/blocks-and-states.md), [diodes-and-observers](../systems/blocks/diodes-and-observers.md), [debugging-the-running-game](../systems/client/debugging-the-running-game.md), [the-client-level](../systems/client/the-client-level.md), [how-a-server-dies](../systems/server/how-a-server-dies.md), [chunk-anatomy](../systems/world/chunk-anatomy.md), [chunk-generation-pipeline](../systems/world/chunk-generation-pipeline.md), [chunk-storage](../systems/world/chunk-storage.md), [fluids](../systems/world/fluids.md), [game-events-and-vibrations](../systems/world/game-events-and-vibrations.md), [lighting](../systems/world/lighting.md), [scheduled-ticks](../systems/world/scheduled-ticks.md), [tickets-and-loading](../systems/world/tickets-and-loading.md) |
| `LevelChunkSection` | [server-level-tick](../systems/server/server-level-tick.md), [chunk-anatomy](../systems/world/chunk-anatomy.md), [chunk-storage](../systems/world/chunk-storage.md), [lighting](../systems/world/lighting.md), [points-of-interest](../systems/world/points-of-interest.md), [scheduled-ticks](../systems/world/scheduled-ticks.md), [biomes](../systems/worldgen/biomes.md) |
| `LevelChunkTicks` | [server-level-tick](../systems/server/server-level-tick.md), [chunk-anatomy](../systems/world/chunk-anatomy.md), [scheduled-ticks](../systems/world/scheduled-ticks.md) |
| `LevelData` | [level-data-and-rules](../reference/level-data-and-rules.md) |
| `LevelDebugSynchronizers` | [debugging-the-running-game](../systems/client/debugging-the-running-game.md), [server-level-tick](../systems/server/server-level-tick.md), [points-of-interest](../systems/world/points-of-interest.md) |
| `LeveledPriorityQueue` | [lighting](../systems/world/lighting.md) |
| `LevelEntityGetterAdapter` | [entity-selectors](../systems/commands/entity-selectors.md) |
| `LevelEvent` | [particles](../systems/rendering/particles.md) |
| `LevelEventHandler` | [what-makes-a-sound](../systems/client/what-makes-a-sound.md) |
| `LevelExtractor` | [block-update-flags](../reference/block-update-flags.md), [naming-drift](../reference/naming-drift.md), [block-breaking](../systems/blocks/block-breaking.md), [block-interaction](../systems/blocks/block-interaction.md), [blocks-and-states](../systems/blocks/blocks-and-states.md), [debugging-the-running-game](../systems/client/debugging-the-running-game.md), [options](../systems/client/options.md), [the-client-level](../systems/client/the-client-level.md), [entity-anatomy](../systems/entities/entity-anatomy.md), [synched-entity-data](../systems/entities/synched-entity-data.md), [resource-system](../systems/foundations/resource-system.md), [XI · Rendering](../systems/rendering/README.md), [block-entity-rendering](../systems/rendering/block-entity-rendering.md), [entity-rendering](../systems/rendering/entity-rendering.md), [lightmap-fog-and-sky](../systems/rendering/lightmap-fog-and-sky.md), [models-and-atlases](../systems/rendering/models-and-atlases.md), [particles](../systems/rendering/particles.md), [section-meshing](../systems/rendering/section-meshing.md), [the-frame](../systems/rendering/the-frame.md), [visibility-and-the-frame-graph](../systems/rendering/visibility-and-the-frame-graph.md), [environment-attributes-and-timelines](../systems/world/environment-attributes-and-timelines.md), [lighting](../systems/world/lighting.md) |
| `LevelHeightAccessor` | [block-breaking](../systems/blocks/block-breaking.md), [block-interaction](../systems/blocks/block-interaction.md), [chunk-anatomy](../systems/world/chunk-anatomy.md) |
| `LevelLightEngine` | [the-client-level](../systems/client/the-client-level.md), [chunk-anatomy](../systems/world/chunk-anatomy.md), [chunk-storage](../systems/world/chunk-storage.md), [lighting](../systems/world/lighting.md) |
| `LevelLoadingScreen` | [hud-elements](../reference/hud-elements.md) |
| `LevelLoadListener` | [players-and-sessions](../systems/server/players-and-sessions.md), [starting-a-server](../systems/server/starting-a-server.md) |
| `LevelLoadProgressTracker` | [starting-a-server](../systems/server/starting-a-server.md) |
| `LevelLoadTracker` | [starting-a-server](../systems/server/starting-a-server.md) |
| `LevelReader` | [the-client-level](../systems/client/the-client-level.md), [identifiers-and-registries](../systems/foundations/identifiers-and-registries.md), [lightmap-fog-and-sky](../systems/rendering/lightmap-fog-and-sky.md), [lighting](../systems/world/lighting.md), [scheduled-ticks](../systems/world/scheduled-ticks.md), [biomes](../systems/worldgen/biomes.md) |
| `LevelRenderer` | [glossary](../reference/glossary.md), [math-and-primitives](../reference/math-and-primitives.md), [naming-drift](../reference/naming-drift.md), [submit-phases](../reference/submit-phases.md), [block-interaction](../systems/blocks/block-interaction.md), [the-client-level](../systems/client/the-client-level.md), [the-gui-render-tree](../systems/client/the-gui-render-tree.md), [blaze3d](../systems/rendering/blaze3d.md), [block-entity-rendering](../systems/rendering/block-entity-rendering.md), [entity-rendering](../systems/rendering/entity-rendering.md), [lightmap-fog-and-sky](../systems/rendering/lightmap-fog-and-sky.md), [models-and-atlases](../systems/rendering/models-and-atlases.md), [post-processing](../systems/rendering/post-processing.md), [section-meshing](../systems/rendering/section-meshing.md), [the-frame](../systems/rendering/the-frame.md), [visibility-and-the-frame-graph](../systems/rendering/visibility-and-the-frame-graph.md) |
| `LevelResource` | [level-data-and-rules](../reference/level-data-and-rules.md), [advancements](../systems/commands/advancements.md) |
| `LevelSettings` | [level-data-and-rules](../reference/level-data-and-rules.md), [creating-a-world](../systems/worldgen/creating-a-world.md) |
| `LevelStem` | [glossary](../reference/glossary.md), [level-data-and-rules](../reference/level-data-and-rules.md), [starting-a-server](../systems/server/starting-a-server.md), [creating-a-world](../systems/worldgen/creating-a-world.md) |
| `LevelStorageSource` | [level-data-and-rules](../reference/level-data-and-rules.md), [resource-system](../systems/foundations/resource-system.md), [how-a-server-dies](../systems/server/how-a-server-dies.md), [starting-a-server](../systems/server/starting-a-server.md), [chunk-storage](../systems/world/chunk-storage.md), [creating-a-world](../systems/worldgen/creating-a-world.md) |
| `LevelSummary` | [level-data-and-rules](../reference/level-data-and-rules.md), [what-this-book-skips](../systems/anatomy/what-this-book-skips.md), [starting-a-server](../systems/server/starting-a-server.md), [creating-a-world](../systems/worldgen/creating-a-world.md) |
| `LevelTargetBundle` | [post-processing](../systems/rendering/post-processing.md), [visibility-and-the-frame-graph](../systems/rendering/visibility-and-the-frame-graph.md) |
| `LevelTickAccess` | [diodes-and-observers](../systems/blocks/diodes-and-observers.md), [scheduled-ticks](../systems/world/scheduled-ticks.md) |
| `LevelTicks` | [the-client-level](../systems/client/the-client-level.md), [server-level-tick](../systems/server/server-level-tick.md), [fluids](../systems/world/fluids.md), [scheduled-ticks](../systems/world/scheduled-ticks.md) |
| `LevelWriter` | [entity-lifecycle](../systems/entities/entity-lifecycle.md), [fluids](../systems/world/fluids.md), [hand-built-structures](../systems/worldgen/hand-built-structures.md) |
| `LeverBlock` | [signal-and-dust](../systems/blocks/signal-and-dust.md) |
| `Library` | [sound-engine](../systems/client/sound-engine.md) |
| `Lifecycle` | [data-driven-types](../systems/foundations/data-driven-types.md), [identifiers-and-registries](../systems/foundations/identifiers-and-registries.md) |
| `LightChunk` | [chunk-anatomy](../systems/world/chunk-anatomy.md), [lighting](../systems/world/lighting.md) |
| `LightChunkGetter` | [lighting](../systems/world/lighting.md) |
| `LightCoordsUtil` | [naming-drift](../reference/naming-drift.md), [lightmap-fog-and-sky](../systems/rendering/lightmap-fog-and-sky.md) |
| `LightEngine` | [the-client-level](../systems/client/the-client-level.md), [chunk-anatomy](../systems/world/chunk-anatomy.md), [lighting](../systems/world/lighting.md) |
| `LightEventListener` | [lighting](../systems/world/lighting.md) |
| `LightLayer` | [lighting](../systems/world/lighting.md) |
| `Lightmap` | [introduction](../introduction.md), [naming-drift](../reference/naming-drift.md), [blaze3d](../systems/rendering/blaze3d.md), [lightmap-fog-and-sky](../systems/rendering/lightmap-fog-and-sky.md), [the-frame](../systems/rendering/the-frame.md), [lighting](../systems/world/lighting.md) |
| `LightmapRenderState` | [lightmap-fog-and-sky](../systems/rendering/lightmap-fog-and-sky.md) |
| `LightmapRenderStateExtractor` | [lightmap-fog-and-sky](../systems/rendering/lightmap-fog-and-sky.md), [environment-attributes-and-timelines](../systems/world/environment-attributes-and-timelines.md) |
| `LightningBolt` | [hierarchy](../maps/hierarchy.md), [non-living-damage](../reference/non-living-damage.md), [the-client-level](../systems/client/the-client-level.md), [what-makes-a-sound](../systems/client/what-makes-a-sound.md), [damage-and-death](../systems/entities/damage-and-death.md), [environment-attributes-and-timelines](../systems/world/environment-attributes-and-timelines.md) |
| `LightningRodBlock` | [scheduled-ticks](../systems/world/scheduled-ticks.md) |
| `LinearCongruentialGenerator` | [math-and-primitives](../reference/math-and-primitives.md) |
| `LinearLayout` | [gui-and-screens](../systems/client/gui-and-screens.md) |
| `LinearPalette` | [chunk-anatomy](../systems/world/chunk-anatomy.md) |
| `LinkFileSystem` | [resource-system](../systems/foundations/resource-system.md) |
| `LinkFSPath` | [resource-system](../systems/foundations/resource-system.md) |
| `LinkFSProvider` | [resource-system](../systems/foundations/resource-system.md) |
| `LiquidBlock` | [glossary](../reference/glossary.md), [fluids](../systems/world/fluids.md), [scheduled-ticks](../systems/world/scheduled-ticks.md) |
| `LiquidBlockContainer` | [fluids](../systems/world/fluids.md) |
| `Listener` | [sound-engine](../systems/client/sound-engine.md) |
| `ListenerTransform` | [sound-engine](../systems/client/sound-engine.md) |
| `ListPoolElement` | [jigsaw-and-templates](../systems/worldgen/jigsaw-and-templates.md) |
| `ListTag` | [glossary](../reference/glossary.md), [codecs-nbt-json](../systems/foundations/codecs-nbt-json.md) |
| `LivingEntity` | [biggest](../maps/biggest.md), [fanin](../maps/fanin.md), [hierarchy](../maps/hierarchy.md), [Reference](../reference/README.md), [glossary](../reference/glossary.md), [hud-elements](../reference/hud-elements.md), [naming-drift](../reference/naming-drift.md), [non-living-damage](../reference/non-living-damage.md), [the-client-level](../systems/client/the-client-level.md), [entity-selectors](../systems/commands/entity-selectors.md), [scoreboard-and-data](../systems/commands/scoreboard-and-data.md), [VI · Entities](../systems/entities/README.md), [ai-goals-and-brains](../systems/entities/ai-goals-and-brains.md), [attributes](../systems/entities/attributes.md), [authority](../systems/entities/authority.md), [damage-and-death](../systems/entities/damage-and-death.md), [entity-anatomy](../systems/entities/entity-anatomy.md), [entity-lifecycle](../systems/entities/entity-lifecycle.md), [movement-and-collision](../systems/entities/movement-and-collision.md), [pathfinding](../systems/entities/pathfinding.md), [synched-entity-data](../systems/entities/synched-entity-data.md), [text-components](../systems/foundations/text-components.md), [contexts-and-predicates](../systems/items/contexts-and-predicates.md), [enchanting](../systems/items/enchanting.md), [enchantments](../systems/items/enchantments.md), [items-and-stacks](../systems/items/items-and-stacks.md), [loot-tables](../systems/items/loot-tables.md), [using-an-item](../systems/items/using-an-item.md), [what-the-client-is-told](../systems/networking/what-the-client-is-told.md), [VIII · The player](../systems/player/README.md), [hunger-and-experience](../systems/player/hunger-and-experience.md), [input-to-movement](../systems/player/input-to-movement.md), [player-anatomy](../systems/player/player-anatomy.md), [status-effects](../systems/player/status-effects.md), [the-spear](../systems/player/the-spear.md), [the-sword-swing](../systems/player/the-sword-swing.md), [the-two-phase-tick](../systems/player/the-two-phase-tick.md), [entity-rendering](../systems/rendering/entity-rendering.md), [game-events-and-vibrations](../systems/world/game-events-and-vibrations.md), [points-of-interest](../systems/world/points-of-interest.md) |
| `LivingEntityRenderer` | [scoreboard-and-data](../systems/commands/scoreboard-and-data.md), [entity-rendering](../systems/rendering/entity-rendering.md) |
| `LivingEntityRenderState` | [entity-rendering](../systems/rendering/entity-rendering.md) |
| `Llama` | [pathfinding](../systems/entities/pathfinding.md) |
| `LoadingChunkTracker` | [tickets-and-loading](../systems/world/tickets-and-loading.md) |
| `LoadingOverlay` | [anatomy](../systems/anatomy/anatomy.md), [gui-and-screens](../systems/client/gui-and-screens.md), [resource-system](../systems/foundations/resource-system.md) |
| `LocalCoordinates` | [brigadier-and-commands](../systems/commands/brigadier-and-commands.md) |
| `LocalFrameDecoder` | [the-connection](../systems/networking/the-connection.md) |
| `LocalFrameEncoder` | [the-connection](../systems/networking/the-connection.md) |
| `LocalMobCapCalculator` | [entity-lifecycle](../systems/entities/entity-lifecycle.md), [server-level-tick](../systems/server/server-level-tick.md) |
| `LocalPlayer` | [biggest](../maps/biggest.md), [naming-drift](../reference/naming-drift.md), [threads](../reference/threads.md), [anatomy](../systems/anatomy/anatomy.md), [block-interaction](../systems/blocks/block-interaction.md), [gui-and-screens](../systems/client/gui-and-screens.md), [input-and-keybinds](../systems/client/input-and-keybinds.md), [prediction-and-acks](../systems/client/prediction-and-acks.md), [what-makes-a-sound](../systems/client/what-makes-a-sound.md), [permissions](../systems/commands/permissions.md), [damage-and-death](../systems/entities/damage-and-death.md), [synched-entity-data](../systems/entities/synched-entity-data.md), [text-components](../systems/foundations/text-components.md), [using-an-item](../systems/items/using-an-item.md), [hunger-and-experience](../systems/player/hunger-and-experience.md), [input-to-movement](../systems/player/input-to-movement.md), [player-anatomy](../systems/player/player-anatomy.md), [the-spear](../systems/player/the-spear.md), [the-sword-swing](../systems/player/the-sword-swing.md), [the-two-phase-tick](../systems/player/the-two-phase-tick.md) |
| `LocalSampleLogger` | [debugging-the-running-game](../systems/client/debugging-the-running-game.md) |
| `LocalTime` | [block-entity-rendering](../systems/rendering/block-entity-rendering.md) |
| `LocateCommand` | [brigadier-and-commands](../systems/commands/brigadier-and-commands.md), [points-of-interest](../systems/world/points-of-interest.md) |
| `LocationCheck` | [contexts-and-predicates](../systems/items/contexts-and-predicates.md) |
| `LocationPredicate` | [contexts-and-predicates](../systems/items/contexts-and-predicates.md) |
| `LocatorBar` | [hud-elements](../reference/hud-elements.md), [hud](../systems/client/hud.md) |
| `LodestoneTracker` | [points-of-interest](../systems/world/points-of-interest.md) |
| `LoggedChatMessage` | [chat-and-signing](../systems/networking/chat-and-signing.md) |
| `LoggingLevelLoadListener` | [players-and-sessions](../systems/server/players-and-sessions.md), [starting-a-server](../systems/server/starting-a-server.md) |
| `LoginProtocols` | [packets-and-stream-codecs](../systems/networking/packets-and-stream-codecs.md), [protocol-phases](../systems/networking/protocol-phases.md) |
| `LogTestReporter` | [game-tests](../systems/commands/game-tests.md) |
| `LongArrayTag` | [codecs-nbt-json](../systems/foundations/codecs-nbt-json.md) |
| `LookAtPlayerGoal` | [ai-goals-and-brains](../systems/entities/ai-goals-and-brains.md) |
| `LookControl` | [pathfinding](../systems/entities/pathfinding.md) |
| `LoopingAudioStream` | [sound-engine](../systems/client/sound-engine.md) |
| `LootCommand` | [brigadier-and-commands](../systems/commands/brigadier-and-commands.md), [contexts-and-predicates](../systems/items/contexts-and-predicates.md) |
| `LootContext` | [advancements](../systems/commands/advancements.md), [contexts-and-predicates](../systems/items/contexts-and-predicates.md), [enchantments](../systems/items/enchantments.md), [loot-tables](../systems/items/loot-tables.md) |
| `LootContextArg` | [contexts-and-predicates](../systems/items/contexts-and-predicates.md) |
| `LootContextParams` | [naming-drift](../reference/naming-drift.md), [block-breaking](../systems/blocks/block-breaking.md), [damage-and-death](../systems/entities/damage-and-death.md), [contexts-and-predicates](../systems/items/contexts-and-predicates.md), [enchanting](../systems/items/enchanting.md), [loot-tables](../systems/items/loot-tables.md), [environment-attributes-and-timelines](../systems/world/environment-attributes-and-timelines.md) |
| `LootContextParamSets` | [block-breaking](../systems/blocks/block-breaking.md), [entity-selectors](../systems/commands/entity-selectors.md), [data-driven-types](../systems/foundations/data-driven-types.md), [contexts-and-predicates](../systems/items/contexts-and-predicates.md) |
| `LootContextUser` | [contexts-and-predicates](../systems/items/contexts-and-predicates.md) |
| `LootDataType` | [data-driven-types](../systems/foundations/data-driven-types.md), [tags](../systems/foundations/tags.md), [contexts-and-predicates](../systems/items/contexts-and-predicates.md), [loot-tables](../systems/items/loot-tables.md) |
| `LootItem` | [data-driven-types](../systems/foundations/data-driven-types.md), [loot-tables](../systems/items/loot-tables.md) |
| `LootItemCondition` | [advancements](../systems/commands/advancements.md), [data-driven-types](../systems/foundations/data-driven-types.md), [contexts-and-predicates](../systems/items/contexts-and-predicates.md), [enchantments](../systems/items/enchantments.md) |
| `LootItemConditionalFunction` | [data-driven-types](../systems/foundations/data-driven-types.md), [loot-tables](../systems/items/loot-tables.md) |
| `LootItemConditions` | [contexts-and-predicates](../systems/items/contexts-and-predicates.md) |
| `LootItemEntityPropertyCondition` | [contexts-and-predicates](../systems/items/contexts-and-predicates.md) |
| `LootItemFunction` | [data-driven-types](../systems/foundations/data-driven-types.md), [enchanting](../systems/items/enchanting.md), [loot-tables](../systems/items/loot-tables.md) |
| `LootItemFunctions` | [data-driven-types](../systems/foundations/data-driven-types.md), [loot-tables](../systems/items/loot-tables.md) |
| `LootItemRandomChanceCondition` | [contexts-and-predicates](../systems/items/contexts-and-predicates.md) |
| `LootItemRandomChanceWithEnchantedBonusCondition` | [loot-tables](../systems/items/loot-tables.md) |
| `LootParams` | [contexts-and-predicates](../systems/items/contexts-and-predicates.md), [loot-tables](../systems/items/loot-tables.md) |
| `LootPool` | [data-driven-types](../systems/foundations/data-driven-types.md), [loot-tables](../systems/items/loot-tables.md) |
| `LootPoolEntries` | [loot-tables](../systems/items/loot-tables.md) |
| `LootPoolEntryContainer` | [data-driven-types](../systems/foundations/data-driven-types.md), [loot-tables](../systems/items/loot-tables.md) |
| `LootPoolSingletonContainer` | [data-driven-types](../systems/foundations/data-driven-types.md), [loot-tables](../systems/items/loot-tables.md) |
| `LootTable` | [data-driven-types](../systems/foundations/data-driven-types.md), [contexts-and-predicates](../systems/items/contexts-and-predicates.md), [loot-tables](../systems/items/loot-tables.md), [what-the-client-is-told](../systems/networking/what-the-client-is-told.md) |
| `LpVec3` | [packets-and-stream-codecs](../systems/networking/packets-and-stream-codecs.md) |
| `MacosUtil` | [the-window](../systems/rendering/the-window.md) |
| `MacroFunction` | [naming-drift](../reference/naming-drift.md), [functions-and-macros](../systems/commands/functions-and-macros.md) |
| `Main` | [the-client-loop](../systems/client/the-client-loop.md), [identifiers-and-registries](../systems/foundations/identifiers-and-registries.md), [how-a-server-dies](../systems/server/how-a-server-dies.md), [starting-a-server](../systems/server/starting-a-server.md), [creating-a-world](../systems/worldgen/creating-a-world.md) |
| `MainTarget` | [blaze3d](../systems/rendering/blaze3d.md) |
| `ManagementServer` | [threads](../reference/threads.md), [what-this-book-skips](../systems/anatomy/what-this-book-skips.md), [how-a-server-dies](../systems/server/how-a-server-dies.md), [server-tick](../systems/server/server-tick.md), [starting-a-server](../systems/server/starting-a-server.md) |
| `MangroveRootPlacement` | [trees](../systems/worldgen/trees.md) |
| `MangroveRootPlacer` | [trees](../systems/worldgen/trees.md) |
| `Mannequin` | [glossary](../reference/glossary.md), [naming-drift](../reference/naming-drift.md), [attributes](../systems/entities/attributes.md), [authority](../systems/entities/authority.md), [entity-anatomy](../systems/entities/entity-anatomy.md), [player-anatomy](../systems/player/player-anatomy.md) |
| `MapCodec` | [fanin](../maps/fanin.md), [dialogs](../systems/commands/dialogs.md), [data-driven-types](../systems/foundations/data-driven-types.md), [recipes](../systems/items/recipes.md) |
| `MapId` | [level-data-and-rules](../reference/level-data-and-rules.md) |
| `MapIndex` | [level-data-and-rules](../reference/level-data-and-rules.md) |
| `MapItem` | [items-and-stacks](../systems/items/items-and-stacks.md) |
| `MapItemSavedData` | [level-data-and-rules](../reference/level-data-and-rules.md) |
| `MappableRingBuffer` | [blaze3d](../systems/rendering/blaze3d.md), [lightmap-fog-and-sky](../systems/rendering/lightmap-fog-and-sky.md), [post-processing](../systems/rendering/post-processing.md) |
| `MappedRegistry` | [data-components](../systems/foundations/data-components.md), [data-driven-types](../systems/foundations/data-driven-types.md), [identifiers-and-registries](../systems/foundations/identifiers-and-registries.md), [tags](../systems/foundations/tags.md) |
| `Marker` | [non-living-damage](../reference/non-living-damage.md), [damage-and-death](../systems/entities/damage-and-death.md), [what-the-client-is-told](../systems/networking/what-the-client-is-told.md) |
| `MarsagliaPolarGaussian` | [math-and-primitives](../reference/math-and-primitives.md) |
| `Material` | [naming-drift](../reference/naming-drift.md), [models-and-atlases](../systems/rendering/models-and-atlases.md) |
| `MaterialBaker` | [models-and-atlases](../systems/rendering/models-and-atlases.md) |
| `MaterialRuleList` | [terrain](../systems/worldgen/terrain.md) |
| `MegaJungleFoliagePlacer` | [trees](../systems/worldgen/trees.md) |
| `MegaJungleTrunkPlacer` | [trees](../systems/worldgen/trees.md) |
| `MegaPineFoliagePlacer` | [trees](../systems/worldgen/trees.md) |
| `MemoryMap` | [data-driven-types](../systems/foundations/data-driven-types.md) |
| `MemoryModuleType` | [ai-goals-and-brains](../systems/entities/ai-goals-and-brains.md), [game-events-and-vibrations](../systems/world/game-events-and-vibrations.md), [points-of-interest](../systems/world/points-of-interest.md) |
| `MemoryReserve` | [starting-a-server](../systems/server/starting-a-server.md) |
| `MemoryServerHandshakePacketListenerImpl` | [protocol-phases](../systems/networking/protocol-phases.md) |
| `MemorySlot` | [ai-goals-and-brains](../systems/entities/ai-goals-and-brains.md) |
| `MenuProvider` | [containers-and-menus](../systems/items/containers-and-menus.md) |
| `MenuScreens` | [gui-and-screens](../systems/client/gui-and-screens.md), [containers-and-menus](../systems/items/containers-and-menus.md) |
| `MenuType` | [hierarchy](../maps/hierarchy.md), [gui-and-screens](../systems/client/gui-and-screens.md), [identifiers-and-registries](../systems/foundations/identifiers-and-registries.md), [containers-and-menus](../systems/items/containers-and-menus.md) |
| `MerchantOffers` | [contexts-and-predicates](../systems/items/contexts-and-predicates.md) |
| `MeshData` | [naming-drift](../reference/naming-drift.md), [blaze3d](../systems/rendering/blaze3d.md) |
| `MeshDefinition` | [entity-rendering](../systems/rendering/entity-rendering.md) |
| `MessageArgument` | [glossary](../reference/glossary.md), [brigadier-and-commands](../systems/commands/brigadier-and-commands.md), [entity-selectors](../systems/commands/entity-selectors.md), [text-components](../systems/foundations/text-components.md), [chat-and-signing](../systems/networking/chat-and-signing.md) |
| `MessageSignature` | [chat-and-signing](../systems/networking/chat-and-signing.md) |
| `MessageSignatureCache` | [chat-and-signing](../systems/networking/chat-and-signing.md) |
| `MetricCategory` | [what-this-book-skips](../systems/anatomy/what-this-book-skips.md) |
| `MetricsRecorder` | [the-client-loop](../systems/client/the-client-loop.md), [server-tick](../systems/server/server-tick.md) |
| `MinecartCollisionContext` | [math-and-primitives](../reference/math-and-primitives.md) |
| `MinecartTNT` | [non-living-damage](../reference/non-living-damage.md), [damage-and-death](../systems/entities/damage-and-death.md) |
| `Minecraft` | [biggest](../maps/biggest.md), [fanin](../maps/fanin.md), [packages](../maps/packages.md), [glossary](../reference/glossary.md), [hud-elements](../reference/hud-elements.md), [naming-drift](../reference/naming-drift.md), [threads](../reference/threads.md), [anatomy](../systems/anatomy/anatomy.md), [what-this-book-skips](../systems/anatomy/what-this-book-skips.md), [block-breaking](../systems/blocks/block-breaking.md), [block-entities](../systems/blocks/block-entities.md), [block-interaction](../systems/blocks/block-interaction.md), [X · The client](../systems/client/README.md), [debugging-the-running-game](../systems/client/debugging-the-running-game.md), [gui-and-screens](../systems/client/gui-and-screens.md), [hud](../systems/client/hud.md), [input-and-keybinds](../systems/client/input-and-keybinds.md), [options](../systems/client/options.md), [prediction-and-acks](../systems/client/prediction-and-acks.md), [sound-engine](../systems/client/sound-engine.md), [the-client-level](../systems/client/the-client-level.md), [the-client-loop](../systems/client/the-client-loop.md), [what-makes-a-sound](../systems/client/what-makes-a-sound.md), [resource-system](../systems/foundations/resource-system.md), [text-components](../systems/foundations/text-components.md), [using-an-item](../systems/items/using-an-item.md), [chat-and-signing](../systems/networking/chat-and-signing.md), [protocol-phases](../systems/networking/protocol-phases.md), [the-connection](../systems/networking/the-connection.md), [input-to-movement](../systems/player/input-to-movement.md), [player-anatomy](../systems/player/player-anatomy.md), [the-spear](../systems/player/the-spear.md), [the-sword-swing](../systems/player/the-sword-swing.md), [the-two-phase-tick](../systems/player/the-two-phase-tick.md), [XI · Rendering](../systems/rendering/README.md), [blaze3d](../systems/rendering/blaze3d.md), [entity-rendering](../systems/rendering/entity-rendering.md), [models-and-atlases](../systems/rendering/models-and-atlases.md), [particles](../systems/rendering/particles.md), [post-processing](../systems/rendering/post-processing.md), [the-frame](../systems/rendering/the-frame.md), [the-window](../systems/rendering/the-window.md), [how-a-server-dies](../systems/server/how-a-server-dies.md), [server-tick](../systems/server/server-tick.md), [starting-a-server](../systems/server/starting-a-server.md), [environment-attributes-and-timelines](../systems/world/environment-attributes-and-timelines.md), [lighting](../systems/world/lighting.md), [creating-a-world](../systems/worldgen/creating-a-world.md) |
| `MinecraftServer` | [introduction](../introduction.md), [lectures](../lectures.md), [biggest](../maps/biggest.md), [packages](../maps/packages.md), [glossary](../reference/glossary.md), [level-data-and-rules](../reference/level-data-and-rules.md), [naming-drift](../reference/naming-drift.md), [threads](../reference/threads.md), [anatomy](../systems/anatomy/anatomy.md), [what-this-book-skips](../systems/anatomy/what-this-book-skips.md), [block-breaking](../systems/blocks/block-breaking.md), [block-interaction](../systems/blocks/block-interaction.md), [pistons-and-block-events](../systems/blocks/pistons-and-block-events.md), [debugging-the-running-game](../systems/client/debugging-the-running-game.md), [XIII · Commands and data packs](../systems/commands/README.md), [advancements](../systems/commands/advancements.md), [brigadier-and-commands](../systems/commands/brigadier-and-commands.md), [dialogs](../systems/commands/dialogs.md), [functions-and-macros](../systems/commands/functions-and-macros.md), [game-tests](../systems/commands/game-tests.md), [permissions](../systems/commands/permissions.md), [scoreboard-and-data](../systems/commands/scoreboard-and-data.md), [synched-entity-data](../systems/entities/synched-entity-data.md), [data-driven-types](../systems/foundations/data-driven-types.md), [identifiers-and-registries](../systems/foundations/identifiers-and-registries.md), [resource-system](../systems/foundations/resource-system.md), [tags](../systems/foundations/tags.md), [text-components](../systems/foundations/text-components.md), [contexts-and-predicates](../systems/items/contexts-and-predicates.md), [loot-tables](../systems/items/loot-tables.md), [recipes](../systems/items/recipes.md), [chat-and-signing](../systems/networking/chat-and-signing.md), [protocol-phases](../systems/networking/protocol-phases.md), [the-connection](../systems/networking/the-connection.md), [what-the-client-is-told](../systems/networking/what-the-client-is-told.md), [input-to-movement](../systems/player/input-to-movement.md), [the-sword-swing](../systems/player/the-sword-swing.md), [the-two-phase-tick](../systems/player/the-two-phase-tick.md), [how-a-server-dies](../systems/server/how-a-server-dies.md), [players-and-sessions](../systems/server/players-and-sessions.md), [server-level-tick](../systems/server/server-level-tick.md), [server-tick](../systems/server/server-tick.md), [starting-a-server](../systems/server/starting-a-server.md), [chunk-generation-pipeline](../systems/world/chunk-generation-pipeline.md), [chunk-storage](../systems/world/chunk-storage.md), [environment-attributes-and-timelines](../systems/world/environment-attributes-and-timelines.md), [lighting](../systems/world/lighting.md), [points-of-interest](../systems/world/points-of-interest.md), [tickets-and-loading](../systems/world/tickets-and-loading.md), [biomes](../systems/worldgen/biomes.md), [creating-a-world](../systems/worldgen/creating-a-world.md) |
| `MinecraftServerGui` | [threads](../reference/threads.md) |
| `MinecraftServerStateServiceImpl` | [how-a-server-dies](../systems/server/how-a-server-dies.md) |
| `MineshaftPieces` | [hand-built-structures](../systems/worldgen/hand-built-structures.md) |
| `MineshaftStructure` | [hand-built-structures](../systems/worldgen/hand-built-structures.md) |
| `MinMaxBounds` | [advancements](../systems/commands/advancements.md), [entity-selectors](../systems/commands/entity-selectors.md) |
| `MipmapGenerator` | [models-and-atlases](../systems/rendering/models-and-atlases.md) |
| `MipmapStrategy` | [models-and-atlases](../systems/rendering/models-and-atlases.md) |
| `Mirror` | [math-and-primitives](../reference/math-and-primitives.md) |
| `MiscOverworldFeatures` | [what-this-book-skips](../systems/anatomy/what-this-book-skips.md) |
| `MissingTextureAtlasSprite` | [models-and-atlases](../systems/rendering/models-and-atlases.md) |
| `Mob` | [biggest](../maps/biggest.md), [hierarchy](../maps/hierarchy.md), [non-living-damage](../reference/non-living-damage.md), [debugging-the-running-game](../systems/client/debugging-the-running-game.md), [scoreboard-and-data](../systems/commands/scoreboard-and-data.md), [ai-goals-and-brains](../systems/entities/ai-goals-and-brains.md), [attributes](../systems/entities/attributes.md), [authority](../systems/entities/authority.md), [damage-and-death](../systems/entities/damage-and-death.md), [entity-anatomy](../systems/entities/entity-anatomy.md), [entity-lifecycle](../systems/entities/entity-lifecycle.md), [movement-and-collision](../systems/entities/movement-and-collision.md), [pathfinding](../systems/entities/pathfinding.md), [synched-entity-data](../systems/entities/synched-entity-data.md), [contexts-and-predicates](../systems/items/contexts-and-predicates.md), [enchanting](../systems/items/enchanting.md), [enchantments](../systems/items/enchantments.md), [what-the-client-is-told](../systems/networking/what-the-client-is-told.md), [the-sword-swing](../systems/player/the-sword-swing.md) |
| `MobCategory` | [attributes](../systems/entities/attributes.md), [entity-anatomy](../systems/entities/entity-anatomy.md), [entity-lifecycle](../systems/entities/entity-lifecycle.md), [server-level-tick](../systems/server/server-level-tick.md) |
| `MobEffect` | [hierarchy](../maps/hierarchy.md), [naming-drift](../reference/naming-drift.md), [attributes](../systems/entities/attributes.md), [identifiers-and-registries](../systems/foundations/identifiers-and-registries.md), [status-effects](../systems/player/status-effects.md) |
| `MobEffectCategory` | [status-effects](../systems/player/status-effects.md) |
| `MobEffectFogEnvironment` | [lightmap-fog-and-sky](../systems/rendering/lightmap-fog-and-sky.md) |
| `MobEffectInstance` | [hud-elements](../reference/hud-elements.md), [status-effects](../systems/player/status-effects.md) |
| `MobEffects` | [naming-drift](../reference/naming-drift.md), [attributes](../systems/entities/attributes.md), [damage-and-death](../systems/entities/damage-and-death.md), [movement-and-collision](../systems/entities/movement-and-collision.md), [input-to-movement](../systems/player/input-to-movement.md), [status-effects](../systems/player/status-effects.md), [the-sword-swing](../systems/player/the-sword-swing.md) |
| `MobEffectUtil` | [block-breaking](../systems/blocks/block-breaking.md), [status-effects](../systems/player/status-effects.md) |
| `MobRenderer` | [entity-rendering](../systems/rendering/entity-rendering.md) |
| `MobSpawnSettings` | [biomes](../systems/worldgen/biomes.md) |
| `Model` | [submit-phases](../reference/submit-phases.md), [entity-rendering](../systems/rendering/entity-rendering.md) |
| `ModelBakery` | [models-and-atlases](../systems/rendering/models-and-atlases.md) |
| `ModelBlockRenderer` | [naming-drift](../reference/naming-drift.md), [submit-phases](../reference/submit-phases.md) |
| `ModelDiscovery` | [models-and-atlases](../systems/rendering/models-and-atlases.md) |
| `ModelFeatureRenderer` | [submit-phases](../reference/submit-phases.md), [entity-rendering](../systems/rendering/entity-rendering.md) |
| `ModelGroupCollector` | [models-and-atlases](../systems/rendering/models-and-atlases.md) |
| `ModelLayerLocation` | [block-entity-rendering](../systems/rendering/block-entity-rendering.md), [entity-rendering](../systems/rendering/entity-rendering.md) |
| `ModelLayers` | [entity-rendering](../systems/rendering/entity-rendering.md) |
| `ModelManager` | [blocks-and-states](../systems/blocks/blocks-and-states.md), [resource-system](../systems/foundations/resource-system.md), [block-entity-rendering](../systems/rendering/block-entity-rendering.md), [models-and-atlases](../systems/rendering/models-and-atlases.md), [section-meshing](../systems/rendering/section-meshing.md) |
| `ModelPart` | [submit-phases](../reference/submit-phases.md), [entity-rendering](../systems/rendering/entity-rendering.md) |
| `ModelProvider` | [biggest](../maps/biggest.md) |
| `ModelState` | [models-and-atlases](../systems/rendering/models-and-atlases.md) |
| `Monitor` | [naming-drift](../reference/naming-drift.md), [the-window](../systems/rendering/the-window.md) |
| `MonitoredLocalFrameDecoder` | [the-connection](../systems/networking/the-connection.md) |
| `MonitorManager` | [naming-drift](../reference/naming-drift.md), [the-window](../systems/rendering/the-window.md) |
| `Monster` | [attributes](../systems/entities/attributes.md), [damage-and-death](../systems/entities/damage-and-death.md), [entity-anatomy](../systems/entities/entity-anatomy.md), [entity-lifecycle](../systems/entities/entity-lifecycle.md) |
| `MonsterRoomFeature` | [loot-tables](../systems/items/loot-tables.md) |
| `MoonPhase` | [lightmap-fog-and-sky](../systems/rendering/lightmap-fog-and-sky.md), [environment-attributes-and-timelines](../systems/world/environment-attributes-and-timelines.md) |
| `MouseButtonEvent` | [naming-drift](../reference/naming-drift.md), [input-and-keybinds](../systems/client/input-and-keybinds.md) |
| `MouseHandler` | [packages](../maps/packages.md), [glossary](../reference/glossary.md), [input-and-keybinds](../systems/client/input-and-keybinds.md), [input-to-movement](../systems/player/input-to-movement.md), [the-window](../systems/rendering/the-window.md) |
| `MoveControl` | [pathfinding](../systems/entities/pathfinding.md) |
| `MoverType` | [authority](../systems/entities/authority.md), [movement-and-collision](../systems/entities/movement-and-collision.md), [input-to-movement](../systems/player/input-to-movement.md) |
| `MoveThroughVillageGoal` | [ai-goals-and-brains](../systems/entities/ai-goals-and-brains.md) |
| `MoveToTargetSink` | [ai-goals-and-brains](../systems/entities/ai-goals-and-brains.md), [pathfinding](../systems/entities/pathfinding.md) |
| `MovingBlockFeatureRenderer` | [submit-phases](../reference/submit-phases.md) |
| `MovingBlockRenderState` | [block-entity-rendering](../systems/rendering/block-entity-rendering.md) |
| `MovingPistonBlock` | [pistons-and-block-events](../systems/blocks/pistons-and-block-events.md) |
| `MsgCommand` | [entity-selectors](../systems/commands/entity-selectors.md) |
| `Mth` | [fanin](../maps/fanin.md), [packages](../maps/packages.md), [math-and-primitives](../reference/math-and-primitives.md), [damage-and-death](../systems/entities/damage-and-death.md), [entity-anatomy](../systems/entities/entity-anatomy.md), [movement-and-collision](../systems/entities/movement-and-collision.md), [packets-and-stream-codecs](../systems/networking/packets-and-stream-codecs.md), [chunk-anatomy](../systems/world/chunk-anatomy.md) |
| `MultiActionDialog` | [dialogs](../systems/commands/dialogs.md) |
| `MultiLineLabel` | [gui-and-screens](../systems/client/gui-and-screens.md) |
| `MultiNoiseBiomeSource` | [biomes](../systems/worldgen/biomes.md) |
| `MultiNoiseBiomeSourceParameterList` | [naming-drift](../reference/naming-drift.md) |
| `MultiPackResourceManager` | [resource-system](../systems/foundations/resource-system.md) |
| `MultiPartModel` | [models-and-atlases](../systems/rendering/models-and-atlases.md) |
| `MultiPlayerGameMode` | [naming-drift](../reference/naming-drift.md), [anatomy](../systems/anatomy/anatomy.md), [block-breaking](../systems/blocks/block-breaking.md), [block-interaction](../systems/blocks/block-interaction.md), [gui-and-screens](../systems/client/gui-and-screens.md), [prediction-and-acks](../systems/client/prediction-and-acks.md), [synched-entity-data](../systems/entities/synched-entity-data.md), [codecs-nbt-json](../systems/foundations/codecs-nbt-json.md), [containers-and-menus](../systems/items/containers-and-menus.md), [enchantments](../systems/items/enchantments.md), [using-an-item](../systems/items/using-an-item.md), [the-connection](../systems/networking/the-connection.md), [player-anatomy](../systems/player/player-anatomy.md), [the-spear](../systems/player/the-spear.md), [the-sword-swing](../systems/player/the-sword-swing.md), [section-meshing](../systems/rendering/section-meshing.md), [fluids](../systems/world/fluids.md), [lighting](../systems/world/lighting.md) |
| `MultipleTestTracker` | [game-tests](../systems/commands/game-tests.md) |
| `MultiplyValue` | [enchantments](../systems/items/enchantments.md) |
| `MultiVariant` | [naming-drift](../reference/naming-drift.md) |
| `MushroomCow` | [synched-entity-data](../systems/entities/synched-entity-data.md) |
| `MusicManager` | [sound-engine](../systems/client/sound-engine.md), [what-makes-a-sound](../systems/client/what-makes-a-sound.md) |
| `MutableComponent` | [text-and-fonts](../systems/client/text-and-fonts.md), [text-components](../systems/foundations/text-components.md) |
| `MyceliumBlock` | [what-this-book-skips](../systems/anatomy/what-this-book-skips.md) |
| `Nameable` | [entity-selectors](../systems/commands/entity-selectors.md), [entity-anatomy](../systems/entities/entity-anatomy.md) |
| `NameAndId` | [naming-drift](../reference/naming-drift.md), [players-and-sessions](../systems/server/players-and-sessions.md) |
| `NamedRule` | [brigadier-and-commands](../systems/commands/brigadier-and-commands.md) |
| `NameTagFeatureRenderer` | [submit-phases](../reference/submit-phases.md), [text-and-fonts](../systems/client/text-and-fonts.md) |
| `NativeImage` | [XI · Rendering](../systems/rendering/README.md), [lightmap-fog-and-sky](../systems/rendering/lightmap-fog-and-sky.md), [the-window](../systems/rendering/the-window.md) |
| `NativeLibrariesBootstrap` | [anatomy](../systems/anatomy/anatomy.md), [sound-engine](../systems/client/sound-engine.md), [the-window](../systems/rendering/the-window.md) |
| `NaturalSpawner` | [entity-anatomy](../systems/entities/entity-anatomy.md), [entity-lifecycle](../systems/entities/entity-lifecycle.md), [what-the-client-is-told](../systems/networking/what-the-client-is-told.md), [server-level-tick](../systems/server/server-level-tick.md), [biomes](../systems/worldgen/biomes.md), [structure-placement](../systems/worldgen/structure-placement.md) |
| `NbtAccounter` | [codecs-nbt-json](../systems/foundations/codecs-nbt-json.md), [text-components](../systems/foundations/text-components.md), [packets-and-stream-codecs](../systems/networking/packets-and-stream-codecs.md) |
| `NbtContents` | [scoreboard-and-data](../systems/commands/scoreboard-and-data.md), [text-components](../systems/foundations/text-components.md) |
| `NbtException` | [codecs-nbt-json](../systems/foundations/codecs-nbt-json.md) |
| `NbtIo` | [glossary](../reference/glossary.md), [codecs-nbt-json](../systems/foundations/codecs-nbt-json.md), [how-a-server-dies](../systems/server/how-a-server-dies.md) |
| `NbtOps` | [glossary](../reference/glossary.md), [codecs-nbt-json](../systems/foundations/codecs-nbt-json.md), [text-components](../systems/foundations/text-components.md), [packets-and-stream-codecs](../systems/networking/packets-and-stream-codecs.md) |
| `NbtPathArgument` | [scoreboard-and-data](../systems/commands/scoreboard-and-data.md), [text-components](../systems/foundations/text-components.md) |
| `NbtProvider` | [data-driven-types](../systems/foundations/data-driven-types.md) |
| `NbtProviders` | [data-driven-types](../systems/foundations/data-driven-types.md) |
| `NbtTagArgument` | [brigadier-and-commands](../systems/commands/brigadier-and-commands.md) |
| `NbtUtils` | [entity-selectors](../systems/commands/entity-selectors.md), [codecs-nbt-json](../systems/foundations/codecs-nbt-json.md) |
| `NearestAttackableTargetGoal` | [ai-goals-and-brains](../systems/entities/ai-goals-and-brains.md) |
| `NearestBedSensor` | [points-of-interest](../systems/world/points-of-interest.md) |
| `NeighborUpdater` | [block-update-flags](../reference/block-update-flags.md), [block-interaction](../systems/blocks/block-interaction.md), [blocks-and-states](../systems/blocks/blocks-and-states.md), [signal-and-dust](../systems/blocks/signal-and-dust.md) |
| `NestedLootTable` | [naming-drift](../reference/naming-drift.md), [loot-tables](../systems/items/loot-tables.md) |
| `NetherFortressPieces` | [hand-built-structures](../systems/worldgen/hand-built-structures.md) |
| `NetherFossilPieces` | [hand-built-structures](../systems/worldgen/hand-built-structures.md) |
| `NetherWorldCarver` | [terrain](../systems/worldgen/terrain.md) |
| `NetworkRegistryLoadTask` | [identifiers-and-registries](../systems/foundations/identifiers-and-registries.md) |
| `NeutralMob` | [entity-anatomy](../systems/entities/entity-anatomy.md) |
| `NewMinecartBehavior` | [input-to-movement](../systems/player/input-to-movement.md) |
| `NoDataSpecialModelRenderer` | [block-entity-rendering](../systems/rendering/block-entity-rendering.md) |
| `Node` | [pathfinding](../systems/entities/pathfinding.md) |
| `NodeEvaluator` | [glossary](../reference/glossary.md), [pathfinding](../systems/entities/pathfinding.md) |
| `NoiseBasedChunkGenerator` | [chunk-anatomy](../systems/world/chunk-anatomy.md), [chunk-generation-pipeline](../systems/world/chunk-generation-pipeline.md), [biomes](../systems/worldgen/biomes.md), [blending](../systems/worldgen/blending.md), [density-functions](../systems/worldgen/density-functions.md), [terrain](../systems/worldgen/terrain.md) |
| `NoiseBasedCountPlacement` | [features-and-placement](../systems/worldgen/features-and-placement.md) |
| `NoiseChunk` | [density-function-nodes](../reference/density-function-nodes.md), [biomes](../systems/worldgen/biomes.md), [blending](../systems/worldgen/blending.md), [density-functions](../systems/worldgen/density-functions.md), [structure-placement](../systems/worldgen/structure-placement.md), [terrain](../systems/worldgen/terrain.md) |
| `NoiseGeneratorSettings` | [math-and-primitives](../reference/math-and-primitives.md), [what-this-book-skips](../systems/anatomy/what-this-book-skips.md), [terrain](../systems/worldgen/terrain.md) |
| `NoiseRouter` | [glossary](../reference/glossary.md), [density-functions](../systems/worldgen/density-functions.md), [terrain](../systems/worldgen/terrain.md) |
| `NoiseRouterData` | [biggest](../maps/biggest.md), [what-this-book-skips](../systems/anatomy/what-this-book-skips.md), [blending](../systems/worldgen/blending.md), [density-functions](../systems/worldgen/density-functions.md) |
| `Noises` | [density-functions](../systems/worldgen/density-functions.md) |
| `NoiseSettings` | [terrain](../systems/worldgen/terrain.md) |
| `NoiseThresholdCountPlacement` | [features-and-placement](../systems/worldgen/features-and-placement.md) |
| `NoneFeatureConfiguration` | [features-and-placement](../systems/worldgen/features-and-placement.md) |
| `NoRenderParticle` | [particles](../systems/rendering/particles.md) |
| `NoRenderParticleGroup` | [particles](../systems/rendering/particles.md) |
| `NormalCraftingRecipe` | [recipes](../systems/items/recipes.md) |
| `NormalNoise` | [density-function-nodes](../reference/density-function-nodes.md), [density-functions](../systems/worldgen/density-functions.md) |
| `NoteBlock` | [pistons-and-block-events](../systems/blocks/pistons-and-block-events.md) |
| `NoticeDialog` | [dialogs](../systems/commands/dialogs.md) |
| `NotificationManager` | [how-a-server-dies](../systems/server/how-a-server-dies.md), [server-tick](../systems/server/server-tick.md), [starting-a-server](../systems/server/starting-a-server.md) |
| `NullOps` | [codecs-nbt-json](../systems/foundations/codecs-nbt-json.md), [packets-and-stream-codecs](../systems/networking/packets-and-stream-codecs.md) |
| `NumberFormat` | [scoreboard-and-data](../systems/commands/scoreboard-and-data.md), [data-driven-types](../systems/foundations/data-driven-types.md) |
| `NumberFormatType` | [data-driven-types](../systems/foundations/data-driven-types.md) |
| `NumberFormatTypes` | [scoreboard-and-data](../systems/commands/scoreboard-and-data.md) |
| `NumberProvider` | [data-driven-types](../systems/foundations/data-driven-types.md), [contexts-and-predicates](../systems/items/contexts-and-predicates.md) |
| `NumberProviders` | [data-driven-types](../systems/foundations/data-driven-types.md), [contexts-and-predicates](../systems/items/contexts-and-predicates.md) |
| `NumberRangeInput` | [dialogs](../systems/commands/dialogs.md) |
| `NumericTag` | [codecs-nbt-json](../systems/foundations/codecs-nbt-json.md) |
| `ObjectContents` | [text-components](../systems/foundations/text-components.md) |
| `ObjectInfo` | [text-components](../systems/foundations/text-components.md) |
| `Objective` | [scoreboard-and-data](../systems/commands/scoreboard-and-data.md), [data-driven-types](../systems/foundations/data-driven-types.md) |
| `ObjectiveArgument` | [scoreboard-and-data](../systems/commands/scoreboard-and-data.md) |
| `ObjectiveCriteria` | [glossary](../reference/glossary.md), [what-this-book-skips](../systems/anatomy/what-this-book-skips.md), [scoreboard-and-data](../systems/commands/scoreboard-and-data.md) |
| `ObjectSelectionList` | [gui-and-screens](../systems/client/gui-and-screens.md) |
| `ObserverBlock` | [diodes-and-observers](../systems/blocks/diodes-and-observers.md), [scheduled-ticks](../systems/world/scheduled-ticks.md) |
| `OceanMonumentPieces` | [biggest](../maps/biggest.md), [hand-built-structures](../systems/worldgen/hand-built-structures.md) |
| `OceanMonumentStructure` | [hand-built-structures](../systems/worldgen/hand-built-structures.md) |
| `OceanRuinPieces` | [hand-built-structures](../systems/worldgen/hand-built-structures.md) |
| `OctahedralGroup` | [math-and-primitives](../reference/math-and-primitives.md) |
| `Octree` | [visibility-and-the-frame-graph](../systems/rendering/visibility-and-the-frame-graph.md) |
| `OldMinecartBehavior` | [input-to-movement](../systems/player/input-to-movement.md) |
| `OldUsersConverter` | [starting-a-server](../systems/server/starting-a-server.md) |
| `OminousBottleAmplifier` | [hunger-and-experience](../systems/player/hunger-and-experience.md) |
| `OminousItemSpawner` | [non-living-damage](../reference/non-living-damage.md), [damage-and-death](../systems/entities/damage-and-death.md) |
| `OptimizeWorldScreen` | [creating-a-world](../systems/worldgen/creating-a-world.md) |
| `OptionInstance` | [options](../systems/client/options.md), [the-client-loop](../systems/client/the-client-loop.md) |
| `Options` | [biggest](../maps/biggest.md), [packages](../maps/packages.md), [hud-elements](../reference/hud-elements.md), [naming-drift](../reference/naming-drift.md), [anatomy](../systems/anatomy/anatomy.md), [options](../systems/client/options.md), [sound-engine](../systems/client/sound-engine.md), [the-client-level](../systems/client/the-client-level.md), [the-client-loop](../systems/client/the-client-loop.md), [resource-system](../systems/foundations/resource-system.md), [chat-and-signing](../systems/networking/chat-and-signing.md), [input-to-movement](../systems/player/input-to-movement.md), [the-sword-swing](../systems/player/the-sword-swing.md), [lightmap-fog-and-sky](../systems/rendering/lightmap-fog-and-sky.md), [post-processing](../systems/rendering/post-processing.md), [the-window](../systems/rendering/the-window.md), [chunk-storage](../systems/world/chunk-storage.md) |
| `OptionsList` | [gui-and-screens](../systems/client/gui-and-screens.md) |
| `OptionsRenderState` | [post-processing](../systems/rendering/post-processing.md) |
| `OrderedSubmitNodeCollector` | [entity-rendering](../systems/rendering/entity-rendering.md) |
| `OreFeature` | [chunk-anatomy](../systems/world/chunk-anatomy.md), [chunk-generation-pipeline](../systems/world/chunk-generation-pipeline.md) |
| `OreVeinifier` | [terrain](../systems/worldgen/terrain.md) |
| `Orientation` | [signal-and-dust](../systems/blocks/signal-and-dust.md), [debugging-the-running-game](../systems/client/debugging-the-running-game.md) |
| `OutgoingChatMessage` | [chat-and-signing](../systems/networking/chat-and-signing.md) |
| `OutputTarget` | [blaze3d](../systems/rendering/blaze3d.md) |
| `Overlay` | [gui-and-screens](../systems/client/gui-and-screens.md) |
| `OverlayMetadataSection` | [resource-system](../systems/foundations/resource-system.md) |
| `OverlayTexture` | [entity-rendering](../systems/rendering/entity-rendering.md) |
| `OversizedItemRenderState` | [the-gui-render-tree](../systems/client/the-gui-render-tree.md) |
| `OverworldBiomeBuilder` | [biomes](../systems/worldgen/biomes.md) |
| `OwnableEntity` | [entity-anatomy](../systems/entities/entity-anatomy.md) |
| `Pack` | [resource-system](../systems/foundations/resource-system.md), [creating-a-world](../systems/worldgen/creating-a-world.md) |
| `PackCompatibility` | [resource-system](../systems/foundations/resource-system.md) |
| `PackDetector` | [resource-system](../systems/foundations/resource-system.md) |
| `Packet` | [fanin](../maps/fanin.md), [hierarchy](../maps/hierarchy.md), [threads](../reference/threads.md), [packets-and-stream-codecs](../systems/networking/packets-and-stream-codecs.md), [the-connection](../systems/networking/the-connection.md) |
| `PacketBundlePacker` | [packets-and-stream-codecs](../systems/networking/packets-and-stream-codecs.md), [the-connection](../systems/networking/the-connection.md) |
| `PacketBundleUnpacker` | [packets-and-stream-codecs](../systems/networking/packets-and-stream-codecs.md), [the-connection](../systems/networking/the-connection.md) |
| `PacketDecoder` | [text-components](../systems/foundations/text-components.md), [packets-and-stream-codecs](../systems/networking/packets-and-stream-codecs.md), [the-connection](../systems/networking/the-connection.md) |
| `PacketEncoder` | [codecs-nbt-json](../systems/foundations/codecs-nbt-json.md), [text-components](../systems/foundations/text-components.md), [packets-and-stream-codecs](../systems/networking/packets-and-stream-codecs.md), [the-connection](../systems/networking/the-connection.md) |
| `PacketFlow` | [packets-and-stream-codecs](../systems/networking/packets-and-stream-codecs.md) |
| `PacketListener` | [anatomy](../systems/anatomy/anatomy.md), [protocol-phases](../systems/networking/protocol-phases.md), [the-connection](../systems/networking/the-connection.md), [server-tick](../systems/server/server-tick.md) |
| `PacketProcessor` | [naming-drift](../reference/naming-drift.md), [anatomy](../systems/anatomy/anatomy.md), [the-client-loop](../systems/client/the-client-loop.md), [dialogs](../systems/commands/dialogs.md), [synched-entity-data](../systems/entities/synched-entity-data.md), [containers-and-menus](../systems/items/containers-and-menus.md), [the-connection](../systems/networking/the-connection.md), [input-to-movement](../systems/player/input-to-movement.md), [the-sword-swing](../systems/player/the-sword-swing.md), [how-a-server-dies](../systems/server/how-a-server-dies.md), [server-tick](../systems/server/server-tick.md), [starting-a-server](../systems/server/starting-a-server.md) |
| `PacketReport` | [packets-and-stream-codecs](../systems/networking/packets-and-stream-codecs.md) |
| `PacketSendListener` | [text-components](../systems/foundations/text-components.md), [the-connection](../systems/networking/the-connection.md) |
| `PacketType` | [fanin](../maps/fanin.md), [glossary](../reference/glossary.md), [packets-and-stream-codecs](../systems/networking/packets-and-stream-codecs.md) |
| `PacketUtils` | [naming-drift](../reference/naming-drift.md), [threads](../reference/threads.md), [anatomy](../systems/anatomy/anatomy.md), [brigadier-and-commands](../systems/commands/brigadier-and-commands.md), [synched-entity-data](../systems/entities/synched-entity-data.md), [protocol-phases](../systems/networking/protocol-phases.md), [the-connection](../systems/networking/the-connection.md), [server-tick](../systems/server/server-tick.md) |
| `PackFormat` | [resource-system](../systems/foundations/resource-system.md) |
| `PackLocationInfo` | [resource-system](../systems/foundations/resource-system.md), [protocol-phases](../systems/networking/protocol-phases.md) |
| `PackMetadataSection` | [resource-system](../systems/foundations/resource-system.md) |
| `PackRepository` | [resource-system](../systems/foundations/resource-system.md) |
| `PackResources` | [resource-system](../systems/foundations/resource-system.md) |
| `PackSelectionConfig` | [resource-system](../systems/foundations/resource-system.md) |
| `PackSource` | [glossary](../reference/glossary.md), [resource-system](../systems/foundations/resource-system.md), [creating-a-world](../systems/worldgen/creating-a-world.md) |
| `PackType` | [resource-system](../systems/foundations/resource-system.md) |
| `PaleMossDecorator` | [trees](../systems/worldgen/trees.md) |
| `Palette` | [chunk-anatomy](../systems/world/chunk-anatomy.md) |
| `PalettedContainer` | [naming-drift](../reference/naming-drift.md), [section-meshing](../systems/rendering/section-meshing.md), [chunk-anatomy](../systems/world/chunk-anatomy.md), [biomes](../systems/worldgen/biomes.md) |
| `PalettedContainerFactory` | [chunk-anatomy](../systems/world/chunk-anatomy.md) |
| `PalettedContainerRO` | [chunk-anatomy](../systems/world/chunk-anatomy.md) |
| `PalettedPermutations` | [models-and-atlases](../systems/rendering/models-and-atlases.md) |
| `PaletteResize` | [chunk-anatomy](../systems/world/chunk-anatomy.md) |
| `PanoramaRenderState` | [the-gui-render-tree](../systems/client/the-gui-render-tree.md) |
| `Parrot` | [tags](../systems/foundations/tags.md) |
| `ParsedTemplate` | [dialogs](../systems/commands/dialogs.md) |
| `ParserBasedArgument` | [naming-drift](../reference/naming-drift.md) |
| `PartDefinition` | [entity-rendering](../systems/rendering/entity-rendering.md) |
| `Particle` | [particles](../systems/rendering/particles.md) |
| `ParticleEngine` | [math-and-primitives](../reference/math-and-primitives.md), [the-client-level](../systems/client/the-client-level.md), [the-client-loop](../systems/client/the-client-loop.md), [particles](../systems/rendering/particles.md) |
| `ParticleGroup` | [naming-drift](../reference/naming-drift.md), [particles](../systems/rendering/particles.md) |
| `ParticleGroupRenderState` | [particles](../systems/rendering/particles.md) |
| `ParticleLimit` | [naming-drift](../reference/naming-drift.md), [particles](../systems/rendering/particles.md) |
| `ParticleOptions` | [data-driven-types](../systems/foundations/data-driven-types.md), [status-effects](../systems/player/status-effects.md) |
| `ParticleRenderType` | [particles](../systems/rendering/particles.md) |
| `ParticleResources` | [resource-system](../systems/foundations/resource-system.md), [particles](../systems/rendering/particles.md) |
| `ParticlesRenderState` | [particles](../systems/rendering/particles.md) |
| `ParticleType` | [data-driven-types](../systems/foundations/data-driven-types.md), [particles](../systems/rendering/particles.md) |
| `ParticleTypes` | [non-living-damage](../reference/non-living-damage.md), [the-sword-swing](../systems/player/the-sword-swing.md) |
| `PartPose` | [entity-rendering](../systems/rendering/entity-rendering.md) |
| `PatchedDataComponentMap` | [naming-drift](../reference/naming-drift.md), [data-components](../systems/foundations/data-components.md), [items-and-stacks](../systems/items/items-and-stacks.md) |
| `Path` | [debugging-the-running-game](../systems/client/debugging-the-running-game.md), [ai-goals-and-brains](../systems/entities/ai-goals-and-brains.md), [pathfinding](../systems/entities/pathfinding.md), [points-of-interest](../systems/world/points-of-interest.md) |
| `PathFinder` | [glossary](../reference/glossary.md), [pathfinding](../systems/entities/pathfinding.md) |
| `PathfinderMob` | [hierarchy](../maps/hierarchy.md), [ai-goals-and-brains](../systems/entities/ai-goals-and-brains.md), [entity-anatomy](../systems/entities/entity-anatomy.md), [synched-entity-data](../systems/entities/synched-entity-data.md) |
| `PathfindingContext` | [pathfinding](../systems/entities/pathfinding.md) |
| `PathNavigation` | [block-interaction](../systems/blocks/block-interaction.md), [attributes](../systems/entities/attributes.md), [entity-anatomy](../systems/entities/entity-anatomy.md), [pathfinding](../systems/entities/pathfinding.md), [points-of-interest](../systems/world/points-of-interest.md) |
| `PathNavigationRegion` | [pathfinding](../systems/entities/pathfinding.md), [chunk-anatomy](../systems/world/chunk-anatomy.md) |
| `PathPackResources` | [resource-system](../systems/foundations/resource-system.md) |
| `PathType` | [naming-drift](../reference/naming-drift.md), [pathfinding](../systems/entities/pathfinding.md) |
| `PathTypeCache` | [pathfinding](../systems/entities/pathfinding.md) |
| `PatrolSpawner` | [entity-lifecycle](../systems/entities/entity-lifecycle.md), [server-level-tick](../systems/server/server-level-tick.md), [starting-a-server](../systems/server/starting-a-server.md), [points-of-interest](../systems/world/points-of-interest.md) |
| `PerfCommand` | [what-this-book-skips](../systems/anatomy/what-this-book-skips.md) |
| `PeriodicNotificationManager` | [X · The client](../systems/client/README.md), [resource-system](../systems/foundations/resource-system.md) |
| `PerlinNoise` | [density-functions](../systems/worldgen/density-functions.md) |
| `PerlinSimplexNoise` | [density-functions](../systems/worldgen/density-functions.md) |
| `Permission` | [glossary](../reference/glossary.md), [permissions](../systems/commands/permissions.md), [data-driven-types](../systems/foundations/data-driven-types.md) |
| `PermissionCheck` | [glossary](../reference/glossary.md), [naming-drift](../reference/naming-drift.md), [permissions](../systems/commands/permissions.md), [data-driven-types](../systems/foundations/data-driven-types.md) |
| `PermissionCheckTypes` | [permissions](../systems/commands/permissions.md) |
| `PermissionLevel` | [glossary](../reference/glossary.md), [naming-drift](../reference/naming-drift.md), [permissions](../systems/commands/permissions.md) |
| `PermissionProviderCheck` | [naming-drift](../reference/naming-drift.md), [permissions](../systems/commands/permissions.md) |
| `Permissions` | [level-data-and-rules](../reference/level-data-and-rules.md), [naming-drift](../reference/naming-drift.md), [entity-selectors](../systems/commands/entity-selectors.md), [permissions](../systems/commands/permissions.md), [text-components](../systems/foundations/text-components.md), [player-anatomy](../systems/player/player-anatomy.md) |
| `PermissionSet` | [glossary](../reference/glossary.md), [naming-drift](../reference/naming-drift.md), [functions-and-macros](../systems/commands/functions-and-macros.md), [permissions](../systems/commands/permissions.md), [player-anatomy](../systems/player/player-anatomy.md) |
| `PermissionSetSupplier` | [naming-drift](../reference/naming-drift.md), [permissions](../systems/commands/permissions.md) |
| `PermissionSetUnion` | [functions-and-macros](../systems/commands/functions-and-macros.md), [permissions](../systems/commands/permissions.md) |
| `PermissionTypes` | [permissions](../systems/commands/permissions.md) |
| `PersistentEntitySectionManager` | [entity-anatomy](../systems/entities/entity-anatomy.md), [entity-lifecycle](../systems/entities/entity-lifecycle.md), [how-a-server-dies](../systems/server/how-a-server-dies.md), [players-and-sessions](../systems/server/players-and-sessions.md), [server-level-tick](../systems/server/server-level-tick.md), [chunk-storage](../systems/world/chunk-storage.md), [scheduled-ticks](../systems/world/scheduled-ticks.md), [tickets-and-loading](../systems/world/tickets-and-loading.md) |
| `Phantom` | [entity-anatomy](../systems/entities/entity-anatomy.md) |
| `PhantomSpawner` | [entity-lifecycle](../systems/entities/entity-lifecycle.md), [server-level-tick](../systems/server/server-level-tick.md), [starting-a-server](../systems/server/starting-a-server.md) |
| `PictureInPictureRenderState` | [the-gui-render-tree](../systems/client/the-gui-render-tree.md) |
| `PieceGenerator` | [structure-placement](../systems/worldgen/structure-placement.md) |
| `PieceGeneratorSupplier` | [structure-placement](../systems/worldgen/structure-placement.md) |
| `PiecesContainer` | [glossary](../reference/glossary.md), [jigsaw-and-templates](../systems/worldgen/jigsaw-and-templates.md), [structure-placement](../systems/worldgen/structure-placement.md) |
| `PiercingWeapon` | [data-components](../systems/foundations/data-components.md), [the-spear](../systems/player/the-spear.md), [the-sword-swing](../systems/player/the-sword-swing.md) |
| `Piglin` | [ai-goals-and-brains](../systems/entities/ai-goals-and-brains.md), [pathfinding](../systems/entities/pathfinding.md), [the-spear](../systems/player/the-spear.md) |
| `PiglinAi` | [ai-goals-and-brains](../systems/entities/ai-goals-and-brains.md), [contexts-and-predicates](../systems/items/contexts-and-predicates.md) |
| `PineFoliagePlacer` | [trees](../systems/worldgen/trees.md) |
| `PingDebugChart` | [hud](../systems/client/hud.md) |
| `PingDebugMonitor` | [threads](../reference/threads.md), [debugging-the-running-game](../systems/client/debugging-the-running-game.md) |
| `PistonBaseBlock` | [pistons-and-block-events](../systems/blocks/pistons-and-block-events.md), [server-level-tick](../systems/server/server-level-tick.md) |
| `PistonHeadBlock` | [pistons-and-block-events](../systems/blocks/pistons-and-block-events.md) |
| `PistonHeadRenderer` | [block-entity-rendering](../systems/rendering/block-entity-rendering.md) |
| `PistonHeadRenderState` | [block-entity-rendering](../systems/rendering/block-entity-rendering.md) |
| `PistonMovingBlockEntity` | [block-entities](../systems/blocks/block-entities.md), [pistons-and-block-events](../systems/blocks/pistons-and-block-events.md) |
| `PistonStructureResolver` | [pistons-and-block-events](../systems/blocks/pistons-and-block-events.md) |
| `PlaceCommand` | [brigadier-and-commands](../systems/commands/brigadier-and-commands.md) |
| `PlacedFeature` | [data-driven-types](../systems/foundations/data-driven-types.md), [features-and-placement](../systems/worldgen/features-and-placement.md), [jigsaw-and-templates](../systems/worldgen/jigsaw-and-templates.md) |
| `PlacementContext` | [XII · World generation](../systems/worldgen/README.md) |
| `PlacementFilter` | [features-and-placement](../systems/worldgen/features-and-placement.md) |
| `PlacementInfo` | [naming-drift](../reference/naming-drift.md), [recipes](../systems/items/recipes.md) |
| `PlacementModifier` | [data-driven-types](../systems/foundations/data-driven-types.md), [features-and-placement](../systems/worldgen/features-and-placement.md) |
| `PlacementModifierType` | [data-driven-types](../systems/foundations/data-driven-types.md) |
| `PlaceOnGroundDecorator` | [trees](../systems/worldgen/trees.md) |
| `PlaceRecipeHelper` | [recipes](../systems/items/recipes.md) |
| `PlainMessage` | [dialogs](../systems/commands/dialogs.md) |
| `PlainTextContents` | [naming-drift](../reference/naming-drift.md), [text-components](../systems/foundations/text-components.md) |
| `PlainTextFunction` | [functions-and-macros](../systems/commands/functions-and-macros.md) |
| `Player` | [biggest](../maps/biggest.md), [fanin](../maps/fanin.md), [glossary](../reference/glossary.md), [hud-elements](../reference/hud-elements.md), [naming-drift](../reference/naming-drift.md), [non-living-damage](../reference/non-living-damage.md), [block-breaking](../systems/blocks/block-breaking.md), [block-interaction](../systems/blocks/block-interaction.md), [gui-and-screens](../systems/client/gui-and-screens.md), [what-makes-a-sound](../systems/client/what-makes-a-sound.md), [scoreboard-and-data](../systems/commands/scoreboard-and-data.md), [VI · Entities](../systems/entities/README.md), [attributes](../systems/entities/attributes.md), [authority](../systems/entities/authority.md), [damage-and-death](../systems/entities/damage-and-death.md), [entity-anatomy](../systems/entities/entity-anatomy.md), [entity-lifecycle](../systems/entities/entity-lifecycle.md), [movement-and-collision](../systems/entities/movement-and-collision.md), [synched-entity-data](../systems/entities/synched-entity-data.md), [text-components](../systems/foundations/text-components.md), [containers-and-menus](../systems/items/containers-and-menus.md), [enchanting](../systems/items/enchanting.md), [enchantments](../systems/items/enchantments.md), [items-and-stacks](../systems/items/items-and-stacks.md), [loot-tables](../systems/items/loot-tables.md), [recipes](../systems/items/recipes.md), [using-an-item](../systems/items/using-an-item.md), [VIII · The player](../systems/player/README.md), [hunger-and-experience](../systems/player/hunger-and-experience.md), [input-to-movement](../systems/player/input-to-movement.md), [player-anatomy](../systems/player/player-anatomy.md), [the-spear](../systems/player/the-spear.md), [the-sword-swing](../systems/player/the-sword-swing.md), [the-two-phase-tick](../systems/player/the-two-phase-tick.md), [block-entity-rendering](../systems/rendering/block-entity-rendering.md), [server-level-tick](../systems/server/server-level-tick.md), [chunk-storage](../systems/world/chunk-storage.md) |
| `PlayerAdvancements` | [naming-drift](../reference/naming-drift.md), [what-this-book-skips](../systems/anatomy/what-this-book-skips.md), [advancements](../systems/commands/advancements.md), [how-a-server-dies](../systems/server/how-a-server-dies.md), [players-and-sessions](../systems/server/players-and-sessions.md) |
| `PlayerChatMessage` | [text-components](../systems/foundations/text-components.md), [chat-and-signing](../systems/networking/chat-and-signing.md) |
| `PlayerChunkSender` | [what-the-client-is-told](../systems/networking/what-the-client-is-told.md), [players-and-sessions](../systems/server/players-and-sessions.md), [server-tick](../systems/server/server-tick.md), [lighting](../systems/world/lighting.md), [tickets-and-loading](../systems/world/tickets-and-loading.md) |
| `PlayerDataStorage` | [level-data-and-rules](../reference/level-data-and-rules.md), [how-a-server-dies](../systems/server/how-a-server-dies.md), [players-and-sessions](../systems/server/players-and-sessions.md), [starting-a-server](../systems/server/starting-a-server.md) |
| `PlayerEnderChestContainer` | [player-anatomy](../systems/player/player-anatomy.md) |
| `PlayerEquipment` | [player-anatomy](../systems/player/player-anatomy.md) |
| `PlayerGlyphProvider` | [text-and-fonts](../systems/client/text-and-fonts.md) |
| `PlayerInfo` | [chat-and-signing](../systems/networking/chat-and-signing.md), [player-anatomy](../systems/player/player-anatomy.md) |
| `PlayerList` | [level-data-and-rules](../reference/level-data-and-rules.md), [naming-drift](../reference/naming-drift.md), [threads](../reference/threads.md), [what-makes-a-sound](../systems/client/what-makes-a-sound.md), [advancements](../systems/commands/advancements.md), [brigadier-and-commands](../systems/commands/brigadier-and-commands.md), [entity-selectors](../systems/commands/entity-selectors.md), [scoreboard-and-data](../systems/commands/scoreboard-and-data.md), [resource-system](../systems/foundations/resource-system.md), [tags](../systems/foundations/tags.md), [text-components](../systems/foundations/text-components.md), [recipes](../systems/items/recipes.md), [chat-and-signing](../systems/networking/chat-and-signing.md), [protocol-phases](../systems/networking/protocol-phases.md), [what-the-client-is-told](../systems/networking/what-the-client-is-told.md), [how-a-server-dies](../systems/server/how-a-server-dies.md), [players-and-sessions](../systems/server/players-and-sessions.md), [server-tick](../systems/server/server-tick.md), [tickets-and-loading](../systems/world/tickets-and-loading.md) |
| `PlayerModel` | [player-anatomy](../systems/player/player-anatomy.md) |
| `PlayerPredicate` | [advancements](../systems/commands/advancements.md), [data-driven-types](../systems/foundations/data-driven-types.md) |
| `PlayerRideableJumping` | [entity-anatomy](../systems/entities/entity-anatomy.md) |
| `PlayerScoreEntry` | [scoreboard-and-data](../systems/commands/scoreboard-and-data.md) |
| `PlayerSkinRenderCache` | [entity-rendering](../systems/rendering/entity-rendering.md) |
| `PlayerSpawnFinder` | [protocol-phases](../systems/networking/protocol-phases.md), [players-and-sessions](../systems/server/players-and-sessions.md), [starting-a-server](../systems/server/starting-a-server.md), [tickets-and-loading](../systems/world/tickets-and-loading.md), [biomes](../systems/worldgen/biomes.md) |
| `PlayerSprite` | [text-components](../systems/foundations/text-components.md) |
| `PlayerTabOverlay` | [hud-elements](../reference/hud-elements.md) |
| `PlayerTeam` | [scoreboard-and-data](../systems/commands/scoreboard-and-data.md), [text-components](../systems/foundations/text-components.md) |
| `PlayerTrigger` | [advancements](../systems/commands/advancements.md) |
| `PlaySoundCommand` | [what-makes-a-sound](../systems/client/what-makes-a-sound.md) |
| `PlaySoundConsumeEffect` | [hunger-and-experience](../systems/player/hunger-and-experience.md) |
| `PlaySoundEffect` | [enchantments](../systems/items/enchantments.md) |
| `PoiCompetitorScan` | [ai-goals-and-brains](../systems/entities/ai-goals-and-brains.md), [points-of-interest](../systems/world/points-of-interest.md) |
| `PoiManager` | [glossary](../reference/glossary.md), [VI · Entities](../systems/entities/README.md), [ai-goals-and-brains](../systems/entities/ai-goals-and-brains.md), [server-tick](../systems/server/server-tick.md), [chunk-storage](../systems/world/chunk-storage.md), [points-of-interest](../systems/world/points-of-interest.md) |
| `PoiRecord` | [points-of-interest](../systems/world/points-of-interest.md) |
| `PoiSection` | [points-of-interest](../systems/world/points-of-interest.md) |
| `PoiType` | [ai-goals-and-brains](../systems/entities/ai-goals-and-brains.md), [points-of-interest](../systems/world/points-of-interest.md) |
| `PoiTypes` | [points-of-interest](../systems/world/points-of-interest.md) |
| `PoiTypeTags` | [tags](../systems/foundations/tags.md), [points-of-interest](../systems/world/points-of-interest.md) |
| `PolygonMode` | [blaze3d](../systems/rendering/blaze3d.md) |
| `PoolAliasBinding` | [data-driven-types](../systems/foundations/data-driven-types.md), [jigsaw-and-templates](../systems/worldgen/jigsaw-and-templates.md) |
| `PoolAliasLookup` | [jigsaw-and-templates](../systems/worldgen/jigsaw-and-templates.md) |
| `PoolElementStructurePiece` | [glossary](../reference/glossary.md), [jigsaw-and-templates](../systems/worldgen/jigsaw-and-templates.md) |
| `Pools` | [what-this-book-skips](../systems/anatomy/what-this-book-skips.md) |
| `PortalForcer` | [points-of-interest](../systems/world/points-of-interest.md) |
| `Pose` | [damage-and-death](../systems/entities/damage-and-death.md), [entity-anatomy](../systems/entities/entity-anatomy.md), [synched-entity-data](../systems/entities/synched-entity-data.md) |
| `PoseStack` | [the-gui-render-tree](../systems/client/the-gui-render-tree.md), [blaze3d](../systems/rendering/blaze3d.md), [entity-rendering](../systems/rendering/entity-rendering.md) |
| `Position` | [math-and-primitives](../reference/math-and-primitives.md) |
| `PositionalRandomFactory` | [math-and-primitives](../reference/math-and-primitives.md) |
| `PositionCollisionContext` | [math-and-primitives](../reference/math-and-primitives.md) |
| `PositionMoveRotation` | [the-client-level](../systems/client/the-client-level.md), [input-to-movement](../systems/player/input-to-movement.md) |
| `PositionSource` | [data-driven-types](../systems/foundations/data-driven-types.md), [game-events-and-vibrations](../systems/world/game-events-and-vibrations.md) |
| `PositionSourceType` | [data-driven-types](../systems/foundations/data-driven-types.md) |
| `PosRuleTest` | [data-driven-types](../systems/foundations/data-driven-types.md), [structure-placement](../systems/worldgen/structure-placement.md) |
| `PosRuleTestType` | [data-driven-types](../systems/foundations/data-driven-types.md) |
| `PostChain` | [what-this-book-skips](../systems/anatomy/what-this-book-skips.md), [post-processing](../systems/rendering/post-processing.md) |
| `PostChainConfig` | [what-this-book-skips](../systems/anatomy/what-this-book-skips.md), [post-processing](../systems/rendering/post-processing.md) |
| `PostPass` | [what-this-book-skips](../systems/anatomy/what-this-book-skips.md), [post-processing](../systems/rendering/post-processing.md) |
| `PostPlacementProcessor` | [structure-placement](../systems/worldgen/structure-placement.md) |
| `PostSpawnProcessor` | [entity-anatomy](../systems/entities/entity-anatomy.md) |
| `PotentialCalculator` | [entity-lifecycle](../systems/entities/entity-lifecycle.md) |
| `PotentSulfurBlock` | [pistons-and-block-events](../systems/blocks/pistons-and-block-events.md) |
| `Potion` | [hierarchy](../maps/hierarchy.md), [identifiers-and-registries](../systems/foundations/identifiers-and-registries.md) |
| `PotionContents` | [hunger-and-experience](../systems/player/hunger-and-experience.md), [status-effects](../systems/player/status-effects.md) |
| `PowderedSnowFogEnvironment` | [lightmap-fog-and-sky](../systems/rendering/lightmap-fog-and-sky.md) |
| `PredictiveAction` | [prediction-and-acks](../systems/client/prediction-and-acks.md) |
| `PreeditEvent` | [naming-drift](../reference/naming-drift.md), [input-and-keybinds](../systems/client/input-and-keybinds.md) |
| `PreferredGraphicsApi` | [anatomy](../systems/anatomy/anatomy.md), [blaze3d](../systems/rendering/blaze3d.md) |
| `PreparableReloadListener` | [glossary](../reference/glossary.md), [resource-system](../systems/foundations/resource-system.md), [models-and-atlases](../systems/rendering/models-and-atlases.md) |
| `PreparedRenderType` | [blaze3d](../systems/rendering/blaze3d.md) |
| `PrepareSpawnTask` | [protocol-phases](../systems/networking/protocol-phases.md), [player-anatomy](../systems/player/player-anatomy.md), [players-and-sessions](../systems/server/players-and-sessions.md), [starting-a-server](../systems/server/starting-a-server.md), [tickets-and-loading](../systems/world/tickets-and-loading.md) |
| `PresetEditor` | [creating-a-world](../systems/worldgen/creating-a-world.md) |
| `PresetFlatWorldScreen` | [creating-a-world](../systems/worldgen/creating-a-world.md) |
| `PrimaryLevelData` | [level-data-and-rules](../reference/level-data-and-rules.md), [how-a-server-dies](../systems/server/how-a-server-dies.md), [starting-a-server](../systems/server/starting-a-server.md), [creating-a-world](../systems/worldgen/creating-a-world.md) |
| `PrimedTnt` | [non-living-damage](../reference/non-living-damage.md), [the-client-level](../systems/client/the-client-level.md), [damage-and-death](../systems/entities/damage-and-death.md) |
| `PrimitiveTag` | [codecs-nbt-json](../systems/foundations/codecs-nbt-json.md) |
| `PrimitiveTopology` | [naming-drift](../reference/naming-drift.md), [blaze3d](../systems/rendering/blaze3d.md) |
| `PriorityConsecutiveExecutor` | [threads](../reference/threads.md), [anatomy](../systems/anatomy/anatomy.md), [chunk-storage](../systems/world/chunk-storage.md) |
| `ProblemReporter` | [codecs-nbt-json](../systems/foundations/codecs-nbt-json.md) |
| `ProcessorRule` | [jigsaw-and-templates](../systems/worldgen/jigsaw-and-templates.md) |
| `ProfiledReloadInstance` | [resource-system](../systems/foundations/resource-system.md) |
| `ProfilePublicKey` | [chat-and-signing](../systems/networking/chat-and-signing.md) |
| `Profiler` | [what-this-book-skips](../systems/anatomy/what-this-book-skips.md) |
| `ProfilerFiller` | [what-this-book-skips](../systems/anatomy/what-this-book-skips.md), [server-tick](../systems/server/server-tick.md) |
| `ProfilerPieChart` | [hud](../systems/client/hud.md) |
| `Projectile` | [hierarchy](../maps/hierarchy.md), [non-living-damage](../reference/non-living-damage.md), [damage-and-death](../systems/entities/damage-and-death.md), [using-an-item](../systems/items/using-an-item.md) |
| `ProjectileUtil` | [the-spear](../systems/player/the-spear.md), [the-sword-swing](../systems/player/the-sword-swing.md) |
| `ProjectileWeaponItem` | [enchantments](../systems/items/enchantments.md), [using-an-item](../systems/items/using-an-item.md) |
| `Projection` | [post-processing](../systems/rendering/post-processing.md) |
| `ProjectionMatrixBuffer` | [blaze3d](../systems/rendering/blaze3d.md), [post-processing](../systems/rendering/post-processing.md) |
| `Property` | [blocks-and-states](../systems/blocks/blocks-and-states.md) |
| `ProtoChunk` | [chunk-anatomy](../systems/world/chunk-anatomy.md), [chunk-generation-pipeline](../systems/world/chunk-generation-pipeline.md), [chunk-storage](../systems/world/chunk-storage.md), [lighting](../systems/world/lighting.md), [scheduled-ticks](../systems/world/scheduled-ticks.md) |
| `ProtoChunkTicks` | [chunk-anatomy](../systems/world/chunk-anatomy.md), [scheduled-ticks](../systems/world/scheduled-ticks.md) |
| `ProtocolCodecBuilder` | [packets-and-stream-codecs](../systems/networking/packets-and-stream-codecs.md) |
| `ProtocolInfo` | [packets-and-stream-codecs](../systems/networking/packets-and-stream-codecs.md), [protocol-phases](../systems/networking/protocol-phases.md), [the-connection](../systems/networking/the-connection.md) |
| `ProtocolInfoBuilder` | [naming-drift](../reference/naming-drift.md), [packets-and-stream-codecs](../systems/networking/packets-and-stream-codecs.md), [protocol-phases](../systems/networking/protocol-phases.md) |
| `ProtocolSwapHandler` | [the-connection](../systems/networking/the-connection.md) |
| `PunchTreeTutorialStepInstance` | [tags](../systems/foundations/tags.md) |
| `PushReaction` | [pistons-and-block-events](../systems/blocks/pistons-and-block-events.md) |
| `QuadCollection` | [models-and-atlases](../systems/rendering/models-and-atlases.md) |
| `QuadParticleFeatureRenderer` | [submit-phases](../reference/submit-phases.md), [particles](../systems/rendering/particles.md) |
| `QuadParticleGroup` | [particles](../systems/rendering/particles.md) |
| `QuadParticleRenderState` | [particles](../systems/rendering/particles.md) |
| `QuartPos` | [glossary](../reference/glossary.md), [math-and-primitives](../reference/math-and-primitives.md) |
| `QueryThreadGs4` | [threads](../reference/threads.md), [what-this-book-skips](../systems/anatomy/what-this-book-skips.md), [how-a-server-dies](../systems/server/how-a-server-dies.md), [starting-a-server](../systems/server/starting-a-server.md) |
| `Rabbit` | [pathfinding](../systems/entities/pathfinding.md) |
| `Raid` | [points-of-interest](../systems/world/points-of-interest.md) |
| `RaidCommand` | [brigadier-and-commands](../systems/commands/brigadier-and-commands.md) |
| `Raids` | [level-data-and-rules](../reference/level-data-and-rules.md), [points-of-interest](../systems/world/points-of-interest.md) |
| `RandomBooleanSelectorFeature` | [features-and-placement](../systems/worldgen/features-and-placement.md) |
| `RandomizableContainer` | [data-driven-types](../systems/foundations/data-driven-types.md), [containers-and-menus](../systems/items/containers-and-menus.md), [contexts-and-predicates](../systems/items/contexts-and-predicates.md), [loot-tables](../systems/items/loot-tables.md) |
| `RandomizableContainerBlockEntity` | [data-driven-types](../systems/foundations/data-driven-types.md), [loot-tables](../systems/items/loot-tables.md) |
| `RandomLookAroundGoal` | [ai-goals-and-brains](../systems/entities/ai-goals-and-brains.md) |
| `RandomOffsetPlacement` | [features-and-placement](../systems/worldgen/features-and-placement.md) |
| `RandomSelectorFeature` | [features-and-placement](../systems/worldgen/features-and-placement.md) |
| `RandomSequence` | [math-and-primitives](../reference/math-and-primitives.md) |
| `RandomSequences` | [level-data-and-rules](../reference/level-data-and-rules.md), [math-and-primitives](../reference/math-and-primitives.md), [contexts-and-predicates](../systems/items/contexts-and-predicates.md), [loot-tables](../systems/items/loot-tables.md) |
| `RandomSource` | [fanin](../maps/fanin.md), [packages](../maps/packages.md), [math-and-primitives](../reference/math-and-primitives.md), [entity-selectors](../systems/commands/entity-selectors.md), [contexts-and-predicates](../systems/items/contexts-and-predicates.md), [enchanting](../systems/items/enchanting.md), [enchantments](../systems/items/enchantments.md), [creating-a-world](../systems/worldgen/creating-a-world.md) |
| `RandomSpreadFoliagePlacer` | [trees](../systems/worldgen/trees.md) |
| `RandomSpreadStructurePlacement` | [structure-placement](../systems/worldgen/structure-placement.md) |
| `RandomSpreadType` | [structure-placement](../systems/worldgen/structure-placement.md) |
| `RandomState` | [math-and-primitives](../reference/math-and-primitives.md), [what-the-client-is-told](../systems/networking/what-the-client-is-told.md), [biomes](../systems/worldgen/biomes.md), [density-functions](../systems/worldgen/density-functions.md), [terrain](../systems/worldgen/terrain.md) |
| `RandomSupport` | [math-and-primitives](../reference/math-and-primitives.md) |
| `RangedAttribute` | [attributes](../systems/entities/attributes.md) |
| `RangedBowAttackGoal` | [pathfinding](../systems/entities/pathfinding.md) |
| `RangedCrossbowAttackGoal` | [using-an-item](../systems/items/using-an-item.md) |
| `RangeSelectItemModel` | [naming-drift](../reference/naming-drift.md) |
| `RateKickingConnection` | [the-connection](../systems/networking/the-connection.md) |
| `Ravager` | [damage-and-death](../systems/entities/damage-and-death.md) |
| `RconClient` | [threads](../reference/threads.md) |
| `RconConsoleSource` | [what-this-book-skips](../systems/anatomy/what-this-book-skips.md) |
| `RconThread` | [threads](../reference/threads.md), [what-this-book-skips](../systems/anatomy/what-this-book-skips.md), [how-a-server-dies](../systems/server/how-a-server-dies.md), [starting-a-server](../systems/server/starting-a-server.md) |
| `ReadOnlyScoreInfo` | [scoreboard-and-data](../systems/commands/scoreboard-and-data.md) |
| `RealmsConnect` | [protocol-phases](../systems/networking/protocol-phases.md) |
| `RealmsScreen` | [hierarchy](../maps/hierarchy.md) |
| `Recipe` | [glossary](../reference/glossary.md), [naming-drift](../reference/naming-drift.md), [data-driven-types](../systems/foundations/data-driven-types.md), [recipes](../systems/items/recipes.md) |
| `RecipeAccess` | [recipes](../systems/items/recipes.md) |
| `RecipeBook` | [what-this-book-skips](../systems/anatomy/what-this-book-skips.md) |
| `RecipeBookCategories` | [recipes](../systems/items/recipes.md) |
| `RecipeBookCategory` | [recipes](../systems/items/recipes.md) |
| `RecipeBookComponent` | [recipes](../systems/items/recipes.md) |
| `RecipeBookMenu` | [recipes](../systems/items/recipes.md) |
| `RecipeBookSettings` | [what-this-book-skips](../systems/anatomy/what-this-book-skips.md) |
| `RecipeBookType` | [recipes](../systems/items/recipes.md) |
| `RecipeCache` | [recipes](../systems/items/recipes.md) |
| `RecipeCollection` | [recipes](../systems/items/recipes.md) |
| `RecipeCraftingHolder` | [recipes](../systems/items/recipes.md) |
| `RecipeDisplay` | [glossary](../reference/glossary.md), [data-driven-types](../systems/foundations/data-driven-types.md), [recipes](../systems/items/recipes.md), [packets-and-stream-codecs](../systems/networking/packets-and-stream-codecs.md) |
| `RecipeDisplayEntry` | [naming-drift](../reference/naming-drift.md), [recipes](../systems/items/recipes.md) |
| `RecipeDisplayId` | [glossary](../reference/glossary.md), [recipes](../systems/items/recipes.md) |
| `RecipeHolder` | [recipes](../systems/items/recipes.md) |
| `RecipeInput` | [recipes](../systems/items/recipes.md) |
| `RecipeManager` | [block-entities](../systems/blocks/block-entities.md), [data-driven-types](../systems/foundations/data-driven-types.md), [resource-system](../systems/foundations/resource-system.md), [recipes](../systems/items/recipes.md) |
| `RecipeMap` | [data-driven-types](../systems/foundations/data-driven-types.md), [recipes](../systems/items/recipes.md) |
| `RecipePropertySet` | [recipes](../systems/items/recipes.md) |
| `RecipeSerializer` | [data-driven-types](../systems/foundations/data-driven-types.md), [recipes](../systems/items/recipes.md) |
| `RecipeSerializers` | [recipes](../systems/items/recipes.md) |
| `RecipeType` | [data-driven-types](../systems/foundations/data-driven-types.md), [recipes](../systems/items/recipes.md) |
| `RecipeUnlockedTrigger` | [advancements](../systems/commands/advancements.md) |
| `RecordCodecBuilder` | [fanin](../maps/fanin.md) |
| `RedStoneOreBlock` | [prediction-and-acks](../systems/client/prediction-and-acks.md) |
| `RedstoneSide` | [signal-and-dust](../systems/blocks/signal-and-dust.md) |
| `RedstoneTorchBlock` | [signal-and-dust](../systems/blocks/signal-and-dust.md), [scheduled-ticks](../systems/world/scheduled-ticks.md) |
| `RedStoneWireBlock` | [diodes-and-observers](../systems/blocks/diodes-and-observers.md), [signal-and-dust](../systems/blocks/signal-and-dust.md) |
| `RedstoneWireEvaluator` | [signal-and-dust](../systems/blocks/signal-and-dust.md) |
| `ReentrantBlockableEventLoop` | [threads](../reference/threads.md), [anatomy](../systems/anatomy/anatomy.md), [the-client-loop](../systems/client/the-client-loop.md), [server-tick](../systems/server/server-tick.md), [points-of-interest](../systems/world/points-of-interest.md) |
| `RegionBitmap` | [chunk-storage](../systems/world/chunk-storage.md) |
| `RegionFile` | [codecs-nbt-json](../systems/foundations/codecs-nbt-json.md), [chunk-storage](../systems/world/chunk-storage.md) |
| `RegionFileStorage` | [chunk-storage](../systems/world/chunk-storage.md) |
| `RegionFileVersion` | [codecs-nbt-json](../systems/foundations/codecs-nbt-json.md), [starting-a-server](../systems/server/starting-a-server.md), [chunk-storage](../systems/world/chunk-storage.md) |
| `RegionStorageInfo` | [chunk-storage](../systems/world/chunk-storage.md) |
| `RegistrationInfo` | [identifiers-and-registries](../systems/foundations/identifiers-and-registries.md) |
| `Registries` | [fanin](../maps/fanin.md), [density-function-nodes](../reference/density-function-nodes.md), [glossary](../reference/glossary.md), [level-data-and-rules](../reference/level-data-and-rules.md), [naming-drift](../reference/naming-drift.md), [what-this-book-skips](../systems/anatomy/what-this-book-skips.md), [V · Blocks](../systems/blocks/README.md), [debugging-the-running-game](../systems/client/debugging-the-running-game.md), [what-makes-a-sound](../systems/client/what-makes-a-sound.md), [game-tests](../systems/commands/game-tests.md), [ai-goals-and-brains](../systems/entities/ai-goals-and-brains.md), [attributes](../systems/entities/attributes.md), [entity-anatomy](../systems/entities/entity-anatomy.md), [data-driven-types](../systems/foundations/data-driven-types.md), [identifiers-and-registries](../systems/foundations/identifiers-and-registries.md), [tags](../systems/foundations/tags.md), [contexts-and-predicates](../systems/items/contexts-and-predicates.md), [enchanting](../systems/items/enchanting.md), [enchantments](../systems/items/enchantments.md), [recipes](../systems/items/recipes.md), [hunger-and-experience](../systems/player/hunger-and-experience.md), [environment-attributes-and-timelines](../systems/world/environment-attributes-and-timelines.md), [creating-a-world](../systems/worldgen/creating-a-world.md), [hand-built-structures](../systems/worldgen/hand-built-structures.md) |
| `Registry` | [entity-anatomy](../systems/entities/entity-anatomy.md), [codecs-nbt-json](../systems/foundations/codecs-nbt-json.md), [data-components](../systems/foundations/data-components.md), [data-driven-types](../systems/foundations/data-driven-types.md), [identifiers-and-registries](../systems/foundations/identifiers-and-registries.md), [tags](../systems/foundations/tags.md) |
| `RegistryAccess` | [identifiers-and-registries](../systems/foundations/identifiers-and-registries.md), [packets-and-stream-codecs](../systems/networking/packets-and-stream-codecs.md), [protocol-phases](../systems/networking/protocol-phases.md) |
| `RegistryCodecs` | [identifiers-and-registries](../systems/foundations/identifiers-and-registries.md), [tags](../systems/foundations/tags.md) |
| `RegistryDataCollector` | [data-components](../systems/foundations/data-components.md), [identifiers-and-registries](../systems/foundations/identifiers-and-registries.md), [tags](../systems/foundations/tags.md), [items-and-stacks](../systems/items/items-and-stacks.md), [protocol-phases](../systems/networking/protocol-phases.md) |
| `RegistryDataLoader` | [codecs-nbt-json](../systems/foundations/codecs-nbt-json.md), [data-driven-types](../systems/foundations/data-driven-types.md), [identifiers-and-registries](../systems/foundations/identifiers-and-registries.md), [contexts-and-predicates](../systems/items/contexts-and-predicates.md), [enchantments](../systems/items/enchantments.md), [protocol-phases](../systems/networking/protocol-phases.md), [starting-a-server](../systems/server/starting-a-server.md), [environment-attributes-and-timelines](../systems/world/environment-attributes-and-timelines.md), [creating-a-world](../systems/worldgen/creating-a-world.md) |
| `RegistryFileCodec` | [codecs-nbt-json](../systems/foundations/codecs-nbt-json.md), [data-driven-types](../systems/foundations/data-driven-types.md), [identifiers-and-registries](../systems/foundations/identifiers-and-registries.md), [density-functions](../systems/worldgen/density-functions.md) |
| `RegistryFixedCodec` | [codecs-nbt-json](../systems/foundations/codecs-nbt-json.md), [identifiers-and-registries](../systems/foundations/identifiers-and-registries.md), [game-events-and-vibrations](../systems/world/game-events-and-vibrations.md) |
| `RegistryFriendlyByteBuf` | [fanin](../maps/fanin.md), [synched-entity-data](../systems/entities/synched-entity-data.md), [codecs-nbt-json](../systems/foundations/codecs-nbt-json.md), [identifiers-and-registries](../systems/foundations/identifiers-and-registries.md), [packets-and-stream-codecs](../systems/networking/packets-and-stream-codecs.md), [protocol-phases](../systems/networking/protocol-phases.md) |
| `RegistryLayer` | [data-driven-types](../systems/foundations/data-driven-types.md), [identifiers-and-registries](../systems/foundations/identifiers-and-registries.md), [resource-system](../systems/foundations/resource-system.md), [tags](../systems/foundations/tags.md), [contexts-and-predicates](../systems/items/contexts-and-predicates.md) |
| `RegistryLoadTask` | [identifiers-and-registries](../systems/foundations/identifiers-and-registries.md), [tags](../systems/foundations/tags.md) |
| `RegistryOps` | [codecs-nbt-json](../systems/foundations/codecs-nbt-json.md), [data-driven-types](../systems/foundations/data-driven-types.md), [identifiers-and-registries](../systems/foundations/identifiers-and-registries.md), [text-components](../systems/foundations/text-components.md) |
| `RegistrySetBuilder` | [identifiers-and-registries](../systems/foundations/identifiers-and-registries.md) |
| `RegistrySynchronization` | [data-components](../systems/foundations/data-components.md), [identifiers-and-registries](../systems/foundations/identifiers-and-registries.md), [tags](../systems/foundations/tags.md), [contexts-and-predicates](../systems/items/contexts-and-predicates.md), [enchantments](../systems/items/enchantments.md), [protocol-phases](../systems/networking/protocol-phases.md) |
| `RegistryValidator` | [identifiers-and-registries](../systems/foundations/identifiers-and-registries.md) |
| `Relative` | [input-to-movement](../systems/player/input-to-movement.md) |
| `ReloadableResourceManager` | [anatomy](../systems/anatomy/anatomy.md), [resource-system](../systems/foundations/resource-system.md), [models-and-atlases](../systems/rendering/models-and-atlases.md) |
| `ReloadableServerRegistries` | [naming-drift](../reference/naming-drift.md), [data-driven-types](../systems/foundations/data-driven-types.md), [identifiers-and-registries](../systems/foundations/identifiers-and-registries.md), [resource-system](../systems/foundations/resource-system.md), [tags](../systems/foundations/tags.md), [contexts-and-predicates](../systems/items/contexts-and-predicates.md), [loot-tables](../systems/items/loot-tables.md) |
| `ReloadableServerResources` | [brigadier-and-commands](../systems/commands/brigadier-and-commands.md), [data-components](../systems/foundations/data-components.md), [data-driven-types](../systems/foundations/data-driven-types.md), [resource-system](../systems/foundations/resource-system.md), [tags](../systems/foundations/tags.md), [items-and-stacks](../systems/items/items-and-stacks.md), [starting-a-server](../systems/server/starting-a-server.md), [creating-a-world](../systems/worldgen/creating-a-world.md) |
| `ReloadCommand` | [resource-system](../systems/foundations/resource-system.md) |
| `ReloadInstance` | [anatomy](../systems/anatomy/anatomy.md), [resource-system](../systems/foundations/resource-system.md) |
| `RemoteChatSession` | [chat-and-signing](../systems/networking/chat-and-signing.md) |
| `RemoteFriendListUpdateHandler` | [X · The client](../systems/client/README.md) |
| `RemotePlayer` | [damage-and-death](../systems/entities/damage-and-death.md), [entity-anatomy](../systems/entities/entity-anatomy.md), [player-anatomy](../systems/player/player-anatomy.md), [the-sword-swing](../systems/player/the-sword-swing.md) |
| `RemoteSampleLogger` | [debugging-the-running-game](../systems/client/debugging-the-running-game.md) |
| `RemoteSlot` | [codecs-nbt-json](../systems/foundations/codecs-nbt-json.md), [data-components](../systems/foundations/data-components.md), [containers-and-menus](../systems/items/containers-and-menus.md) |
| `RemoveBinomial` | [enchantments](../systems/items/enchantments.md) |
| `RemoveStatusEffectsConsumeEffect` | [hunger-and-experience](../systems/player/hunger-and-experience.md) |
| `RenderBuffers` | [section-meshing](../systems/rendering/section-meshing.md), [the-frame](../systems/rendering/the-frame.md) |
| `RenderLayer` | [entity-rendering](../systems/rendering/entity-rendering.md) |
| `RenderLayerParent` | [entity-rendering](../systems/rendering/entity-rendering.md) |
| `RenderPass` | [glossary](../reference/glossary.md), [submit-phases](../reference/submit-phases.md), [blaze3d](../systems/rendering/blaze3d.md), [post-processing](../systems/rendering/post-processing.md), [visibility-and-the-frame-graph](../systems/rendering/visibility-and-the-frame-graph.md) |
| `RenderPassBackend` | [blaze3d](../systems/rendering/blaze3d.md) |
| `RenderPipeline` | [glossary](../reference/glossary.md), [naming-drift](../reference/naming-drift.md), [blaze3d](../systems/rendering/blaze3d.md), [post-processing](../systems/rendering/post-processing.md) |
| `RenderPipelines` | [naming-drift](../reference/naming-drift.md), [blaze3d](../systems/rendering/blaze3d.md), [lightmap-fog-and-sky](../systems/rendering/lightmap-fog-and-sky.md), [particles](../systems/rendering/particles.md), [post-processing](../systems/rendering/post-processing.md) |
| `RenderRegionCache` | [section-meshing](../systems/rendering/section-meshing.md) |
| `RenderSectionRegion` | [the-client-level](../systems/client/the-client-level.md), [section-meshing](../systems/rendering/section-meshing.md) |
| `RenderSetup` | [naming-drift](../reference/naming-drift.md), [blaze3d](../systems/rendering/blaze3d.md) |
| `RenderShape` | [naming-drift](../reference/naming-drift.md) |
| `RenderSystem` | [naming-drift](../reference/naming-drift.md), [threads](../reference/threads.md), [anatomy](../systems/anatomy/anatomy.md), [input-and-keybinds](../systems/client/input-and-keybinds.md), [the-client-loop](../systems/client/the-client-loop.md), [blaze3d](../systems/rendering/blaze3d.md), [lightmap-fog-and-sky](../systems/rendering/lightmap-fog-and-sky.md), [post-processing](../systems/rendering/post-processing.md) |
| `RenderTarget` | [blaze3d](../systems/rendering/blaze3d.md) |
| `RenderType` | [glossary](../reference/glossary.md), [naming-drift](../reference/naming-drift.md), [submit-phases](../reference/submit-phases.md), [blaze3d](../systems/rendering/blaze3d.md), [entity-rendering](../systems/rendering/entity-rendering.md), [models-and-atlases](../systems/rendering/models-and-atlases.md) |
| `RenderTypeFeatureRenderer` | [submit-phases](../reference/submit-phases.md) |
| `RenderTypes` | [naming-drift](../reference/naming-drift.md), [blaze3d](../systems/rendering/blaze3d.md) |
| `Repairable` | [data-components](../systems/foundations/data-components.md) |
| `RepairItemRecipe` | [enchanting](../systems/items/enchanting.md) |
| `RepeaterBlock` | [diodes-and-observers](../systems/blocks/diodes-and-observers.md), [scheduled-ticks](../systems/world/scheduled-ticks.md) |
| `RepeatingPlacement` | [features-and-placement](../systems/worldgen/features-and-placement.md) |
| `ReportedException` | [the-client-loop](../systems/client/the-client-loop.md), [containers-and-menus](../systems/items/containers-and-menus.md), [the-connection](../systems/networking/the-connection.md), [how-a-server-dies](../systems/server/how-a-server-dies.md), [server-tick](../systems/server/server-tick.md) |
| `ReportGameListener` | [game-tests](../systems/commands/game-tests.md) |
| `ReportingContext` | [what-this-book-skips](../systems/anatomy/what-this-book-skips.md) |
| `ReportType` | [how-a-server-dies](../systems/server/how-a-server-dies.md) |
| `RepositorySource` | [resource-system](../systems/foundations/resource-system.md) |
| `ResetRaidStatus` | [ai-goals-and-brains](../systems/entities/ai-goals-and-brains.md) |
| `ResolutionContext` | [naming-drift](../reference/naming-drift.md), [entity-selectors](../systems/commands/entity-selectors.md), [text-components](../systems/foundations/text-components.md) |
| `ResolvableProfile` | [text-components](../systems/foundations/text-components.md), [player-anatomy](../systems/player/player-anatomy.md) |
| `ResolvedModel` | [models-and-atlases](../systems/rendering/models-and-atlases.md) |
| `Resource` | [resource-system](../systems/foundations/resource-system.md) |
| `ResourceFilterSection` | [resource-system](../systems/foundations/resource-system.md) |
| `ResourceKey` | [fanin](../maps/fanin.md), [level-data-and-rules](../reference/level-data-and-rules.md), [blocks-and-states](../systems/blocks/blocks-and-states.md), [advancements](../systems/commands/advancements.md), [entity-anatomy](../systems/entities/entity-anatomy.md), [data-driven-types](../systems/foundations/data-driven-types.md), [identifiers-and-registries](../systems/foundations/identifiers-and-registries.md), [tags](../systems/foundations/tags.md) |
| `ResourceLoadStateTracker` | [resource-system](../systems/foundations/resource-system.md) |
| `ResourceManager` | [identifiers-and-registries](../systems/foundations/identifiers-and-registries.md), [resource-system](../systems/foundations/resource-system.md) |
| `ResourceManagerRegistryLoadTask` | [identifiers-and-registries](../systems/foundations/identifiers-and-registries.md), [tags](../systems/foundations/tags.md) |
| `ResourceManagerReloadListener` | [resource-system](../systems/foundations/resource-system.md) |
| `ResourceMetadata` | [resource-system](../systems/foundations/resource-system.md) |
| `ResourceOrIdArgument` | [brigadier-and-commands](../systems/commands/brigadier-and-commands.md), [contexts-and-predicates](../systems/items/contexts-and-predicates.md) |
| `ResourceOrTagArgument` | [tags](../systems/foundations/tags.md) |
| `ResourceOrTagKeyArgument` | [tags](../systems/foundations/tags.md) |
| `ResourceSelectorArgument` | [naming-drift](../reference/naming-drift.md), [tags](../systems/foundations/tags.md) |
| `ResultContainer` | [recipes](../systems/items/recipes.md) |
| `ResultSlot` | [containers-and-menus](../systems/items/containers-and-menus.md), [recipes](../systems/items/recipes.md) |
| `ReturnCommand` | [the-execution-engine](../systems/commands/the-execution-engine.md) |
| `RootCommandNode` | [brigadier-and-commands](../systems/commands/brigadier-and-commands.md) |
| `RootPlacer` | [data-driven-types](../systems/foundations/data-driven-types.md), [trees](../systems/worldgen/trees.md) |
| `RootPlacerType` | [data-driven-types](../systems/foundations/data-driven-types.md), [trees](../systems/worldgen/trees.md) |
| `RotatingSectionStorage` | [section-meshing](../systems/rendering/section-meshing.md) |
| `Rotation` | [math-and-primitives](../reference/math-and-primitives.md) |
| `Rotations` | [math-and-primitives](../reference/math-and-primitives.md) |
| `RuinedPortalPiece` | [hand-built-structures](../systems/worldgen/hand-built-structures.md) |
| `RuleBlockEntityModifier` | [data-driven-types](../systems/foundations/data-driven-types.md) |
| `RuleBlockEntityModifierType` | [data-driven-types](../systems/foundations/data-driven-types.md) |
| `RuleProcessor` | [jigsaw-and-templates](../systems/worldgen/jigsaw-and-templates.md) |
| `RuleTest` | [data-driven-types](../systems/foundations/data-driven-types.md) |
| `RuleTestType` | [data-driven-types](../systems/foundations/data-driven-types.md) |
| `RunFunction` | [functions-and-macros](../systems/commands/functions-and-macros.md) |
| `RunningOnDifferentThreadException` | [the-connection](../systems/networking/the-connection.md), [server-tick](../systems/server/server-tick.md) |
| `RunOne` | [ai-goals-and-brains](../systems/entities/ai-goals-and-brains.md) |
| `SampleLogger` | [debugging-the-running-game](../systems/client/debugging-the-running-game.md), [server-tick](../systems/server/server-tick.md) |
| `SamplerCache` | [blaze3d](../systems/rendering/blaze3d.md) |
| `SampleStorage` | [debugging-the-running-game](../systems/client/debugging-the-running-game.md) |
| `SaplingBlock` | [features-and-placement](../systems/worldgen/features-and-placement.md), [trees](../systems/worldgen/trees.md) |
| `SavedData` | [glossary](../reference/glossary.md), [level-data-and-rules](../reference/level-data-and-rules.md), [scoreboard-and-data](../systems/commands/scoreboard-and-data.md), [contexts-and-predicates](../systems/items/contexts-and-predicates.md), [how-a-server-dies](../systems/server/how-a-server-dies.md), [server-tick](../systems/server/server-tick.md), [environment-attributes-and-timelines](../systems/world/environment-attributes-and-timelines.md), [tickets-and-loading](../systems/world/tickets-and-loading.md), [creating-a-world](../systems/worldgen/creating-a-world.md) |
| `SavedDataStorage` | [level-data-and-rules](../reference/level-data-and-rules.md), [naming-drift](../reference/naming-drift.md), [how-a-server-dies](../systems/server/how-a-server-dies.md), [starting-a-server](../systems/server/starting-a-server.md), [chunk-storage](../systems/world/chunk-storage.md), [creating-a-world](../systems/worldgen/creating-a-world.md) |
| `SavedDataType` | [level-data-and-rules](../reference/level-data-and-rules.md), [creating-a-world](../systems/worldgen/creating-a-world.md) |
| `SavedTick` | [scheduled-ticks](../systems/world/scheduled-ticks.md) |
| `ScaleExponentially` | [enchantments](../systems/items/enchantments.md) |
| `ScatteredFeaturePiece` | [hand-built-structures](../systems/worldgen/hand-built-structures.md) |
| `ScheduleCommand` | [functions-and-macros](../systems/commands/functions-and-macros.md) |
| `ScheduledTick` | [server-level-tick](../systems/server/server-level-tick.md), [scheduled-ticks](../systems/world/scheduled-ticks.md) |
| `ScheduledTickAccess` | [the-client-level](../systems/client/the-client-level.md), [scheduled-ticks](../systems/world/scheduled-ticks.md) |
| `Schema` | [fanin](../maps/fanin.md) |
| `Scope` | [brigadier-and-commands](../systems/commands/brigadier-and-commands.md) |
| `Score` | [scoreboard-and-data](../systems/commands/scoreboard-and-data.md), [data-driven-types](../systems/foundations/data-driven-types.md) |
| `ScoreAccess` | [glossary](../reference/glossary.md), [scoreboard-and-data](../systems/commands/scoreboard-and-data.md) |
| `Scoreboard` | [scoreboard-and-data](../systems/commands/scoreboard-and-data.md) |
| `ScoreboardCommand` | [brigadier-and-commands](../systems/commands/brigadier-and-commands.md) |
| `ScoreboardNameProvider` | [data-driven-types](../systems/foundations/data-driven-types.md) |
| `ScoreboardNameProviders` | [data-driven-types](../systems/foundations/data-driven-types.md) |
| `ScoreboardSaveData` | [level-data-and-rules](../reference/level-data-and-rules.md), [scoreboard-and-data](../systems/commands/scoreboard-and-data.md) |
| `ScoreContents` | [scoreboard-and-data](../systems/commands/scoreboard-and-data.md), [text-components](../systems/foundations/text-components.md) |
| `ScoreHolder` | [scoreboard-and-data](../systems/commands/scoreboard-and-data.md), [entity-anatomy](../systems/entities/entity-anatomy.md), [text-components](../systems/foundations/text-components.md) |
| `ScoreHolderArgument` | [brigadier-and-commands](../systems/commands/brigadier-and-commands.md), [scoreboard-and-data](../systems/commands/scoreboard-and-data.md) |
| `Screen` | [hierarchy](../maps/hierarchy.md), [hud-elements](../reference/hud-elements.md), [naming-drift](../reference/naming-drift.md), [gui-and-screens](../systems/client/gui-and-screens.md), [the-client-loop](../systems/client/the-client-loop.md), [permissions](../systems/commands/permissions.md), [post-processing](../systems/rendering/post-processing.md) |
| `ScreenAxis` | [gui-and-screens](../systems/client/gui-and-screens.md) |
| `ScreenDirection` | [gui-and-screens](../systems/client/gui-and-screens.md) |
| `ScreenEffectRenderer` | [lightmap-fog-and-sky](../systems/rendering/lightmap-fog-and-sky.md) |
| `ScreenNarrationCollector` | [gui-and-screens](../systems/client/gui-and-screens.md) |
| `ScreenPosition` | [gui-and-screens](../systems/client/gui-and-screens.md) |
| `ScreenRectangle` | [gui-and-screens](../systems/client/gui-and-screens.md) |
| `Screenshot` | [text-components](../systems/foundations/text-components.md), [the-window](../systems/rendering/the-window.md) |
| `ScrollWheelHandler` | [input-and-keybinds](../systems/client/input-and-keybinds.md) |
| `SculkCatalystBlockEntity` | [game-events-and-vibrations](../systems/world/game-events-and-vibrations.md) |
| `SculkSensorBlock` | [game-events-and-vibrations](../systems/world/game-events-and-vibrations.md), [scheduled-ticks](../systems/world/scheduled-ticks.md) |
| `SculkSensorBlockEntity` | [game-events-and-vibrations](../systems/world/game-events-and-vibrations.md) |
| `SculkSensorPhase` | [game-events-and-vibrations](../systems/world/game-events-and-vibrations.md) |
| `SculkShriekerBlock` | [game-events-and-vibrations](../systems/world/game-events-and-vibrations.md) |
| `SculkShriekerBlockEntity` | [game-events-and-vibrations](../systems/world/game-events-and-vibrations.md) |
| `SecondaryPoiSensor` | [ai-goals-and-brains](../systems/entities/ai-goals-and-brains.md) |
| `SectionBufferBuilderPack` | [section-meshing](../systems/rendering/section-meshing.md) |
| `SectionBufferBuilderPool` | [section-meshing](../systems/rendering/section-meshing.md) |
| `SectionCompiler` | [block-entity-rendering](../systems/rendering/block-entity-rendering.md), [models-and-atlases](../systems/rendering/models-and-atlases.md), [section-meshing](../systems/rendering/section-meshing.md) |
| `SectionCopy` | [section-meshing](../systems/rendering/section-meshing.md), [chunk-anatomy](../systems/world/chunk-anatomy.md) |
| `SectionOcclusionGraph` | [visibility-and-the-frame-graph](../systems/rendering/visibility-and-the-frame-graph.md) |
| `SectionPos` | [math-and-primitives](../reference/math-and-primitives.md), [IV · The world](../systems/world/README.md), [game-events-and-vibrations](../systems/world/game-events-and-vibrations.md), [lighting](../systems/world/lighting.md), [tickets-and-loading](../systems/world/tickets-and-loading.md) |
| `SectionRenderDispatcher` | [naming-drift](../reference/naming-drift.md), [threads](../reference/threads.md), [anatomy](../systems/anatomy/anatomy.md), [the-client-loop](../systems/client/the-client-loop.md), [blaze3d](../systems/rendering/blaze3d.md), [section-meshing](../systems/rendering/section-meshing.md) |
| `SectionStorage` | [how-a-server-dies](../systems/server/how-a-server-dies.md), [server-tick](../systems/server/server-tick.md), [chunk-generation-pipeline](../systems/world/chunk-generation-pipeline.md), [chunk-storage](../systems/world/chunk-storage.md), [points-of-interest](../systems/world/points-of-interest.md) |
| `SectionTaskDynamicQueue` | [section-meshing](../systems/rendering/section-meshing.md) |
| `SectionTracker` | [lighting](../systems/world/lighting.md), [points-of-interest](../systems/world/points-of-interest.md) |
| `SectionUpdateTracker` | [section-meshing](../systems/rendering/section-meshing.md), [lighting](../systems/world/lighting.md) |
| `SeedCommand` | [permissions](../systems/commands/permissions.md) |
| `SeededContainerLoot` | [loot-tables](../systems/items/loot-tables.md) |
| `SelectableRecipe` | [recipes](../systems/items/recipes.md) |
| `SelectItemModel` | [naming-drift](../reference/naming-drift.md) |
| `SelectorContents` | [text-components](../systems/foundations/text-components.md) |
| `SelectWorldScreen` | [creating-a-world](../systems/worldgen/creating-a-world.md) |
| `Sensing` | [ai-goals-and-brains](../systems/entities/ai-goals-and-brains.md) |
| `Sensor` | [ai-goals-and-brains](../systems/entities/ai-goals-and-brains.md) |
| `SequenceFeature` | [features-and-placement](../systems/worldgen/features-and-placement.md) |
| `SequenceFunction` | [data-driven-types](../systems/foundations/data-driven-types.md) |
| `SequentialEntry` | [loot-tables](../systems/items/loot-tables.md) |
| `SerializableChunkData` | [chunk-anatomy](../systems/world/chunk-anatomy.md), [chunk-generation-pipeline](../systems/world/chunk-generation-pipeline.md), [chunk-storage](../systems/world/chunk-storage.md), [lighting](../systems/world/lighting.md), [points-of-interest](../systems/world/points-of-interest.md), [scheduled-ticks](../systems/world/scheduled-ticks.md), [blending](../systems/worldgen/blending.md) |
| `SerializableTickContainer` | [scheduled-ticks](../systems/world/scheduled-ticks.md) |
| `ServerActivityMonitor` | [server-tick](../systems/server/server-tick.md) |
| `ServerAdvancementManager` | [advancements](../systems/commands/advancements.md), [data-driven-types](../systems/foundations/data-driven-types.md), [resource-system](../systems/foundations/resource-system.md) |
| `ServerboundAcceptTeleportationPacket` | [input-to-movement](../systems/player/input-to-movement.md) |
| `ServerboundAttackPacket` | [naming-drift](../reference/naming-drift.md), [synched-entity-data](../systems/entities/synched-entity-data.md), [the-sword-swing](../systems/player/the-sword-swing.md) |
| `ServerboundChangeGameModePacket` | [input-and-keybinds](../systems/client/input-and-keybinds.md) |
| `ServerboundChatAckPacket` | [chat-and-signing](../systems/networking/chat-and-signing.md) |
| `ServerboundChatCommandPacket` | [brigadier-and-commands](../systems/commands/brigadier-and-commands.md), [chat-and-signing](../systems/networking/chat-and-signing.md) |
| `ServerboundChatCommandSignedPacket` | [chat-and-signing](../systems/networking/chat-and-signing.md) |
| `ServerboundChatPacket` | [text-components](../systems/foundations/text-components.md) |
| `ServerboundChatSessionUpdatePacket` | [chat-and-signing](../systems/networking/chat-and-signing.md) |
| `ServerboundChunkBatchReceivedPacket` | [threads](../reference/threads.md), [what-the-client-is-told](../systems/networking/what-the-client-is-told.md), [players-and-sessions](../systems/server/players-and-sessions.md), [tickets-and-loading](../systems/world/tickets-and-loading.md) |
| `ServerboundClientCommandPacket` | [level-data-and-rules](../reference/level-data-and-rules.md) |
| `ServerboundClientInformationPacket` | [options](../systems/client/options.md), [synched-entity-data](../systems/entities/synched-entity-data.md), [protocol-phases](../systems/networking/protocol-phases.md) |
| `ServerboundClientTickEndPacket` | [the-client-loop](../systems/client/the-client-loop.md), [input-to-movement](../systems/player/input-to-movement.md) |
| `ServerboundCommandSuggestionPacket` | [brigadier-and-commands](../systems/commands/brigadier-and-commands.md) |
| `ServerboundConfigurationAcknowledgedPacket` | [protocol-phases](../systems/networking/protocol-phases.md) |
| `ServerboundContainerButtonClickPacket` | [containers-and-menus](../systems/items/containers-and-menus.md) |
| `ServerboundContainerClickPacket` | [containers-and-menus](../systems/items/containers-and-menus.md), [packets-and-stream-codecs](../systems/networking/packets-and-stream-codecs.md) |
| `ServerboundContainerClosePacket` | [gui-and-screens](../systems/client/gui-and-screens.md) |
| `ServerboundContainerSlotStateChangedPacket` | [containers-and-menus](../systems/items/containers-and-menus.md) |
| `ServerboundCookieResponsePacket` | [protocol-phases](../systems/networking/protocol-phases.md) |
| `ServerboundCustomClickActionPacket` | [codecs-nbt-json](../systems/foundations/codecs-nbt-json.md) |
| `ServerboundCustomPayloadPacket` | [packets-and-stream-codecs](../systems/networking/packets-and-stream-codecs.md) |
| `ServerboundDebugSubscriptionRequestPacket` | [debugging-the-running-game](../systems/client/debugging-the-running-game.md) |
| `ServerboundFinishConfigurationPacket` | [packets-and-stream-codecs](../systems/networking/packets-and-stream-codecs.md), [protocol-phases](../systems/networking/protocol-phases.md) |
| `ServerboundHelloPacket` | [naming-drift](../reference/naming-drift.md), [protocol-phases](../systems/networking/protocol-phases.md) |
| `ServerboundInteractPacket` | [naming-drift](../reference/naming-drift.md), [synched-entity-data](../systems/entities/synched-entity-data.md) |
| `ServerboundJigsawGeneratePacket` | [jigsaw-and-templates](../systems/worldgen/jigsaw-and-templates.md) |
| `ServerboundKeepAlivePacket` | [threads](../reference/threads.md) |
| `ServerboundKeyPacket` | [naming-drift](../reference/naming-drift.md), [protocol-phases](../systems/networking/protocol-phases.md) |
| `ServerboundLoginAcknowledgedPacket` | [protocol-phases](../systems/networking/protocol-phases.md) |
| `ServerboundMovePlayerPacket` | [authority](../systems/entities/authority.md), [input-to-movement](../systems/player/input-to-movement.md) |
| `ServerboundMoveVehiclePacket` | [the-client-level](../systems/client/the-client-level.md), [authority](../systems/entities/authority.md), [input-to-movement](../systems/player/input-to-movement.md) |
| `ServerboundPingRequestPacket` | [protocol-phases](../systems/networking/protocol-phases.md) |
| `ServerboundPlaceRecipePacket` | [recipes](../systems/items/recipes.md) |
| `ServerboundPlayerAbilitiesPacket` | [player-anatomy](../systems/player/player-anatomy.md) |
| `ServerboundPlayerActionPacket` | [block-breaking](../systems/blocks/block-breaking.md), [block-interaction](../systems/blocks/block-interaction.md), [prediction-and-acks](../systems/client/prediction-and-acks.md), [using-an-item](../systems/items/using-an-item.md), [the-spear](../systems/player/the-spear.md) |
| `ServerboundPlayerCommandPacket` | [input-to-movement](../systems/player/input-to-movement.md) |
| `ServerboundPlayerInputPacket` | [naming-drift](../reference/naming-drift.md), [input-to-movement](../systems/player/input-to-movement.md) |
| `ServerboundPlayerLoadedPacket` | [players-and-sessions](../systems/server/players-and-sessions.md) |
| `ServerboundResourcePackPacket` | [resource-system](../systems/foundations/resource-system.md), [packets-and-stream-codecs](../systems/networking/packets-and-stream-codecs.md) |
| `ServerboundSeenAdvancementsPacket` | [advancements](../systems/commands/advancements.md) |
| `ServerboundSelectBundleItemPacket` | [containers-and-menus](../systems/items/containers-and-menus.md) |
| `ServerboundSelectKnownPacks` | [identifiers-and-registries](../systems/foundations/identifiers-and-registries.md) |
| `ServerboundSetCarriedItemPacket` | [block-breaking](../systems/blocks/block-breaking.md), [block-interaction](../systems/blocks/block-interaction.md), [containers-and-menus](../systems/items/containers-and-menus.md), [player-anatomy](../systems/player/player-anatomy.md) |
| `ServerboundSetCreativeModeSlotPacket` | [codecs-nbt-json](../systems/foundations/codecs-nbt-json.md), [containers-and-menus](../systems/items/containers-and-menus.md), [items-and-stacks](../systems/items/items-and-stacks.md), [packets-and-stream-codecs](../systems/networking/packets-and-stream-codecs.md) |
| `ServerboundSetGameRulePacket` | [level-data-and-rules](../reference/level-data-and-rules.md) |
| `ServerboundSetJigsawBlockPacket` | [jigsaw-and-templates](../systems/worldgen/jigsaw-and-templates.md) |
| `ServerboundStatusRequestPacket` | [protocol-phases](../systems/networking/protocol-phases.md) |
| `ServerboundSwingPacket` | [block-interaction](../systems/blocks/block-interaction.md), [packets-and-stream-codecs](../systems/networking/packets-and-stream-codecs.md) |
| `ServerboundUseItemOnPacket` | [block-interaction](../systems/blocks/block-interaction.md), [prediction-and-acks](../systems/client/prediction-and-acks.md) |
| `ServerboundUseItemPacket` | [block-interaction](../systems/blocks/block-interaction.md), [prediction-and-acks](../systems/client/prediction-and-acks.md), [using-an-item](../systems/items/using-an-item.md) |
| `ServerChunkCache` | [level-data-and-rules](../reference/level-data-and-rules.md), [threads](../reference/threads.md), [anatomy](../systems/anatomy/anatomy.md), [block-entities](../systems/blocks/block-entities.md), [what-the-client-is-told](../systems/networking/what-the-client-is-told.md), [input-to-movement](../systems/player/input-to-movement.md), [how-a-server-dies](../systems/server/how-a-server-dies.md), [server-level-tick](../systems/server/server-level-tick.md), [server-tick](../systems/server/server-tick.md), [chunk-generation-pipeline](../systems/world/chunk-generation-pipeline.md), [chunk-storage](../systems/world/chunk-storage.md), [game-events-and-vibrations](../systems/world/game-events-and-vibrations.md), [lighting](../systems/world/lighting.md), [scheduled-ticks](../systems/world/scheduled-ticks.md), [tickets-and-loading](../systems/world/tickets-and-loading.md) |
| `ServerClockManager` | [glossary](../reference/glossary.md), [level-data-and-rules](../reference/level-data-and-rules.md), [naming-drift](../reference/naming-drift.md), [server-level-tick](../systems/server/server-level-tick.md), [server-tick](../systems/server/server-tick.md), [environment-attributes-and-timelines](../systems/world/environment-attributes-and-timelines.md) |
| `ServerCommonPacketListenerImpl` | [biggest](../maps/biggest.md), [synched-entity-data](../systems/entities/synched-entity-data.md), [resource-system](../systems/foundations/resource-system.md), [protocol-phases](../systems/networking/protocol-phases.md), [the-connection](../systems/networking/the-connection.md), [how-a-server-dies](../systems/server/how-a-server-dies.md), [players-and-sessions](../systems/server/players-and-sessions.md), [server-tick](../systems/server/server-tick.md) |
| `ServerConfigurationPacketListenerImpl` | [protocol-phases](../systems/networking/protocol-phases.md), [players-and-sessions](../systems/server/players-and-sessions.md) |
| `ServerConnectionListener` | [anatomy](../systems/anatomy/anatomy.md), [protocol-phases](../systems/networking/protocol-phases.md), [the-connection](../systems/networking/the-connection.md), [how-a-server-dies](../systems/server/how-a-server-dies.md), [players-and-sessions](../systems/server/players-and-sessions.md), [server-tick](../systems/server/server-tick.md), [starting-a-server](../systems/server/starting-a-server.md) |
| `ServerDebugSubscribers` | [debugging-the-running-game](../systems/client/debugging-the-running-game.md), [pathfinding](../systems/entities/pathfinding.md), [server-tick](../systems/server/server-tick.md) |
| `ServerEntity` | [attributes](../systems/entities/attributes.md), [entity-anatomy](../systems/entities/entity-anatomy.md), [movement-and-collision](../systems/entities/movement-and-collision.md), [synched-entity-data](../systems/entities/synched-entity-data.md), [packets-and-stream-codecs](../systems/networking/packets-and-stream-codecs.md), [what-the-client-is-told](../systems/networking/what-the-client-is-told.md), [server-level-tick](../systems/server/server-level-tick.md) |
| `ServerFunctionLibrary` | [functions-and-macros](../systems/commands/functions-and-macros.md), [resource-system](../systems/foundations/resource-system.md), [tags](../systems/foundations/tags.md) |
| `ServerFunctionManager` | [functions-and-macros](../systems/commands/functions-and-macros.md), [resource-system](../systems/foundations/resource-system.md), [tags](../systems/foundations/tags.md), [server-tick](../systems/server/server-tick.md) |
| `ServerGamePacketListenerImpl` | [biggest](../maps/biggest.md), [level-data-and-rules](../reference/level-data-and-rules.md), [block-breaking](../systems/blocks/block-breaking.md), [block-interaction](../systems/blocks/block-interaction.md), [prediction-and-acks](../systems/client/prediction-and-acks.md), [XIII · Commands and data packs](../systems/commands/README.md), [advancements](../systems/commands/advancements.md), [brigadier-and-commands](../systems/commands/brigadier-and-commands.md), [permissions](../systems/commands/permissions.md), [authority](../systems/entities/authority.md), [damage-and-death](../systems/entities/damage-and-death.md), [synched-entity-data](../systems/entities/synched-entity-data.md), [data-components](../systems/foundations/data-components.md), [containers-and-menus](../systems/items/containers-and-menus.md), [enchantments](../systems/items/enchantments.md), [recipes](../systems/items/recipes.md), [using-an-item](../systems/items/using-an-item.md), [chat-and-signing](../systems/networking/chat-and-signing.md), [protocol-phases](../systems/networking/protocol-phases.md), [input-to-movement](../systems/player/input-to-movement.md), [player-anatomy](../systems/player/player-anatomy.md), [the-spear](../systems/player/the-spear.md), [the-sword-swing](../systems/player/the-sword-swing.md), [the-two-phase-tick](../systems/player/the-two-phase-tick.md), [players-and-sessions](../systems/server/players-and-sessions.md), [server-tick](../systems/server/server-tick.md), [tickets-and-loading](../systems/world/tickets-and-loading.md) |
| `ServerHandshakePacketListenerImpl` | [threads](../reference/threads.md), [protocol-phases](../systems/networking/protocol-phases.md), [the-connection](../systems/networking/the-connection.md) |
| `ServerItemCooldowns` | [using-an-item](../systems/items/using-an-item.md) |
| `ServerLevel` | [biggest](../maps/biggest.md), [fanin](../maps/fanin.md), [packages](../maps/packages.md), [glossary](../reference/glossary.md), [level-data-and-rules](../reference/level-data-and-rules.md), [math-and-primitives](../reference/math-and-primitives.md), [naming-drift](../reference/naming-drift.md), [threads](../reference/threads.md), [block-breaking](../systems/blocks/block-breaking.md), [block-entities](../systems/blocks/block-entities.md), [block-interaction](../systems/blocks/block-interaction.md), [blocks-and-states](../systems/blocks/blocks-and-states.md), [pistons-and-block-events](../systems/blocks/pistons-and-block-events.md), [what-makes-a-sound](../systems/client/what-makes-a-sound.md), [entity-selectors](../systems/commands/entity-selectors.md), [functions-and-macros](../systems/commands/functions-and-macros.md), [the-execution-engine](../systems/commands/the-execution-engine.md), [ai-goals-and-brains](../systems/entities/ai-goals-and-brains.md), [attributes](../systems/entities/attributes.md), [authority](../systems/entities/authority.md), [damage-and-death](../systems/entities/damage-and-death.md), [entity-anatomy](../systems/entities/entity-anatomy.md), [entity-lifecycle](../systems/entities/entity-lifecycle.md), [movement-and-collision](../systems/entities/movement-and-collision.md), [pathfinding](../systems/entities/pathfinding.md), [synched-entity-data](../systems/entities/synched-entity-data.md), [identifiers-and-registries](../systems/foundations/identifiers-and-registries.md), [contexts-and-predicates](../systems/items/contexts-and-predicates.md), [enchantments](../systems/items/enchantments.md), [items-and-stacks](../systems/items/items-and-stacks.md), [loot-tables](../systems/items/loot-tables.md), [recipes](../systems/items/recipes.md), [using-an-item](../systems/items/using-an-item.md), [what-the-client-is-told](../systems/networking/what-the-client-is-told.md), [player-anatomy](../systems/player/player-anatomy.md), [the-spear](../systems/player/the-spear.md), [the-sword-swing](../systems/player/the-sword-swing.md), [the-two-phase-tick](../systems/player/the-two-phase-tick.md), [particles](../systems/rendering/particles.md), [III · The server](../systems/server/README.md), [how-a-server-dies](../systems/server/how-a-server-dies.md), [players-and-sessions](../systems/server/players-and-sessions.md), [server-level-tick](../systems/server/server-level-tick.md), [server-tick](../systems/server/server-tick.md), [starting-a-server](../systems/server/starting-a-server.md), [chunk-anatomy](../systems/world/chunk-anatomy.md), [chunk-generation-pipeline](../systems/world/chunk-generation-pipeline.md), [chunk-storage](../systems/world/chunk-storage.md), [environment-attributes-and-timelines](../systems/world/environment-attributes-and-timelines.md), [fluids](../systems/world/fluids.md), [game-events-and-vibrations](../systems/world/game-events-and-vibrations.md), [points-of-interest](../systems/world/points-of-interest.md), [scheduled-ticks](../systems/world/scheduled-ticks.md), [tickets-and-loading](../systems/world/tickets-and-loading.md), [features-and-placement](../systems/worldgen/features-and-placement.md), [jigsaw-and-templates](../systems/worldgen/jigsaw-and-templates.md), [structure-placement](../systems/worldgen/structure-placement.md) |
| `ServerLevelAccessor` | [entity-anatomy](../systems/entities/entity-anatomy.md) |
| `ServerLevelData` | [level-data-and-rules](../reference/level-data-and-rules.md), [starting-a-server](../systems/server/starting-a-server.md) |
| `ServerLinks` | [starting-a-server](../systems/server/starting-a-server.md) |
| `ServerLinksDialog` | [dialogs](../systems/commands/dialogs.md) |
| `ServerLoginPacketListenerImpl` | [threads](../reference/threads.md), [anatomy](../systems/anatomy/anatomy.md), [protocol-phases](../systems/networking/protocol-phases.md), [the-connection](../systems/networking/the-connection.md), [players-and-sessions](../systems/server/players-and-sessions.md) |
| `ServerOpListEntry` | [naming-drift](../reference/naming-drift.md), [players-and-sessions](../systems/server/players-and-sessions.md) |
| `ServerPackCommand` | [brigadier-and-commands](../systems/commands/brigadier-and-commands.md), [resource-system](../systems/foundations/resource-system.md) |
| `ServerPacketListener` | [server-tick](../systems/server/server-tick.md) |
| `ServerPackManager` | [resource-system](../systems/foundations/resource-system.md) |
| `ServerPacksSource` | [resource-system](../systems/foundations/resource-system.md), [starting-a-server](../systems/server/starting-a-server.md) |
| `ServerPlaceRecipe` | [recipes](../systems/items/recipes.md) |
| `ServerPlayer` | [lectures](../lectures.md), [biggest](../maps/biggest.md), [packages](../maps/packages.md), [glossary](../reference/glossary.md), [naming-drift](../reference/naming-drift.md), [block-breaking](../systems/blocks/block-breaking.md), [block-entities](../systems/blocks/block-entities.md), [block-interaction](../systems/blocks/block-interaction.md), [debugging-the-running-game](../systems/client/debugging-the-running-game.md), [XIII · Commands and data packs](../systems/commands/README.md), [advancements](../systems/commands/advancements.md), [brigadier-and-commands](../systems/commands/brigadier-and-commands.md), [dialogs](../systems/commands/dialogs.md), [entity-selectors](../systems/commands/entity-selectors.md), [permissions](../systems/commands/permissions.md), [scoreboard-and-data](../systems/commands/scoreboard-and-data.md), [attributes](../systems/entities/attributes.md), [authority](../systems/entities/authority.md), [damage-and-death](../systems/entities/damage-and-death.md), [entity-lifecycle](../systems/entities/entity-lifecycle.md), [synched-entity-data](../systems/entities/synched-entity-data.md), [codecs-nbt-json](../systems/foundations/codecs-nbt-json.md), [data-components](../systems/foundations/data-components.md), [text-components](../systems/foundations/text-components.md), [containers-and-menus](../systems/items/containers-and-menus.md), [enchanting](../systems/items/enchanting.md), [enchantments](../systems/items/enchantments.md), [items-and-stacks](../systems/items/items-and-stacks.md), [loot-tables](../systems/items/loot-tables.md), [recipes](../systems/items/recipes.md), [using-an-item](../systems/items/using-an-item.md), [IX · Networking](../systems/networking/README.md), [chat-and-signing](../systems/networking/chat-and-signing.md), [protocol-phases](../systems/networking/protocol-phases.md), [what-the-client-is-told](../systems/networking/what-the-client-is-told.md), [VIII · The player](../systems/player/README.md), [hunger-and-experience](../systems/player/hunger-and-experience.md), [input-to-movement](../systems/player/input-to-movement.md), [player-anatomy](../systems/player/player-anatomy.md), [status-effects](../systems/player/status-effects.md), [the-spear](../systems/player/the-spear.md), [the-sword-swing](../systems/player/the-sword-swing.md), [the-two-phase-tick](../systems/player/the-two-phase-tick.md), [how-a-server-dies](../systems/server/how-a-server-dies.md), [players-and-sessions](../systems/server/players-and-sessions.md), [server-level-tick](../systems/server/server-level-tick.md), [server-tick](../systems/server/server-tick.md), [tickets-and-loading](../systems/world/tickets-and-loading.md) |
| `ServerPlayerGameMode` | [block-breaking](../systems/blocks/block-breaking.md), [block-interaction](../systems/blocks/block-interaction.md), [prediction-and-acks](../systems/client/prediction-and-acks.md), [items-and-stacks](../systems/items/items-and-stacks.md), [loot-tables](../systems/items/loot-tables.md), [using-an-item](../systems/items/using-an-item.md), [player-anatomy](../systems/player/player-anatomy.md), [the-two-phase-tick](../systems/player/the-two-phase-tick.md) |
| `ServerRecipeBook` | [what-this-book-skips](../systems/anatomy/what-this-book-skips.md), [recipes](../systems/items/recipes.md), [player-anatomy](../systems/player/player-anatomy.md) |
| `ServerResourcePackConfigurationTask` | [resource-system](../systems/foundations/resource-system.md) |
| `ServerScoreboard` | [level-data-and-rules](../reference/level-data-and-rules.md), [scoreboard-and-data](../systems/commands/scoreboard-and-data.md) |
| `ServerStatsCounter` | [what-this-book-skips](../systems/anatomy/what-this-book-skips.md), [how-a-server-dies](../systems/server/how-a-server-dies.md), [players-and-sessions](../systems/server/players-and-sessions.md) |
| `ServerStatus` | [server-tick](../systems/server/server-tick.md), [starting-a-server](../systems/server/starting-a-server.md) |
| `ServerStatusPacketListenerImpl` | [protocol-phases](../systems/networking/protocol-phases.md), [the-connection](../systems/networking/the-connection.md) |
| `ServerStatusPinger` | [entity-selectors](../systems/commands/entity-selectors.md), [text-components](../systems/foundations/text-components.md), [protocol-phases](../systems/networking/protocol-phases.md), [the-connection](../systems/networking/the-connection.md) |
| `ServerTextFilter` | [starting-a-server](../systems/server/starting-a-server.md) |
| `ServerTickRateManager` | [anatomy](../systems/anatomy/anatomy.md), [server-tick](../systems/server/server-tick.md), [scheduled-ticks](../systems/world/scheduled-ticks.md) |
| `ServerWatchdog` | [glossary](../reference/glossary.md), [threads](../reference/threads.md), [how-a-server-dies](../systems/server/how-a-server-dies.md), [starting-a-server](../systems/server/starting-a-server.md) |
| `ServerWaypointManager` | [level-data-and-rules](../reference/level-data-and-rules.md), [scoreboard-and-data](../systems/commands/scoreboard-and-data.md), [attributes](../systems/entities/attributes.md) |
| `Services` | [starting-a-server](../systems/server/starting-a-server.md) |
| `SetAttributesFunction` | [attributes](../systems/entities/attributes.md) |
| `SetEnchantmentsFunction` | [enchanting](../systems/items/enchanting.md) |
| `SetHiddenState` | [ai-goals-and-brains](../systems/entities/ai-goals-and-brains.md) |
| `SetItemCountFunction` | [naming-drift](../reference/naming-drift.md), [data-driven-types](../systems/foundations/data-driven-types.md) |
| `SetOnceOptionState` | [entity-selectors](../systems/commands/entity-selectors.md) |
| `Settings` | [starting-a-server](../systems/server/starting-a-server.md) |
| `SetValue` | [enchantments](../systems/items/enchantments.md) |
| `SetWalkTargetFromBlockMemory` | [points-of-interest](../systems/world/points-of-interest.md) |
| `ShaderDefines` | [blaze3d](../systems/rendering/blaze3d.md) |
| `ShaderManager` | [resource-system](../systems/foundations/resource-system.md), [blaze3d](../systems/rendering/blaze3d.md), [post-processing](../systems/rendering/post-processing.md) |
| `ShaderSource` | [blaze3d](../systems/rendering/blaze3d.md) |
| `ShadowFeatureRenderer` | [submit-phases](../reference/submit-phases.md) |
| `ShapedRecipe` | [naming-drift](../reference/naming-drift.md), [recipes](../systems/items/recipes.md) |
| `ShapedRecipePattern` | [recipes](../systems/items/recipes.md) |
| `ShapelessRecipe` | [recipes](../systems/items/recipes.md) |
| `ShapeOutlineFeatureRenderer` | [submit-phases](../reference/submit-phases.md) |
| `Shapes` | [math-and-primitives](../reference/math-and-primitives.md), [movement-and-collision](../systems/entities/movement-and-collision.md), [pathfinding](../systems/entities/pathfinding.md), [fluids](../systems/world/fluids.md) |
| `SharedConstants` | [threads](../reference/threads.md), [anatomy](../systems/anatomy/anatomy.md), [block-breaking](../systems/blocks/block-breaking.md), [debugging-the-running-game](../systems/client/debugging-the-running-game.md), [what-makes-a-sound](../systems/client/what-makes-a-sound.md), [brigadier-and-commands](../systems/commands/brigadier-and-commands.md), [dialogs](../systems/commands/dialogs.md), [entity-lifecycle](../systems/entities/entity-lifecycle.md), [chat-and-signing](../systems/networking/chat-and-signing.md), [the-connection](../systems/networking/the-connection.md), [starting-a-server](../systems/server/starting-a-server.md), [blending](../systems/worldgen/blending.md), [creating-a-world](../systems/worldgen/creating-a-world.md), [jigsaw-and-templates](../systems/worldgen/jigsaw-and-templates.md), [terrain](../systems/worldgen/terrain.md) |
| `Shearable` | [entity-anatomy](../systems/entities/entity-anatomy.md), [synched-entity-data](../systems/entities/synched-entity-data.md) |
| `ShearsItem` | [block-breaking](../systems/blocks/block-breaking.md) |
| `Sheep` | [VI · Entities](../systems/entities/README.md), [synched-entity-data](../systems/entities/synched-entity-data.md), [data-components](../systems/foundations/data-components.md) |
| `SheepRenderer` | [synched-entity-data](../systems/entities/synched-entity-data.md) |
| `SheepRenderState` | [synched-entity-data](../systems/entities/synched-entity-data.md) |
| `SheepWoolLayer` | [synched-entity-data](../systems/entities/synched-entity-data.md) |
| `SheepWoolUndercoatLayer` | [synched-entity-data](../systems/entities/synched-entity-data.md) |
| `ShelfRenderState` | [block-entity-rendering](../systems/rendering/block-entity-rendering.md) |
| `ShieldItem` | [damage-and-death](../systems/entities/damage-and-death.md) |
| `ShipwreckPieces` | [hand-built-structures](../systems/worldgen/hand-built-structures.md) |
| `ShovelItem` | [data-components](../systems/foundations/data-components.md) |
| `Shulker` | [the-client-level](../systems/client/the-client-level.md), [movement-and-collision](../systems/entities/movement-and-collision.md) |
| `ShulkerBoxBlock` | [loot-tables](../systems/items/loot-tables.md) |
| `ShulkerBoxBlockEntity` | [block-entities](../systems/blocks/block-entities.md) |
| `ShulkerBoxSpecialRenderer` | [block-entity-rendering](../systems/rendering/block-entity-rendering.md) |
| `ShulkerBullet` | [non-living-damage](../reference/non-living-damage.md), [damage-and-death](../systems/entities/damage-and-death.md), [entity-lifecycle](../systems/entities/entity-lifecycle.md) |
| `SignableCommand` | [brigadier-and-commands](../systems/commands/brigadier-and-commands.md), [chat-and-signing](../systems/networking/chat-and-signing.md) |
| `SignalGetter` | [diodes-and-observers](../systems/blocks/diodes-and-observers.md), [pistons-and-block-events](../systems/blocks/pistons-and-block-events.md), [signal-and-dust](../systems/blocks/signal-and-dust.md) |
| `SignBlockEntity` | [block-entities](../systems/blocks/block-entities.md), [dialogs](../systems/commands/dialogs.md), [permissions](../systems/commands/permissions.md) |
| `SignedArgument` | [brigadier-and-commands](../systems/commands/brigadier-and-commands.md), [chat-and-signing](../systems/networking/chat-and-signing.md) |
| `SignedMessageBody` | [chat-and-signing](../systems/networking/chat-and-signing.md) |
| `SignedMessageChain` | [naming-drift](../reference/naming-drift.md), [chat-and-signing](../systems/networking/chat-and-signing.md) |
| `SignedMessageLink` | [naming-drift](../reference/naming-drift.md), [chat-and-signing](../systems/networking/chat-and-signing.md) |
| `SignedMessageValidator` | [chat-and-signing](../systems/networking/chat-and-signing.md) |
| `SignRenderState` | [block-entity-rendering](../systems/rendering/block-entity-rendering.md) |
| `SignText` | [block-entity-rendering](../systems/rendering/block-entity-rendering.md) |
| `SilentInitException` | [the-client-loop](../systems/client/the-client-loop.md) |
| `SimpleBitStorage` | [chunk-anatomy](../systems/world/chunk-anatomy.md) |
| `SimpleContainer` | [containers-and-menus](../systems/items/containers-and-menus.md) |
| `SimpleCriterionTrigger` | [advancements](../systems/commands/advancements.md) |
| `SimpleDialog` | [dialogs](../systems/commands/dialogs.md) |
| `SimpleFeatureRenderPhase` | [submit-phases](../reference/submit-phases.md) |
| `SimpleJsonResourceReloadListener` | [codecs-nbt-json](../systems/foundations/codecs-nbt-json.md), [data-driven-types](../systems/foundations/data-driven-types.md), [resource-system](../systems/foundations/resource-system.md) |
| `SimpleModelWrapper` | [models-and-atlases](../systems/rendering/models-and-atlases.md) |
| `SimplePreparableReloadListener` | [resource-system](../systems/foundations/resource-system.md), [recipes](../systems/items/recipes.md), [post-processing](../systems/rendering/post-processing.md) |
| `SimpleRandomSelectorFeature` | [features-and-placement](../systems/worldgen/features-and-placement.md) |
| `SimpleRegionStorage` | [how-a-server-dies](../systems/server/how-a-server-dies.md), [chunk-storage](../systems/world/chunk-storage.md), [blending](../systems/worldgen/blending.md) |
| `SimpleReloadInstance` | [resource-system](../systems/foundations/resource-system.md) |
| `SimpleSoundInstance` | [sound-engine](../systems/client/sound-engine.md) |
| `SimpleUnboundProtocol` | [packets-and-stream-codecs](../systems/networking/packets-and-stream-codecs.md), [protocol-phases](../systems/networking/protocol-phases.md) |
| `SimpleWaterloggedBlock` | [fluids](../systems/world/fluids.md) |
| `SimplexNoise` | [density-functions](../systems/worldgen/density-functions.md) |
| `SimulationChunkTracker` | [tickets-and-loading](../systems/world/tickets-and-loading.md) |
| `SingleEnchantment` | [enchanting](../systems/items/enchanting.md) |
| `SingleFile` | [models-and-atlases](../systems/rendering/models-and-atlases.md) |
| `SingleOptionInput` | [dialogs](../systems/commands/dialogs.md) |
| `SinglePieceStructure` | [hand-built-structures](../systems/worldgen/hand-built-structures.md) |
| `SinglePoolElement` | [jigsaw-and-templates](../systems/worldgen/jigsaw-and-templates.md) |
| `SingleQuadParticle` | [naming-drift](../reference/naming-drift.md), [submit-phases](../reference/submit-phases.md), [particles](../systems/rendering/particles.md) |
| `SingleSpriteSource` | [text-and-fonts](../systems/client/text-and-fonts.md) |
| `SingleThreadedRandomSource` | [math-and-primitives](../reference/math-and-primitives.md) |
| `SingleTickProfiler` | [the-client-loop](../systems/client/the-client-loop.md), [server-tick](../systems/server/server-tick.md) |
| `SingletonArgumentInfo` | [brigadier-and-commands](../systems/commands/brigadier-and-commands.md) |
| `SingleValuePalette` | [chunk-anatomy](../systems/world/chunk-anatomy.md) |
| `SingleVariant` | [models-and-atlases](../systems/rendering/models-and-atlases.md) |
| `SkinManager` | [entity-rendering](../systems/rendering/entity-rendering.md) |
| `SkinTextureDownloader` | [entity-rendering](../systems/rendering/entity-rendering.md) |
| `SkipPacketDecoderException` | [packets-and-stream-codecs](../systems/networking/packets-and-stream-codecs.md), [the-connection](../systems/networking/the-connection.md) |
| `SkipPacketEncoderException` | [packets-and-stream-codecs](../systems/networking/packets-and-stream-codecs.md), [the-connection](../systems/networking/the-connection.md) |
| `SkipPacketException` | [packets-and-stream-codecs](../systems/networking/packets-and-stream-codecs.md), [the-connection](../systems/networking/the-connection.md) |
| `SkullBlockRenderer` | [block-entity-rendering](../systems/rendering/block-entity-rendering.md) |
| `SkyLightEngine` | [lighting](../systems/world/lighting.md) |
| `SkyLightSectionStorage` | [lighting](../systems/world/lighting.md) |
| `SkyRenderer` | [lightmap-fog-and-sky](../systems/rendering/lightmap-fog-and-sky.md), [environment-attributes-and-timelines](../systems/world/environment-attributes-and-timelines.md) |
| `SkyRenderState` | [lightmap-fog-and-sky](../systems/rendering/lightmap-fog-and-sky.md) |
| `SleepInBed` | [ai-goals-and-brains](../systems/entities/ai-goals-and-brains.md), [points-of-interest](../systems/world/points-of-interest.md) |
| `SleepStatus` | [server-level-tick](../systems/server/server-level-tick.md) |
| `SliceShape` | [math-and-primitives](../reference/math-and-primitives.md) |
| `Slot` | [containers-and-menus](../systems/items/containers-and-menus.md) |
| `SlotDisplay` | [data-driven-types](../systems/foundations/data-driven-types.md), [recipes](../systems/items/recipes.md), [packets-and-stream-codecs](../systems/networking/packets-and-stream-codecs.md) |
| `SlotDisplayContext` | [contexts-and-predicates](../systems/items/contexts-and-predicates.md), [recipes](../systems/items/recipes.md) |
| `SlotLoot` | [data-driven-types](../systems/foundations/data-driven-types.md), [loot-tables](../systems/items/loot-tables.md) |
| `SlotProvider` | [entity-anatomy](../systems/entities/entity-anatomy.md) |
| `SlotSource` | [data-driven-types](../systems/foundations/data-driven-types.md), [contexts-and-predicates](../systems/items/contexts-and-predicates.md) |
| `SlotSources` | [data-driven-types](../systems/foundations/data-driven-types.md) |
| `SmithingMenu` | [recipes](../systems/items/recipes.md) |
| `SmoothDouble` | [input-to-movement](../systems/player/input-to-movement.md) |
| `SnbtDatafixer` | [anatomy](../systems/anatomy/anatomy.md) |
| `SnbtGrammar` | [naming-drift](../reference/naming-drift.md), [brigadier-and-commands](../systems/commands/brigadier-and-commands.md), [codecs-nbt-json](../systems/foundations/codecs-nbt-json.md) |
| `SnbtOperations` | [brigadier-and-commands](../systems/commands/brigadier-and-commands.md), [codecs-nbt-json](../systems/foundations/codecs-nbt-json.md) |
| `SnowGolem` | [synched-entity-data](../systems/entities/synched-entity-data.md) |
| `Sound` | [sound-engine](../systems/client/sound-engine.md), [what-makes-a-sound](../systems/client/what-makes-a-sound.md) |
| `SoundBuffer` | [sound-engine](../systems/client/sound-engine.md) |
| `SoundBufferLibrary` | [sound-engine](../systems/client/sound-engine.md) |
| `SoundEngine` | [threads](../reference/threads.md), [sound-engine](../systems/client/sound-engine.md), [what-makes-a-sound](../systems/client/what-makes-a-sound.md) |
| `SoundEngineExecutor` | [threads](../reference/threads.md), [sound-engine](../systems/client/sound-engine.md) |
| `SoundEvent` | [fanin](../maps/fanin.md), [what-makes-a-sound](../systems/client/what-makes-a-sound.md) |
| `SoundEventListener` | [sound-engine](../systems/client/sound-engine.md) |
| `SoundEventRegistration` | [what-makes-a-sound](../systems/client/what-makes-a-sound.md) |
| `SoundEvents` | [biggest](../maps/biggest.md), [fanin](../maps/fanin.md), [non-living-damage](../reference/non-living-damage.md), [what-makes-a-sound](../systems/client/what-makes-a-sound.md), [synched-entity-data](../systems/entities/synched-entity-data.md) |
| `SoundInstance` | [sound-engine](../systems/client/sound-engine.md) |
| `SoundManager` | [sound-engine](../systems/client/sound-engine.md), [the-client-loop](../systems/client/the-client-loop.md), [what-makes-a-sound](../systems/client/what-makes-a-sound.md), [resource-system](../systems/foundations/resource-system.md) |
| `SoundPreviewHandler` | [what-makes-a-sound](../systems/client/what-makes-a-sound.md) |
| `SoundSource` | [sound-engine](../systems/client/sound-engine.md), [what-makes-a-sound](../systems/client/what-makes-a-sound.md) |
| `SoundType` | [what-makes-a-sound](../systems/client/what-makes-a-sound.md) |
| `SourceFilter` | [models-and-atlases](../systems/rendering/models-and-atlases.md) |
| `SpacerElement` | [gui-and-screens](../systems/client/gui-and-screens.md) |
| `SpatialAttributeInterpolator` | [environment-attributes-and-timelines](../systems/world/environment-attributes-and-timelines.md), [biomes](../systems/worldgen/biomes.md) |
| `SpatialLongSet` | [lighting](../systems/world/lighting.md) |
| `SpawnArmorTrimsCommand` | [brigadier-and-commands](../systems/commands/brigadier-and-commands.md) |
| `SpawnCondition` | [data-driven-types](../systems/foundations/data-driven-types.md) |
| `SpawnEggItem` | [entity-lifecycle](../systems/entities/entity-lifecycle.md) |
| `SpawnerBlockEntity` | [entity-lifecycle](../systems/entities/entity-lifecycle.md) |
| `SpawnerRenderState` | [block-entity-rendering](../systems/rendering/block-entity-rendering.md) |
| `SpawnParticlesEffect` | [data-driven-types](../systems/foundations/data-driven-types.md) |
| `SpawnPlacements` | [glossary](../reference/glossary.md), [entity-lifecycle](../systems/entities/entity-lifecycle.md) |
| `SpawnPlacementType` | [naming-drift](../reference/naming-drift.md) |
| `SpawnPlacementTypes` | [naming-drift](../reference/naming-drift.md) |
| `SpawnPrioritySelectors` | [data-driven-types](../systems/foundations/data-driven-types.md) |
| `SpearAnimations` | [the-spear](../systems/player/the-spear.md) |
| `SpearApproach` | [the-spear](../systems/player/the-spear.md) |
| `SpearAttack` | [the-spear](../systems/player/the-spear.md) |
| `SpearRetreat` | [the-spear](../systems/player/the-spear.md) |
| `SpearUseGoal` | [ai-goals-and-brains](../systems/entities/ai-goals-and-brains.md), [the-spear](../systems/player/the-spear.md) |
| `SpecialBlockModelWrapper` | [block-entity-rendering](../systems/rendering/block-entity-rendering.md) |
| `SpecialDates` | [block-entity-rendering](../systems/rendering/block-entity-rendering.md) |
| `SpecialModelRenderer` | [block-entity-rendering](../systems/rendering/block-entity-rendering.md) |
| `SpecialModelRenderers` | [block-entity-rendering](../systems/rendering/block-entity-rendering.md), [models-and-atlases](../systems/rendering/models-and-atlases.md) |
| `SpecialModelWrapper` | [block-entity-rendering](../systems/rendering/block-entity-rendering.md) |
| `SpectatorGui` | [hud-elements](../reference/hud-elements.md) |
| `SpeleothemBlock` | [scheduled-ticks](../systems/world/scheduled-ticks.md) |
| `Spider` | [post-processing](../systems/rendering/post-processing.md) |
| `SplashManager` | [resource-system](../systems/foundations/resource-system.md) |
| `SpriteContents` | [models-and-atlases](../systems/rendering/models-and-atlases.md) |
| `SpriteId` | [models-and-atlases](../systems/rendering/models-and-atlases.md) |
| `SpriteLoader` | [models-and-atlases](../systems/rendering/models-and-atlases.md) |
| `SpriteSet` | [particles](../systems/rendering/particles.md) |
| `SpriteSourceList` | [models-and-atlases](../systems/rendering/models-and-atlases.md) |
| `SpriteSources` | [models-and-atlases](../systems/rendering/models-and-atlases.md) |
| `SpruceFoliagePlacer` | [trees](../systems/worldgen/trees.md) |
| `SpvSampler` | [blaze3d](../systems/rendering/blaze3d.md) |
| `SpvUniformBuffer` | [blaze3d](../systems/rendering/blaze3d.md) |
| `SpyglassItem` | [using-an-item](../systems/items/using-an-item.md) |
| `StackedContents` | [recipes](../systems/items/recipes.md) |
| `StackedItemContents` | [recipes](../systems/items/recipes.md) |
| `StagedVertexBuffer` | [submit-phases](../reference/submit-phases.md), [blaze3d](../systems/rendering/blaze3d.md), [the-frame](../systems/rendering/the-frame.md) |
| `StagingBuffer` | [blaze3d](../systems/rendering/blaze3d.md), [section-meshing](../systems/rendering/section-meshing.md) |
| `StairBlock` | [blocks-and-states](../systems/blocks/blocks-and-states.md) |
| `StairsShape` | [blocks-and-states](../systems/blocks/blocks-and-states.md) |
| `Stat` | [glossary](../reference/glossary.md), [scoreboard-and-data](../systems/commands/scoreboard-and-data.md) |
| `StateDefinition` | [glossary](../reference/glossary.md), [blocks-and-states](../systems/blocks/blocks-and-states.md), [fluids](../systems/world/fluids.md) |
| `StateHolder` | [V · Blocks](../systems/blocks/README.md), [block-interaction](../systems/blocks/block-interaction.md), [blocks-and-states](../systems/blocks/blocks-and-states.md), [fluids](../systems/world/fluids.md) |
| `StaticCache2D` | [chunk-generation-pipeline](../systems/world/chunk-generation-pipeline.md) |
| `Stats` | [what-this-book-skips](../systems/anatomy/what-this-book-skips.md), [block-breaking](../systems/blocks/block-breaking.md), [block-interaction](../systems/blocks/block-interaction.md), [damage-and-death](../systems/entities/damage-and-death.md), [items-and-stacks](../systems/items/items-and-stacks.md), [recipes](../systems/items/recipes.md), [using-an-item](../systems/items/using-an-item.md) |
| `StatType` | [data-driven-types](../systems/foundations/data-driven-types.md) |
| `StatusProtocols` | [packets-and-stream-codecs](../systems/networking/packets-and-stream-codecs.md), [protocol-phases](../systems/networking/protocol-phases.md) |
| `Std140Builder` | [blaze3d](../systems/rendering/blaze3d.md), [post-processing](../systems/rendering/post-processing.md) |
| `Std140SizeCalculator` | [blaze3d](../systems/rendering/blaze3d.md) |
| `Stitcher` | [models-and-atlases](../systems/rendering/models-and-atlases.md) |
| `StitcherException` | [models-and-atlases](../systems/rendering/models-and-atlases.md) |
| `StonecutterMenu` | [recipes](../systems/items/recipes.md) |
| `StonecutterRecipe` | [recipes](../systems/items/recipes.md) |
| `StopCommand` | [how-a-server-dies](../systems/server/how-a-server-dies.md) |
| `Stopwatches` | [level-data-and-rules](../reference/level-data-and-rules.md), [starting-a-server](../systems/server/starting-a-server.md) |
| `StorageDataAccessor` | [scoreboard-and-data](../systems/commands/scoreboard-and-data.md) |
| `StorageDataSource` | [text-components](../systems/foundations/text-components.md) |
| `StraightTrunkPlacer` | [trees](../systems/worldgen/trees.md) |
| `Strategy` | [naming-drift](../reference/naming-drift.md), [chunk-anatomy](../systems/world/chunk-anatomy.md) |
| `StreamCodec` | [fanin](../maps/fanin.md), [glossary](../reference/glossary.md), [naming-drift](../reference/naming-drift.md), [synched-entity-data](../systems/entities/synched-entity-data.md), [codecs-nbt-json](../systems/foundations/codecs-nbt-json.md), [data-driven-types](../systems/foundations/data-driven-types.md), [recipes](../systems/items/recipes.md), [packets-and-stream-codecs](../systems/networking/packets-and-stream-codecs.md), [the-connection](../systems/networking/the-connection.md) |
| `StreamDecoder` | [packets-and-stream-codecs](../systems/networking/packets-and-stream-codecs.md) |
| `StreamEncoder` | [packets-and-stream-codecs](../systems/networking/packets-and-stream-codecs.md) |
| `StreamMemberEncoder` | [packets-and-stream-codecs](../systems/networking/packets-and-stream-codecs.md) |
| `StreamTagVisitor` | [codecs-nbt-json](../systems/foundations/codecs-nbt-json.md) |
| `StrictJsonParser` | [codecs-nbt-json](../systems/foundations/codecs-nbt-json.md) |
| `Strider` | [pathfinding](../systems/entities/pathfinding.md) |
| `StringDecomposer` | [text-and-fonts](../systems/client/text-and-fonts.md), [text-components](../systems/foundations/text-components.md) |
| `StringReader` | [advancements](../systems/commands/advancements.md), [brigadier-and-commands](../systems/commands/brigadier-and-commands.md), [identifiers-and-registries](../systems/foundations/identifiers-and-registries.md) |
| `StringSplitter` | [text-and-fonts](../systems/client/text-and-fonts.md) |
| `StringTag` | [codecs-nbt-json](../systems/foundations/codecs-nbt-json.md) |
| `StringTemplate` | [dialogs](../systems/commands/dialogs.md), [functions-and-macros](../systems/commands/functions-and-macros.md) |
| `StringUtil` | [chat-and-signing](../systems/networking/chat-and-signing.md) |
| `StrollAroundPoi` | [ai-goals-and-brains](../systems/entities/ai-goals-and-brains.md) |
| `StrollToPoi` | [ai-goals-and-brains](../systems/entities/ai-goals-and-brains.md) |
| `StrollToPoiList` | [ai-goals-and-brains](../systems/entities/ai-goals-and-brains.md) |
| `StrongholdPieces` | [biggest](../maps/biggest.md), [hand-built-structures](../systems/worldgen/hand-built-structures.md) |
| `StrongholdStructure` | [hand-built-structures](../systems/worldgen/hand-built-structures.md) |
| `Structure` | [glossary](../reference/glossary.md), [naming-drift](../reference/naming-drift.md), [data-driven-types](../systems/foundations/data-driven-types.md), [biomes](../systems/worldgen/biomes.md), [hand-built-structures](../systems/worldgen/hand-built-structures.md), [structure-placement](../systems/worldgen/structure-placement.md) |
| `StructureAccess` | [chunk-anatomy](../systems/world/chunk-anatomy.md) |
| `StructureCheck` | [codecs-nbt-json](../systems/foundations/codecs-nbt-json.md), [chunk-generation-pipeline](../systems/world/chunk-generation-pipeline.md), [chunk-storage](../systems/world/chunk-storage.md), [hand-built-structures](../systems/worldgen/hand-built-structures.md), [structure-placement](../systems/worldgen/structure-placement.md) |
| `StructureCheckResult` | [structure-placement](../systems/worldgen/structure-placement.md) |
| `StructureGridSpawner` | [game-tests](../systems/commands/game-tests.md) |
| `StructureManager` | [structure-placement](../systems/worldgen/structure-placement.md) |
| `StructurePiece` | [loot-tables](../systems/items/loot-tables.md), [hand-built-structures](../systems/worldgen/hand-built-structures.md), [jigsaw-and-templates](../systems/worldgen/jigsaw-and-templates.md), [structure-placement](../systems/worldgen/structure-placement.md) |
| `StructurePieceAccessor` | [hand-built-structures](../systems/worldgen/hand-built-structures.md) |
| `StructurePiecesBuilder` | [hand-built-structures](../systems/worldgen/hand-built-structures.md), [jigsaw-and-templates](../systems/worldgen/jigsaw-and-templates.md) |
| `StructurePieceSerializationContext` | [data-driven-types](../systems/foundations/data-driven-types.md) |
| `StructurePieceType` | [glossary](../reference/glossary.md), [data-driven-types](../systems/foundations/data-driven-types.md) |
| `StructurePlacement` | [data-driven-types](../systems/foundations/data-driven-types.md), [hand-built-structures](../systems/worldgen/hand-built-structures.md), [structure-placement](../systems/worldgen/structure-placement.md) |
| `StructurePlacementType` | [data-driven-types](../systems/foundations/data-driven-types.md) |
| `StructurePlaceSettings` | [jigsaw-and-templates](../systems/worldgen/jigsaw-and-templates.md) |
| `StructurePoolElement` | [data-driven-types](../systems/foundations/data-driven-types.md), [jigsaw-and-templates](../systems/worldgen/jigsaw-and-templates.md) |
| `StructurePoolElementType` | [data-driven-types](../systems/foundations/data-driven-types.md) |
| `StructureProcessor` | [data-driven-types](../systems/foundations/data-driven-types.md), [hand-built-structures](../systems/worldgen/hand-built-structures.md), [jigsaw-and-templates](../systems/worldgen/jigsaw-and-templates.md) |
| `StructureProcessorList` | [jigsaw-and-templates](../systems/worldgen/jigsaw-and-templates.md) |
| `StructureSet` | [structure-placement](../systems/worldgen/structure-placement.md) |
| `StructureStart` | [what-the-client-is-told](../systems/networking/what-the-client-is-told.md), [hand-built-structures](../systems/worldgen/hand-built-structures.md), [jigsaw-and-templates](../systems/worldgen/jigsaw-and-templates.md), [structure-placement](../systems/worldgen/structure-placement.md) |
| `StructureTags` | [tags](../systems/foundations/tags.md) |
| `StructureTemplate` | [block-entities](../systems/blocks/block-entities.md), [hand-built-structures](../systems/worldgen/hand-built-structures.md), [jigsaw-and-templates](../systems/worldgen/jigsaw-and-templates.md), [trees](../systems/worldgen/trees.md) |
| `StructureTemplateManager` | [resource-system](../systems/foundations/resource-system.md), [starting-a-server](../systems/server/starting-a-server.md), [jigsaw-and-templates](../systems/worldgen/jigsaw-and-templates.md) |
| `StructureTemplatePool` | [jigsaw-and-templates](../systems/worldgen/jigsaw-and-templates.md) |
| `StructureType` | [data-driven-types](../systems/foundations/data-driven-types.md) |
| `StructureUtils` | [game-tests](../systems/commands/game-tests.md), [scheduled-ticks](../systems/world/scheduled-ticks.md) |
| `Style` | [naming-drift](../reference/naming-drift.md), [text-and-fonts](../systems/client/text-and-fonts.md), [text-components](../systems/foundations/text-components.md) |
| `StyleArgument` | [brigadier-and-commands](../systems/commands/brigadier-and-commands.md) |
| `SubmitNodeCollection` | [submit-phases](../reference/submit-phases.md), [text-and-fonts](../systems/client/text-and-fonts.md), [entity-rendering](../systems/rendering/entity-rendering.md), [post-processing](../systems/rendering/post-processing.md) |
| `SubmitNodeCollector` | [naming-drift](../reference/naming-drift.md), [submit-phases](../reference/submit-phases.md), [block-entity-rendering](../systems/rendering/block-entity-rendering.md), [entity-rendering](../systems/rendering/entity-rendering.md), [the-frame](../systems/rendering/the-frame.md) |
| `SubmitNodeStorage` | [glossary](../reference/glossary.md), [naming-drift](../reference/naming-drift.md), [entity-rendering](../systems/rendering/entity-rendering.md), [the-frame](../systems/rendering/the-frame.md) |
| `SubStringSource` | [text-and-fonts](../systems/client/text-and-fonts.md) |
| `SubtitleOverlay` | [hud-elements](../reference/hud-elements.md), [sound-engine](../systems/client/sound-engine.md) |
| `SuggestionProviders` | [brigadier-and-commands](../systems/commands/brigadier-and-commands.md) |
| `SuggestionSupplier` | [brigadier-and-commands](../systems/commands/brigadier-and-commands.md) |
| `SulfurCube` | [synched-entity-data](../systems/entities/synched-entity-data.md) |
| `SummonCommand` | [entity-anatomy](../systems/entities/entity-anatomy.md), [entity-lifecycle](../systems/entities/entity-lifecycle.md) |
| `SummonEntityEffect` | [enchantments](../systems/items/enchantments.md) |
| `SuppressedExceptionCollector` | [how-a-server-dies](../systems/server/how-a-server-dies.md), [server-tick](../systems/server/server-tick.md) |
| `SurfaceRuleData` | [what-this-book-skips](../systems/anatomy/what-this-book-skips.md) |
| `SurfaceRules` | [data-driven-types](../systems/foundations/data-driven-types.md), [terrain](../systems/worldgen/terrain.md) |
| `SurfaceSystem` | [density-functions](../systems/worldgen/density-functions.md), [terrain](../systems/worldgen/terrain.md) |
| `SuspiciousStewEffects` | [hunger-and-experience](../systems/player/hunger-and-experience.md), [status-effects](../systems/player/status-effects.md) |
| `SwampHutPiece` | [hand-built-structures](../systems/worldgen/hand-built-structures.md) |
| `SweetBerryBushBlock` | [authority](../systems/entities/authority.md) |
| `SwimNodeEvaluator` | [pathfinding](../systems/entities/pathfinding.md) |
| `SwingAnimation` | [data-components](../systems/foundations/data-components.md), [the-sword-swing](../systems/player/the-sword-swing.md) |
| `SwingAnimationType` | [the-spear](../systems/player/the-spear.md) |
| `SwizzleArgument` | [brigadier-and-commands](../systems/commands/brigadier-and-commands.md) |
| `SyncedDataHolder` | [entity-anatomy](../systems/entities/entity-anatomy.md), [synched-entity-data](../systems/entities/synched-entity-data.md) |
| `SynchedEntityData` | [entity-anatomy](../systems/entities/entity-anatomy.md), [synched-entity-data](../systems/entities/synched-entity-data.md) |
| `SynchronizeRegistriesTask` | [identifiers-and-registries](../systems/foundations/identifiers-and-registries.md), [tags](../systems/foundations/tags.md), [protocol-phases](../systems/networking/protocol-phases.md) |
| `TabOrderedElement` | [gui-and-screens](../systems/client/gui-and-screens.md) |
| `Tag` | [glossary](../reference/glossary.md), [codecs-nbt-json](../systems/foundations/codecs-nbt-json.md), [text-components](../systems/foundations/text-components.md) |
| `TagBuilder` | [tags](../systems/foundations/tags.md) |
| `TagEntry` | [tags](../systems/foundations/tags.md), [loot-tables](../systems/items/loot-tables.md) |
| `TagFile` | [tags](../systems/foundations/tags.md) |
| `TagKey` | [identifiers-and-registries](../systems/foundations/identifiers-and-registries.md), [tags](../systems/foundations/tags.md) |
| `TagLoader` | [identifiers-and-registries](../systems/foundations/identifiers-and-registries.md), [resource-system](../systems/foundations/resource-system.md), [tags](../systems/foundations/tags.md), [starting-a-server](../systems/server/starting-a-server.md) |
| `TagNetworkSerialization` | [tags](../systems/foundations/tags.md) |
| `TagParser` | [brigadier-and-commands](../systems/commands/brigadier-and-commands.md), [codecs-nbt-json](../systems/foundations/codecs-nbt-json.md) |
| `TagsProvider` | [tags](../systems/foundations/tags.md) |
| `TagType` | [codecs-nbt-json](../systems/foundations/codecs-nbt-json.md) |
| `TagValueInput` | [codecs-nbt-json](../systems/foundations/codecs-nbt-json.md) |
| `TagValueOutput` | [codecs-nbt-json](../systems/foundations/codecs-nbt-json.md) |
| `Target` | [pathfinding](../systems/entities/pathfinding.md) |
| `TargetBlock` | [scheduled-ticks](../systems/world/scheduled-ticks.md) |
| `TargetedConditionalEffect` | [enchantments](../systems/items/enchantments.md) |
| `Targeting` | [entity-anatomy](../systems/entities/entity-anatomy.md) |
| `TargetingConditions` | [ai-goals-and-brains](../systems/entities/ai-goals-and-brains.md) |
| `TaskScheduler` | [tickets-and-loading](../systems/world/tickets-and-loading.md) |
| `Team` | [scoreboard-and-data](../systems/commands/scoreboard-and-data.md), [text-components](../systems/foundations/text-components.md) |
| `TeamColor` | [naming-drift](../reference/naming-drift.md), [scoreboard-and-data](../systems/commands/scoreboard-and-data.md) |
| `TeamColorArgument` | [naming-drift](../reference/naming-drift.md) |
| `TeamCommand` | [brigadier-and-commands](../systems/commands/brigadier-and-commands.md) |
| `TeamMsgCommand` | [entity-selectors](../systems/commands/entity-selectors.md) |
| `TelemetryEventType` | [what-this-book-skips](../systems/anatomy/what-this-book-skips.md) |
| `TelemetryProperty` | [what-this-book-skips](../systems/anatomy/what-this-book-skips.md) |
| `TeleportRandomlyConsumeEffect` | [hunger-and-experience](../systems/player/hunger-and-experience.md) |
| `TeleportTransition` | [players-and-sessions](../systems/server/players-and-sessions.md) |
| `TemplateStructurePiece` | [hand-built-structures](../systems/worldgen/hand-built-structures.md) |
| `TemptGoal` | [pathfinding](../systems/entities/pathfinding.md) |
| `Term` | [brigadier-and-commands](../systems/commands/brigadier-and-commands.md) |
| `TerrainAdjustment` | [structure-placement](../systems/worldgen/structure-placement.md) |
| `TerrainParticle` | [models-and-atlases](../systems/rendering/models-and-atlases.md), [particles](../systems/rendering/particles.md) |
| `TerrainProvider` | [what-this-book-skips](../systems/anatomy/what-this-book-skips.md) |
| `TestBlock` | [game-tests](../systems/commands/game-tests.md) |
| `TestBlockEditScreen` | [game-tests](../systems/commands/game-tests.md) |
| `TestBlockMode` | [game-tests](../systems/commands/game-tests.md) |
| `TestData` | [naming-drift](../reference/naming-drift.md), [game-tests](../systems/commands/game-tests.md) |
| `TestEnvironmentDefinition` | [naming-drift](../reference/naming-drift.md), [functions-and-macros](../systems/commands/functions-and-macros.md), [game-tests](../systems/commands/game-tests.md), [data-driven-types](../systems/foundations/data-driven-types.md), [environment-attributes-and-timelines](../systems/world/environment-attributes-and-timelines.md) |
| `TestFinder` | [naming-drift](../reference/naming-drift.md) |
| `TestFunctionLoader` | [naming-drift](../reference/naming-drift.md) |
| `TestInstanceBlock` | [naming-drift](../reference/naming-drift.md) |
| `TestInstanceBlockEditScreen` | [game-tests](../systems/commands/game-tests.md) |
| `TestInstanceBlockEntity` | [naming-drift](../reference/naming-drift.md), [game-tests](../systems/commands/game-tests.md) |
| `TestInstanceRenderer` | [game-tests](../systems/commands/game-tests.md), [block-entity-rendering](../systems/rendering/block-entity-rendering.md) |
| `TextColor` | [naming-drift](../reference/naming-drift.md), [text-components](../systems/foundations/text-components.md) |
| `TextComponentTagVisitor` | [text-components](../systems/foundations/text-components.md) |
| `TextFeatureRenderer` | [submit-phases](../reference/submit-phases.md), [text-and-fonts](../systems/client/text-and-fonts.md) |
| `TextInput` | [dialogs](../systems/commands/dialogs.md) |
| `TextInputManager` | [the-client-loop](../systems/client/the-client-loop.md), [the-window](../systems/rendering/the-window.md) |
| `TextRenderable` | [text-and-fonts](../systems/client/text-and-fonts.md) |
| `TextureAtlas` | [blaze3d](../systems/rendering/blaze3d.md), [models-and-atlases](../systems/rendering/models-and-atlases.md) |
| `TextureManager` | [the-client-loop](../systems/client/the-client-loop.md), [resource-system](../systems/foundations/resource-system.md), [models-and-atlases](../systems/rendering/models-and-atlases.md), [the-window](../systems/rendering/the-window.md) |
| `TextureSlots` | [models-and-atlases](../systems/rendering/models-and-atlases.md) |
| `TextureTarget` | [blaze3d](../systems/rendering/blaze3d.md), [post-processing](../systems/rendering/post-processing.md) |
| `TextureTransform` | [blaze3d](../systems/rendering/blaze3d.md) |
| `TheEndBiomeSource` | [biomes](../systems/worldgen/biomes.md) |
| `TheEndGatewayRenderer` | [block-entity-rendering](../systems/rendering/block-entity-rendering.md) |
| `ThreadedLevelLightEngine` | [how-a-server-dies](../systems/server/how-a-server-dies.md), [chunk-generation-pipeline](../systems/world/chunk-generation-pipeline.md), [chunk-storage](../systems/world/chunk-storage.md), [lighting](../systems/world/lighting.md) |
| `ThreadingDetector` | [math-and-primitives](../reference/math-and-primitives.md), [chunk-anatomy](../systems/world/chunk-anatomy.md) |
| `ThreadSafeLegacyRandomSource` | [math-and-primitives](../reference/math-and-primitives.md) |
| `ThreeLayersFeatureSize` | [trees](../systems/worldgen/trees.md) |
| `ThrottlingChunkTaskDispatcher` | [chunk-generation-pipeline](../systems/world/chunk-generation-pipeline.md), [tickets-and-loading](../systems/world/tickets-and-loading.md) |
| `ThrownTrident` | [enchantments](../systems/items/enchantments.md) |
| `TickablePacketListener` | [threads](../reference/threads.md), [anatomy](../systems/anatomy/anatomy.md), [the-connection](../systems/networking/the-connection.md), [server-tick](../systems/server/server-tick.md) |
| `TickableSoundInstance` | [sound-engine](../systems/client/sound-engine.md) |
| `TickableTexture` | [models-and-atlases](../systems/rendering/models-and-atlases.md) |
| `TickAccess` | [scheduled-ticks](../systems/world/scheduled-ticks.md) |
| `TickCommand` | [server-tick](../systems/server/server-tick.md) |
| `TickContainerAccess` | [scheduled-ticks](../systems/world/scheduled-ticks.md) |
| `Ticket` | [tickets-and-loading](../systems/world/tickets-and-loading.md) |
| `TicketStorage` | [glossary](../reference/glossary.md), [level-data-and-rules](../reference/level-data-and-rules.md), [naming-drift](../reference/naming-drift.md), [what-the-client-is-told](../systems/networking/what-the-client-is-told.md), [how-a-server-dies](../systems/server/how-a-server-dies.md), [server-level-tick](../systems/server/server-level-tick.md), [starting-a-server](../systems/server/starting-a-server.md), [tickets-and-loading](../systems/world/tickets-and-loading.md) |
| `TicketType` | [glossary](../reference/glossary.md), [protocol-phases](../systems/networking/protocol-phases.md), [how-a-server-dies](../systems/server/how-a-server-dies.md), [players-and-sessions](../systems/server/players-and-sessions.md), [starting-a-server](../systems/server/starting-a-server.md), [chunk-generation-pipeline](../systems/world/chunk-generation-pipeline.md), [tickets-and-loading](../systems/world/tickets-and-loading.md) |
| `TickingBlockEntity` | [block-entities](../systems/blocks/block-entities.md), [chunk-anatomy](../systems/world/chunk-anatomy.md) |
| `TickPriority` | [diodes-and-observers](../systems/blocks/diodes-and-observers.md), [scheduled-ticks](../systems/world/scheduled-ticks.md) |
| `TickRateManager` | [threads](../reference/threads.md), [anatomy](../systems/anatomy/anatomy.md), [block-entities](../systems/blocks/block-entities.md), [the-client-loop](../systems/client/the-client-loop.md), [entity-lifecycle](../systems/entities/entity-lifecycle.md), [input-to-movement](../systems/player/input-to-movement.md), [block-entity-rendering](../systems/rendering/block-entity-rendering.md), [the-frame](../systems/rendering/the-frame.md), [server-level-tick](../systems/server/server-level-tick.md), [server-tick](../systems/server/server-tick.md), [scheduled-ticks](../systems/world/scheduled-ticks.md) |
| `TickTask` | [server-tick](../systems/server/server-tick.md) |
| `TickThrottler` | [chat-and-signing](../systems/networking/chat-and-signing.md), [input-to-movement](../systems/player/input-to-movement.md) |
| `TiledBlitRenderState` | [the-gui-render-tree](../systems/client/the-gui-render-tree.md) |
| `TimeCommand` | [environment-attributes-and-timelines](../systems/world/environment-attributes-and-timelines.md) |
| `Timeline` | [naming-drift](../reference/naming-drift.md), [VI · Entities](../systems/entities/README.md), [ai-goals-and-brains](../systems/entities/ai-goals-and-brains.md), [identifiers-and-registries](../systems/foundations/identifiers-and-registries.md), [environment-attributes-and-timelines](../systems/world/environment-attributes-and-timelines.md) |
| `Timelines` | [naming-drift](../reference/naming-drift.md), [ai-goals-and-brains](../systems/entities/ai-goals-and-brains.md), [lightmap-fog-and-sky](../systems/rendering/lightmap-fog-and-sky.md), [environment-attributes-and-timelines](../systems/world/environment-attributes-and-timelines.md), [points-of-interest](../systems/world/points-of-interest.md) |
| `TimelineTags` | [tags](../systems/foundations/tags.md), [environment-attributes-and-timelines](../systems/world/environment-attributes-and-timelines.md) |
| `TimerQueue` | [level-data-and-rules](../reference/level-data-and-rules.md), [naming-drift](../reference/naming-drift.md), [server-level-tick](../systems/server/server-level-tick.md), [server-tick](../systems/server/server-tick.md) |
| `TlsfAllocator` | [section-meshing](../systems/rendering/section-meshing.md) |
| `ToggleKeyMapping` | [input-and-keybinds](../systems/client/input-and-keybinds.md), [input-to-movement](../systems/player/input-to-movement.md) |
| `Tool` | [block-breaking](../systems/blocks/block-breaking.md), [data-components](../systems/foundations/data-components.md), [items-and-stacks](../systems/items/items-and-stacks.md) |
| `ToolMaterial` | [block-breaking](../systems/blocks/block-breaking.md) |
| `Tooltip` | [gui-and-screens](../systems/client/gui-and-screens.md) |
| `TooltipDisplay` | [data-components](../systems/foundations/data-components.md) |
| `TpsDebugChart` | [hud](../systems/client/hud.md) |
| `TpsDebugDimensions` | [server-tick](../systems/server/server-tick.md) |
| `TraceableEntity` | [entity-anatomy](../systems/entities/entity-anatomy.md) |
| `TrackingDebugSynchronizer` | [debugging-the-running-game](../systems/client/debugging-the-running-game.md) |
| `TrackingEmitter` | [particles](../systems/rendering/particles.md) |
| `TracyZoneFiller` | [what-this-book-skips](../systems/anatomy/what-this-book-skips.md) |
| `TradeSet` | [contexts-and-predicates](../systems/items/contexts-and-predicates.md), [loot-tables](../systems/items/loot-tables.md) |
| `Transformation` | [math-and-primitives](../reference/math-and-primitives.md) |
| `TransientBlockAllocator` | [blaze3d](../systems/rendering/blaze3d.md) |
| `TransientCraftingContainer` | [containers-and-menus](../systems/items/containers-and-menus.md), [recipes](../systems/items/recipes.md) |
| `TransientEntitySectionManager` | [the-client-level](../systems/client/the-client-level.md), [entity-lifecycle](../systems/entities/entity-lifecycle.md) |
| `TransientMemory` | [blaze3d](../systems/rendering/blaze3d.md) |
| `TranslatableContents` | [text-and-fonts](../systems/client/text-and-fonts.md), [text-components](../systems/foundations/text-components.md) |
| `TranslatableFormatException` | [text-components](../systems/foundations/text-components.md) |
| `TranslucencyPointOfView` | [visibility-and-the-frame-graph](../systems/rendering/visibility-and-the-frame-graph.md) |
| `TranslucentFeatureRenderPhase` | [submit-phases](../reference/submit-phases.md) |
| `TransmuteRecipe` | [recipes](../systems/items/recipes.md) |
| `TreeConfiguration` | [naming-drift](../reference/naming-drift.md), [trees](../systems/worldgen/trees.md) |
| `TreeDecorator` | [data-driven-types](../systems/foundations/data-driven-types.md), [trees](../systems/worldgen/trees.md) |
| `TreeDecoratorType` | [data-driven-types](../systems/foundations/data-driven-types.md) |
| `TreeFeature` | [trees](../systems/worldgen/trees.md) |
| `TreeFeatures` | [what-this-book-skips](../systems/anatomy/what-this-book-skips.md), [trees](../systems/worldgen/trees.md) |
| `TreeGrower` | [naming-drift](../reference/naming-drift.md), [trees](../systems/worldgen/trees.md) |
| `TreeNodePosition` | [advancements](../systems/commands/advancements.md) |
| `TrialSpawner` | [entity-lifecycle](../systems/entities/entity-lifecycle.md), [contexts-and-predicates](../systems/items/contexts-and-predicates.md) |
| `TrialSpawnerStateData` | [contexts-and-predicates](../systems/items/contexts-and-predicates.md) |
| `TridentItem` | [enchantments](../systems/items/enchantments.md), [using-an-item](../systems/items/using-an-item.md) |
| `TridentSpecialRenderer` | [block-entity-rendering](../systems/rendering/block-entity-rendering.md) |
| `TriggerCommand` | [brigadier-and-commands](../systems/commands/brigadier-and-commands.md) |
| `TripWireBlock` | [scheduled-ticks](../systems/world/scheduled-ticks.md) |
| `TrunkPlacer` | [data-driven-types](../systems/foundations/data-driven-types.md), [tags](../systems/foundations/tags.md), [trees](../systems/worldgen/trees.md) |
| `TrunkPlacerType` | [data-driven-types](../systems/foundations/data-driven-types.md) |
| `TrunkVineDecorator` | [trees](../systems/worldgen/trees.md) |
| `TryFindWaterGoal` | [pathfinding](../systems/entities/pathfinding.md) |
| `Tutorial` | [the-client-loop](../systems/client/the-client-loop.md) |
| `TwoLayersFeatureSize` | [trees](../systems/worldgen/trees.md) |
| `TypedDataComponent` | [codecs-nbt-json](../systems/foundations/codecs-nbt-json.md), [data-components](../systems/foundations/data-components.md) |
| `TypedEntityData` | [codecs-nbt-json](../systems/foundations/codecs-nbt-json.md), [data-components](../systems/foundations/data-components.md) |
| `TypedInstance` | [entity-anatomy](../systems/entities/entity-anatomy.md), [tags](../systems/foundations/tags.md), [items-and-stacks](../systems/items/items-and-stacks.md) |
| `UberGpuBuffer` | [naming-drift](../reference/naming-drift.md), [blaze3d](../systems/rendering/blaze3d.md), [section-meshing](../systems/rendering/section-meshing.md) |
| `UiLightmap` | [lightmap-fog-and-sky](../systems/rendering/lightmap-fog-and-sky.md) |
| `UnbakedCuboidGeometry` | [models-and-atlases](../systems/rendering/models-and-atlases.md) |
| `UnbakedGeometry` | [models-and-atlases](../systems/rendering/models-and-atlases.md) |
| `UnbakedGlyph` | [naming-drift](../reference/naming-drift.md), [text-and-fonts](../systems/client/text-and-fonts.md) |
| `UnboundEntryAction` | [functions-and-macros](../systems/commands/functions-and-macros.md) |
| `UnboundProtocol` | [packets-and-stream-codecs](../systems/networking/packets-and-stream-codecs.md), [protocol-phases](../systems/networking/protocol-phases.md) |
| `UnconfiguredPipelineHandler` | [the-connection](../systems/networking/the-connection.md) |
| `UndeadRenderState` | [entity-rendering](../systems/rendering/entity-rendering.md) |
| `UnderwaterAmbientSoundHandler` | [what-makes-a-sound](../systems/client/what-makes-a-sound.md) |
| `Uniform` | [post-processing](../systems/rendering/post-processing.md) |
| `UniformGenerator` | [contexts-and-predicates](../systems/items/contexts-and-predicates.md) |
| `UniformValue` | [what-this-book-skips](../systems/anatomy/what-this-book-skips.md), [post-processing](../systems/rendering/post-processing.md) |
| `Unit` | [math-and-primitives](../reference/math-and-primitives.md), [codecs-nbt-json](../systems/foundations/codecs-nbt-json.md), [enchantments](../systems/items/enchantments.md) |
| `Unstitcher` | [models-and-atlases](../systems/rendering/models-and-atlases.md) |
| `UpdateActivityFromSchedule` | [points-of-interest](../systems/world/points-of-interest.md) |
| `UpgradeData` | [chunk-anatomy](../systems/world/chunk-anatomy.md) |
| `UpwardsBranchingTrunkPlacer` | [trees](../systems/worldgen/trees.md) |
| `UseBonemeal` | [ai-goals-and-brains](../systems/entities/ai-goals-and-brains.md) |
| `UseCooldown` | [using-an-item](../systems/items/using-an-item.md) |
| `UseDuration` | [using-an-item](../systems/items/using-an-item.md) |
| `UseEffects` | [using-an-item](../systems/items/using-an-item.md), [hunger-and-experience](../systems/player/hunger-and-experience.md), [the-spear](../systems/player/the-spear.md) |
| `UseOnContext` | [blocks-and-states](../systems/blocks/blocks-and-states.md) |
| `UseRemainder` | [items-and-stacks](../systems/items/items-and-stacks.md) |
| `UserNameToIdResolver` | [players-and-sessions](../systems/server/players-and-sessions.md) |
| `Utf8String` | [packets-and-stream-codecs](../systems/networking/packets-and-stream-codecs.md) |
| `Util` | [packages](../maps/packages.md), [level-data-and-rules](../reference/level-data-and-rules.md), [math-and-primitives](../reference/math-and-primitives.md), [threads](../reference/threads.md), [anatomy](../systems/anatomy/anatomy.md), [sound-engine](../systems/client/sound-engine.md), [the-client-loop](../systems/client/the-client-loop.md), [entity-selectors](../systems/commands/entity-selectors.md), [identifiers-and-registries](../systems/foundations/identifiers-and-registries.md), [resource-system](../systems/foundations/resource-system.md), [tags](../systems/foundations/tags.md), [recipes](../systems/items/recipes.md), [chat-and-signing](../systems/networking/chat-and-signing.md), [section-meshing](../systems/rendering/section-meshing.md), [visibility-and-the-frame-graph](../systems/rendering/visibility-and-the-frame-graph.md), [how-a-server-dies](../systems/server/how-a-server-dies.md), [server-tick](../systems/server/server-tick.md), [starting-a-server](../systems/server/starting-a-server.md), [chunk-generation-pipeline](../systems/world/chunk-generation-pipeline.md), [chunk-storage](../systems/world/chunk-storage.md), [scheduled-ticks](../systems/world/scheduled-ticks.md) |
| `UUIDUtil` | [packets-and-stream-codecs](../systems/networking/packets-and-stream-codecs.md), [protocol-phases](../systems/networking/protocol-phases.md) |
| `Validatable` | [contexts-and-predicates](../systems/items/contexts-and-predicates.md), [enchantments](../systems/items/enchantments.md) |
| `ValidateNearbyPoi` | [ai-goals-and-brains](../systems/entities/ai-goals-and-brains.md), [points-of-interest](../systems/world/points-of-interest.md) |
| `ValidationContext` | [contexts-and-predicates](../systems/items/contexts-and-predicates.md) |
| `ValidationContextSource` | [contexts-and-predicates](../systems/items/contexts-and-predicates.md) |
| `ValueInput` | [block-entities](../systems/blocks/block-entities.md), [codecs-nbt-json](../systems/foundations/codecs-nbt-json.md) |
| `ValueInputContextHelper` | [codecs-nbt-json](../systems/foundations/codecs-nbt-json.md) |
| `ValueOutput` | [naming-drift](../reference/naming-drift.md), [codecs-nbt-json](../systems/foundations/codecs-nbt-json.md) |
| `VanillaEnchantmentProviders` | [enchanting](../systems/items/enchanting.md) |
| `VanillaPackResources` | [resource-system](../systems/foundations/resource-system.md) |
| `VanillaRegistries` | [what-this-book-skips](../systems/anatomy/what-this-book-skips.md), [identifiers-and-registries](../systems/foundations/identifiers-and-registries.md) |
| `VariantSelector` | [models-and-atlases](../systems/rendering/models-and-atlases.md) |
| `VarInt` | [packets-and-stream-codecs](../systems/networking/packets-and-stream-codecs.md) |
| `Varint21FrameDecoder` | [packets-and-stream-codecs](../systems/networking/packets-and-stream-codecs.md), [the-connection](../systems/networking/the-connection.md) |
| `VarLong` | [packets-and-stream-codecs](../systems/networking/packets-and-stream-codecs.md) |
| `VaultBlockEntity` | [contexts-and-predicates](../systems/items/contexts-and-predicates.md) |
| `VaultRenderState` | [block-entity-rendering](../systems/rendering/block-entity-rendering.md) |
| `Vec2` | [math-and-primitives](../reference/math-and-primitives.md), [input-to-movement](../systems/player/input-to-movement.md) |
| `Vec3` | [fanin](../maps/fanin.md), [math-and-primitives](../reference/math-and-primitives.md), [contexts-and-predicates](../systems/items/contexts-and-predicates.md), [packets-and-stream-codecs](../systems/networking/packets-and-stream-codecs.md) |
| `Vec3Argument` | [glossary](../reference/glossary.md), [brigadier-and-commands](../systems/commands/brigadier-and-commands.md) |
| `Vec3i` | [math-and-primitives](../reference/math-and-primitives.md) |
| `VecDeltaCodec` | [what-the-client-is-told](../systems/networking/what-the-client-is-told.md) |
| `VehicleEntity` | [hierarchy](../maps/hierarchy.md), [non-living-damage](../reference/non-living-damage.md), [damage-and-death](../systems/entities/damage-and-death.md), [the-sword-swing](../systems/player/the-sword-swing.md) |
| `VersionCommand` | [permissions](../systems/commands/permissions.md) |
| `VertexConsumer` | [lightmap-fog-and-sky](../systems/rendering/lightmap-fog-and-sky.md) |
| `VertexFormat` | [blaze3d](../systems/rendering/blaze3d.md) |
| `VertexFormatElement` | [blaze3d](../systems/rendering/blaze3d.md) |
| `VerticalAnchor` | [features-and-placement](../systems/worldgen/features-and-placement.md) |
| `Vex` | [pathfinding](../systems/entities/pathfinding.md) |
| `VibrationParticleOption` | [data-driven-types](../systems/foundations/data-driven-types.md), [game-events-and-vibrations](../systems/world/game-events-and-vibrations.md) |
| `VibrationSelector` | [game-events-and-vibrations](../systems/world/game-events-and-vibrations.md) |
| `VibrationSystem` | [game-events-and-vibrations](../systems/world/game-events-and-vibrations.md) |
| `VideoMode` | [naming-drift](../reference/naming-drift.md), [the-window](../systems/rendering/the-window.md) |
| `VideoSettingsScreen` | [the-window](../systems/rendering/the-window.md) |
| `ViewArea` | [visibility-and-the-frame-graph](../systems/rendering/visibility-and-the-frame-graph.md) |
| `Villager` | [ai-goals-and-brains](../systems/entities/ai-goals-and-brains.md), [entity-lifecycle](../systems/entities/entity-lifecycle.md), [pathfinding](../systems/entities/pathfinding.md), [data-components](../systems/foundations/data-components.md), [environment-attributes-and-timelines](../systems/world/environment-attributes-and-timelines.md), [points-of-interest](../systems/world/points-of-interest.md) |
| `VillagerCalmDown` | [ai-goals-and-brains](../systems/entities/ai-goals-and-brains.md) |
| `VillagerGoalPackages` | [ai-goals-and-brains](../systems/entities/ai-goals-and-brains.md), [points-of-interest](../systems/world/points-of-interest.md) |
| `VillagerMakeLove` | [points-of-interest](../systems/world/points-of-interest.md) |
| `VillagerProfession` | [ai-goals-and-brains](../systems/entities/ai-goals-and-brains.md) |
| `VillagerTrade` | [contexts-and-predicates](../systems/items/contexts-and-predicates.md), [enchanting](../systems/items/enchanting.md) |
| `VillageSiege` | [entity-lifecycle](../systems/entities/entity-lifecycle.md), [server-level-tick](../systems/server/server-level-tick.md), [starting-a-server](../systems/server/starting-a-server.md), [environment-attributes-and-timelines](../systems/world/environment-attributes-and-timelines.md), [points-of-interest](../systems/world/points-of-interest.md) |
| `VisGraph` | [visibility-and-the-frame-graph](../systems/rendering/visibility-and-the-frame-graph.md) |
| `Visibility` | [entity-lifecycle](../systems/entities/entity-lifecycle.md), [server-level-tick](../systems/server/server-level-tick.md), [tickets-and-loading](../systems/world/tickets-and-loading.md) |
| `VisibilitySet` | [visibility-and-the-frame-graph](../systems/rendering/visibility-and-the-frame-graph.md) |
| `VoxelShape` | [math-and-primitives](../reference/math-and-primitives.md), [submit-phases](../reference/submit-phases.md), [movement-and-collision](../systems/entities/movement-and-collision.md), [jigsaw-and-templates](../systems/worldgen/jigsaw-and-templates.md) |
| `VulkanBackend` | [anatomy](../systems/anatomy/anatomy.md), [blaze3d](../systems/rendering/blaze3d.md) |
| `VulkanCommandEncoder` | [blaze3d](../systems/rendering/blaze3d.md) |
| `VulkanGpuSurface` | [blaze3d](../systems/rendering/blaze3d.md) |
| `VulkanRenderPass` | [blaze3d](../systems/rendering/blaze3d.md) |
| `WaitingForResponseScreen` | [dialogs](../systems/commands/dialogs.md) |
| `WakeUp` | [ai-goals-and-brains](../systems/entities/ai-goals-and-brains.md), [points-of-interest](../systems/world/points-of-interest.md) |
| `WalkNodeEvaluator` | [pathfinding](../systems/entities/pathfinding.md) |
| `WallClimberNavigation` | [pathfinding](../systems/entities/pathfinding.md) |
| `WanderingTraderData` | [level-data-and-rules](../reference/level-data-and-rules.md) |
| `WanderingTraderSpawner` | [entity-lifecycle](../systems/entities/entity-lifecycle.md), [server-level-tick](../systems/server/server-level-tick.md), [starting-a-server](../systems/server/starting-a-server.md), [points-of-interest](../systems/world/points-of-interest.md) |
| `Warden` | [ai-goals-and-brains](../systems/entities/ai-goals-and-brains.md), [damage-and-death](../systems/entities/damage-and-death.md), [game-events-and-vibrations](../systems/world/game-events-and-vibrations.md) |
| `WardenAi` | [ai-goals-and-brains](../systems/entities/ai-goals-and-brains.md), [game-events-and-vibrations](../systems/world/game-events-and-vibrations.md) |
| `WardenSpawnTrackerCommand` | [brigadier-and-commands](../systems/commands/brigadier-and-commands.md) |
| `WaterAvoidingRandomStrollGoal` | [ai-goals-and-brains](../systems/entities/ai-goals-and-brains.md) |
| `WaterBoundPathNavigation` | [pathfinding](../systems/entities/pathfinding.md) |
| `WaterFluid` | [fluids](../systems/world/fluids.md) |
| `WaterFogEnvironment` | [lightmap-fog-and-sky](../systems/rendering/lightmap-fog-and-sky.md), [environment-attributes-and-timelines](../systems/world/environment-attributes-and-timelines.md) |
| `WaterloggedTransparentBlock` | [fluids](../systems/world/fluids.md) |
| `WaypointStyleManager` | [hud](../systems/client/hud.md), [resource-system](../systems/foundations/resource-system.md) |
| `Weapon` | [data-components](../systems/foundations/data-components.md), [the-sword-swing](../systems/player/the-sword-swing.md) |
| `WeatherAttributes` | [environment-attributes-and-timelines](../systems/world/environment-attributes-and-timelines.md) |
| `WeatherData` | [level-data-and-rules](../reference/level-data-and-rules.md), [naming-drift](../reference/naming-drift.md), [server-level-tick](../systems/server/server-level-tick.md) |
| `WeatherEffectRenderer` | [lightmap-fog-and-sky](../systems/rendering/lightmap-fog-and-sky.md) |
| `WeatherRenderState` | [lightmap-fog-and-sky](../systems/rendering/lightmap-fog-and-sky.md) |
| `WeighedSoundEvents` | [sound-engine](../systems/client/sound-engine.md), [what-makes-a-sound](../systems/client/what-makes-a-sound.md) |
| `WeightedList` | [particles](../systems/rendering/particles.md) |
| `WeightedRandomSelectorFeature` | [features-and-placement](../systems/worldgen/features-and-placement.md) |
| `WeightedVariants` | [models-and-atlases](../systems/rendering/models-and-atlases.md) |
| `WidgetTooltipHolder` | [gui-and-screens](../systems/client/gui-and-screens.md) |
| `Window` | [anatomy](../systems/anatomy/anatomy.md), [the-client-loop](../systems/client/the-client-loop.md), [the-window](../systems/rendering/the-window.md), [how-a-server-dies](../systems/server/how-a-server-dies.md) |
| `WindowEventHandler` | [the-window](../systems/rendering/the-window.md) |
| `WingsLayer` | [entity-rendering](../systems/rendering/entity-rendering.md) |
| `WitherBoss` | [entity-lifecycle](../systems/entities/entity-lifecycle.md) |
| `Wolf` | [damage-and-death](../systems/entities/damage-and-death.md), [data-components](../systems/foundations/data-components.md) |
| `WoodlandMansionPieces` | [hand-built-structures](../systems/worldgen/hand-built-structures.md) |
| `WorkAtComposter` | [ai-goals-and-brains](../systems/entities/ai-goals-and-brains.md) |
| `WorkAtPoi` | [ai-goals-and-brains](../systems/entities/ai-goals-and-brains.md) |
| `WorldBorder` | [level-data-and-rules](../reference/level-data-and-rules.md), [server-level-tick](../systems/server/server-level-tick.md) |
| `WorldCarver` | [data-driven-types](../systems/foundations/data-driven-types.md), [blending](../systems/worldgen/blending.md), [terrain](../systems/worldgen/terrain.md) |
| `WorldClock` | [level-data-and-rules](../reference/level-data-and-rules.md), [naming-drift](../reference/naming-drift.md), [server-level-tick](../systems/server/server-level-tick.md), [environment-attributes-and-timelines](../systems/world/environment-attributes-and-timelines.md) |
| `WorldClocks` | [level-data-and-rules](../reference/level-data-and-rules.md), [environment-attributes-and-timelines](../systems/world/environment-attributes-and-timelines.md) |
| `WorldCreationContext` | [creating-a-world](../systems/worldgen/creating-a-world.md) |
| `WorldCreationGameRulesScreen` | [creating-a-world](../systems/worldgen/creating-a-world.md) |
| `WorldCreationUiState` | [creating-a-world](../systems/worldgen/creating-a-world.md) |
| `WorldData` | [level-data-and-rules](../reference/level-data-and-rules.md), [starting-a-server](../systems/server/starting-a-server.md) |
| `WorldDataConfiguration` | [level-data-and-rules](../reference/level-data-and-rules.md), [identifiers-and-registries](../systems/foundations/identifiers-and-registries.md), [resource-system](../systems/foundations/resource-system.md) |
| `WorldDimensions` | [level-data-and-rules](../reference/level-data-and-rules.md), [creating-a-world](../systems/worldgen/creating-a-world.md) |
| `WorldGenContext` | [chunk-generation-pipeline](../systems/world/chunk-generation-pipeline.md), [chunk-storage](../systems/world/chunk-storage.md) |
| `WorldGenerationContext` | [features-and-placement](../systems/worldgen/features-and-placement.md) |
| `WorldGenLevel` | [features-and-placement](../systems/worldgen/features-and-placement.md) |
| `WorldgenRandom` | [math-and-primitives](../reference/math-and-primitives.md), [features-and-placement](../systems/worldgen/features-and-placement.md), [structure-placement](../systems/worldgen/structure-placement.md) |
| `WorldGenRegion` | [block-update-flags](../reference/block-update-flags.md), [blocks-and-states](../systems/blocks/blocks-and-states.md), [entity-lifecycle](../systems/entities/entity-lifecycle.md), [chunk-generation-pipeline](../systems/world/chunk-generation-pipeline.md), [environment-attributes-and-timelines](../systems/world/environment-attributes-and-timelines.md), [points-of-interest](../systems/world/points-of-interest.md), [scheduled-ticks](../systems/world/scheduled-ticks.md), [blending](../systems/worldgen/blending.md), [features-and-placement](../systems/worldgen/features-and-placement.md) |
| `WorldGenSettings` | [glossary](../reference/glossary.md), [level-data-and-rules](../reference/level-data-and-rules.md), [starting-a-server](../systems/server/starting-a-server.md), [creating-a-world](../systems/worldgen/creating-a-world.md) |
| `WorldGenTickAccess` | [scheduled-ticks](../systems/world/scheduled-ticks.md) |
| `WorldLoader` | [glossary](../reference/glossary.md), [data-driven-types](../systems/foundations/data-driven-types.md), [identifiers-and-registries](../systems/foundations/identifiers-and-registries.md), [resource-system](../systems/foundations/resource-system.md), [tags](../systems/foundations/tags.md), [starting-a-server](../systems/server/starting-a-server.md), [XII · World generation](../systems/worldgen/README.md), [creating-a-world](../systems/worldgen/creating-a-world.md) |
| `WorldOpenFlows` | [creating-a-world](../systems/worldgen/creating-a-world.md), [features-and-placement](../systems/worldgen/features-and-placement.md) |
| `WorldOptions` | [glossary](../reference/glossary.md), [level-data-and-rules](../reference/level-data-and-rules.md), [chunk-generation-pipeline](../systems/world/chunk-generation-pipeline.md), [creating-a-world](../systems/worldgen/creating-a-world.md) |
| `WorldOptionsScreen` | [permissions](../systems/commands/permissions.md) |
| `WorldPreset` | [glossary](../reference/glossary.md), [creating-a-world](../systems/worldgen/creating-a-world.md) |
| `WorldPresets` | [creating-a-world](../systems/worldgen/creating-a-world.md) |
| `WorldSelectionList` | [creating-a-world](../systems/worldgen/creating-a-world.md) |
| `WorldSessionTelemetryManager` | [advancements](../systems/commands/advancements.md) |
| `WorldStem` | [glossary](../reference/glossary.md), [starting-a-server](../systems/server/starting-a-server.md), [creating-a-world](../systems/worldgen/creating-a-world.md) |
| `WorldUpgrader` | [starting-a-server](../systems/server/starting-a-server.md) |
| `WrappedGoal` | [glossary](../reference/glossary.md), [ai-goals-and-brains](../systems/entities/ai-goals-and-brains.md) |
| `WritableLevelData` | [level-data-and-rules](../reference/level-data-and-rules.md) |
| `WritableRegistry` | [identifiers-and-registries](../systems/foundations/identifiers-and-registries.md) |
| `WrittenBookContent` | [text-components](../systems/foundations/text-components.md) |
| `XoroshiroRandomSource` | [math-and-primitives](../reference/math-and-primitives.md) |
| `YieldJobSite` | [points-of-interest](../systems/world/points-of-interest.md) |
| `ZeroBitStorage` | [chunk-anatomy](../systems/world/chunk-anatomy.md) |
| `Zoglin` | [ai-goals-and-brains](../systems/entities/ai-goals-and-brains.md) |
| `Zombie` | [ai-goals-and-brains](../systems/entities/ai-goals-and-brains.md), [attributes](../systems/entities/attributes.md), [authority](../systems/entities/authority.md), [entity-lifecycle](../systems/entities/entity-lifecycle.md) |
| `ZombieAttackGoal` | [ai-goals-and-brains](../systems/entities/ai-goals-and-brains.md) |
| `ZombieRenderer` | [entity-rendering](../systems/rendering/entity-rendering.md) |
| `ZombieRenderState` | [entity-rendering](../systems/rendering/entity-rendering.md) |
| `ZombifiedPiglin` | [pathfinding](../systems/entities/pathfinding.md) |
---
# The lecture map
The video series is an *ordering* over the system pages, which are the
stable content. Each part's landing page lists its lectures in watching
order and says in a line what each one is; this page assembles the thirteen
orders and states the dependencies between them — where a later part is
assumed by an earlier one, and what to watch first because of it. It was
drafted in pass 3, one part per session, and is confirmed by the owner, who
reads every part with the decompile open, before anything is recorded.
Nothing in [Reference](reference/README.md) is watched, and nothing in the
[maps](maps/README.md) is: the maps are looked at once, the reference pages
are looked up, and a lecture links into them wherever a viewer would pause
the video to read a table.
Because the subject here is the order, nothing below describes a lecture.
What each one is about is said once, on its part's landing page; what is
here is what the order needs — the shape of each part, the lectures that
must be watched together or in a fixed sequence, and the three that are
worth taking out of turn: *environment attributes and timelines* before Part
III, *contexts and predicates* before Part XIII's advancements, and *the
client loop* before Part XI.
The one rule that holds everywhere: **each part assumes only the parts
before it**, and where that is not true the section says so and names the
page to watch early. [The dependencies between parts](#the-dependencies-between-parts),
at the end, draws the whole graph.
## I · Anatomy
1. [Anatomy](systems/anatomy/anatomy.md) — watched first, because every
later diagram's lanes assume it.
2. [What this book skips](systems/anatomy/what-this-book-skips.md) — second
and not last, for the reason the closing paragraph of this page gives.
## II · Foundations
Part II is a stack, watched bottom-up; each page is the machinery the one
above it assumes.
1. [Codecs, NBT and JSON](systems/foundations/codecs-nbt-json.md)
2. [Identifiers and registries](systems/foundations/identifiers-and-registries.md)
3. [The resource system](systems/foundations/resource-system.md)
4. [Tags](systems/foundations/tags.md)
5. [Data components](systems/foundations/data-components.md)
6. [Text components](systems/foundations/text-components.md)
7. [The data-driven type pattern](systems/foundations/data-driven-types.md)
[Math and primitives](reference/math-and-primitives.md) is not a lecture;
it is Reference, and the parts link into it.
## III · The server
Part III is a line into a loop and out again: the loop first, because it is
what the rest of the book lives inside, and the beginning and the end last,
because they are only interesting once you know what they start and stop.
1. [The server tick](systems/server/server-tick.md) — with the next, the
most load-bearing pair in the book after *Anatomy*: seven later parts
assume one lecture or the other.
2. [The level tick](systems/server/server-level-tick.md) — watched
immediately after, and never apart from it.
3. [Players and sessions](systems/server/players-and-sessions.md)
4. [Starting a server](systems/server/starting-a-server.md)
5. [How a server dies](systems/server/how-a-server-dies.md)
Watch [environment attributes and
timelines](systems/world/environment-attributes-and-timelines.md) (Part IV)
before *the level tick* if you want its opening to mean anything.
It costs nothing to take out of order — it depends on nothing but registries
and codecs — and Part III is the earliest of the six other parts that lean
on it.
## IV · The world
Part IV is a conveyor: five pages that hand a chunk along a line, and five
more about the world the line delivers. The conveyor is lectures 2 to 6 and
must be watched in that order — nothing later in the chain can be watched
first. Lecture 1 is off the line on purpose, and the last four can be
watched in any order once the vocabulary page is done.
1. [Environment attributes and
timelines](systems/world/environment-attributes-and-timelines.md) —
first, for the reason Part III already gave: it depends on nothing but
registries and codecs, and nine pages in six other parts depend on it.
2. [Chunk anatomy](systems/world/chunk-anatomy.md) — the vocabulary page.
Everything below assumes it, and so does Part V.
3. [Tickets and loading](systems/world/tickets-and-loading.md)
4. [The chunk generation
pipeline](systems/world/chunk-generation-pipeline.md) — Part XII is the
cargo on this conveyor and cannot be watched before it.
5. [Lighting](systems/world/lighting.md) — self-contained.
6. [Chunk storage](systems/world/chunk-storage.md)
7. [Scheduled ticks](systems/world/scheduled-ticks.md) — Part V's redstone
lecture assumes this one, and so does the next.
8. [Fluids](systems/world/fluids.md)
9. [Game events and vibrations](systems/world/game-events-and-vibrations.md)
10. [Points of interest](systems/world/points-of-interest.md) — Part VI owns
the brain; this owns the index it reads.
[Level data and rules](reference/level-data-and-rules.md) is not a lecture;
it is Reference, and this part and Part III both link into it.
## V · Blocks
Part V is a hub and six spokes, and the hub is watched first because the
other six all reach back into the same figure in it: what `Level.setBlock`
and `LevelChunk.setBlockState` do once the section has been written. Two of
the six are one lecture in two halves.
1. [Blocks and states](systems/blocks/blocks-and-states.md)
2. [Block interaction](systems/blocks/block-interaction.md)
3. [Block breaking](systems/blocks/block-breaking.md) — the same lecture's
other half. Watch it immediately after, and never apart from it.
4. [Block entities](systems/blocks/block-entities.md) — self-contained.
5. [Signal and dust](systems/blocks/signal-and-dust.md) — assumes [scheduled
ticks](systems/world/scheduled-ticks.md) only lightly.
6. [Pistons and block events](systems/blocks/pistons-and-block-events.md)
7. [Diodes and the observer](systems/blocks/diodes-and-observers.md) —
assumes [scheduled ticks](systems/world/scheduled-ticks.md) properly: a
repeater's delay is an entry in that queue.
Part V's two click lectures are the applications of [prediction and
acknowledgement](systems/client/prediction-and-acks.md) in Part X, and that
page's own scenario needs this part's vocabulary. The dependency is
circular and this book cuts it here: both click pages open with the same
statement of the contract, and the machinery waits for Part X.
## VI · Entities
Part VI is a ladder, and the second rung carries the rest of it. Nothing in
the part can be reordered without breaking something, and the one page most
often skipped — *authority* — is the one three later parts link back to.
1. [Entity anatomy](systems/entities/entity-anatomy.md) — the vocabulary the
other eight lectures use.
2. [Authority](systems/entities/authority.md) — short, and Parts VIII, IX
and X all assume it rather than re-deriving it.
3. [Entity lifecycle](systems/entities/entity-lifecycle.md) — assumes
[tickets and loading](systems/world/tickets-and-loading.md) for what
*entity-ticking* means.
4. [Synched entity data](systems/entities/synched-entity-data.md) — the
first of the two channels that describe an entity.
5. [Attributes](systems/entities/attributes.md) — the second. Watch it after
*synched entity data*, because the contrast between the two is the
lesson.
6. [Movement and collision](systems/entities/movement-and-collision.md) —
needs *authority* in front of it, and [blocks and
states](systems/blocks/blocks-and-states.md) for what a collision shape
is.
7. [AI: goals and brains](systems/entities/ai-goals-and-brains.md) — assumes
[environment attributes and
timelines](systems/world/environment-attributes-and-timelines.md) for the
schedule and [points of
interest](systems/world/points-of-interest.md) for the bed.
8. [Pathfinding](systems/entities/pathfinding.md) — the other half of the
same lecture, and watchable on its own once *goals and brains* has said
where the wanted position comes from.
9. [Damage and death](systems/entities/damage-and-death.md)
Part VI must precede Part VIII, which is the player half of nearly every
page here, and it should precede Parts IX and X, both of which lean on
*authority*. It assumes Part IV for what makes an entity tick at all and
Part V for what it collides with.
## VII · Items and inventories
Part VII is two tiers. The first three lectures are the vocabulary and are
watched in order; the last five are three engines that hand each other nothing,
so they can be watched in any order — though the two pairs below want to stay
together, and *contexts and predicates* leans on no stack at all and could come
first.
1. [Items and stacks](systems/items/items-and-stacks.md) — assumes [data
components](systems/foundations/data-components.md) completely.
2. [Using an item](systems/items/using-an-item.md)
3. [Containers and menus](systems/items/containers-and-menus.md) — needs
[the level tick](systems/server/server-level-tick.md) for when a
broadcast happens.
4. [Recipes](systems/items/recipes.md)
5. [Enchantments](systems/items/enchantments.md)
6. [Enchanting](systems/items/enchanting.md) — watch it directly after
*enchantments*.
7. [Contexts and predicates](systems/items/contexts-and-predicates.md) — the
one page in this part that Part XIII needs.
8. [Loot tables](systems/items/loot-tables.md)
Part VII assumes Part II for components, codecs and the reload, Part III for
where in a tick a packet is drained and a broadcast lands, and Part V for how a
chest gets opened. It must precede Part VIII, which is the player's own inventory and
the swing that spends an item's durability. *Contexts and predicates* is a
prerequisite of Part XIII's `/execute if predicate` and of the advancement
system, and is the one lecture here a viewer coming for commands should
watch out of order.
## VIII · The player
Part VIII is a trunk and four branches: two lectures on what a player is and
when it runs, then five more on what a player does, in four independent
groups. Only one group has an internal order — the spear is the sword
swing's sequel.
1. [Player anatomy](systems/player/player-anatomy.md) — the vocabulary
lecture.
2. [The two-phase tick](systems/player/the-two-phase-tick.md) — watch it
immediately after *player anatomy*.
3. [Input to movement](systems/player/input-to-movement.md)
4. [The sword swing](systems/player/the-sword-swing.md)
5. [The spear](systems/player/the-spear.md)
6. [Hunger and experience](systems/player/hunger-and-experience.md)
7. [Status effects](systems/player/status-effects.md)
Part VIII assumes Part VI above everything — [authority](systems/entities/authority.md)
in particular, which is where the whole part's premise is stated — and Part
III for the tick phases the two-phase tick lives between. It assumes Part VII
for the inventory and for [using an item](systems/items/using-an-item.md),
which is the machinery the spear's charge runs on. Nothing later in the book
is needed to watch it, but Part IX and Part X both come back to it.
## IX · Networking
Part IX is one wire and three passengers. The first two lectures are one
lecture in two halves and should be watched together; the last three are
unrelated systems that ride the wire, and each has a different shape.
1. [The connection](systems/networking/the-connection.md)
2. [Packets and stream codecs](systems/networking/packets-and-stream-codecs.md)
— the second half of the same lecture.
3. [Protocol phases](systems/networking/protocol-phases.md)
4. [What the client is told](systems/networking/what-the-client-is-told.md)
5. [Chat and signing](systems/networking/chat-and-signing.md)
Part IX assumes Part III for the tick phases its traffic is timed against,
and Part I's [anatomy](systems/anatomy/anatomy.md) for the two loops — the
client drains packets once per *frame*, and that single fact explains most
of what looks like network jitter. It assumes Part II for codecs and for
`Component`, and Part VI's [authority](systems/entities/authority.md) for
the premise under lecture four. It is a prerequisite of Part X, which is the
same wire watched from the receiving end.
## X · The client
Part X is a hub and its spokes, and the spokes are cadences rather than
stages. Nothing here hands off to anything; every page after the first
answers "when in the client's one loop does *this* happen". Watch the hub
first and then take the rest in any order that suits — except the two pairs
noted below.
1. [The client loop](systems/client/the-client-loop.md) — the hub, and the
page every other page in the part leans on.
2. [The client level](systems/client/the-client-level.md)
3. [Prediction and acknowledgement](systems/client/prediction-and-acks.md)
4. [Input and keybinds](systems/client/input-and-keybinds.md)
5. [Options](systems/client/options.md)
6. [GUI and screens](systems/client/gui-and-screens.md)
7. [The GUI render tree](systems/client/the-gui-render-tree.md)
8. [Text and fonts](systems/client/text-and-fonts.md)
9. [The HUD](systems/client/hud.md)
10. [Sound: the engine](systems/client/sound-engine.md)
11. [What makes a sound happen](systems/client/what-makes-a-sound.md)
12. [Debugging the running game](systems/client/debugging-the-running-game.md)
Six to nine are the part's one internal pipeline — a screen records, the
tree sorts and batches, the text becomes glyphs — and are watched
consecutively. Two and three are the other pair: the ledger lives on
`ClientLevel` and is reached through four of its methods.
Part X assumes [Part IX](systems/networking/README.md), which is the same
wire watched from the sending end, and Part I's
[anatomy](systems/anatomy/anatomy.md) for the two loops. It assumes Part
VI's [authority](systems/entities/authority.md) as the premise under
lectures two and three, and **Part V before lecture three** — Part V's
landing page rules that its two click lectures come first, because they are
the ledger's two applications. Lecture eight assumes Part II's [text
components](systems/foundations/text-components.md). Lecture one is a
prerequisite of Part XI, which begins where it ends, at the acquired
surface.
## XI · Rendering
*A substrate under a pipeline.* Two lectures are what the renderer stands
on and have no trace through the world; the rest are one frame in the order
it happens. The part opens on the frame itself because it is the shortest
way to see the whole shape at once — and because a viewer who has watched
one frame end to end has a reason to care what a `GpuDevice` is.
1. [The frame](systems/rendering/the-frame.md)
2. [The window](systems/rendering/the-window.md)
3. [Blaze3D](systems/rendering/blaze3d.md)
4. [Visibility and the frame graph](systems/rendering/visibility-and-the-frame-graph.md)
5. [Section meshing](systems/rendering/section-meshing.md)
6. [Models and atlases](systems/rendering/models-and-atlases.md)
7. [Entity rendering](systems/rendering/entity-rendering.md)
8. [Block-entity rendering](systems/rendering/block-entity-rendering.md)
9. [Lightmap, fog and sky](systems/rendering/lightmap-fog-and-sky.md)
10. [Particles](systems/rendering/particles.md)
11. [Post-processing](systems/rendering/post-processing.md)
Four and five are a pair — two pages that were one, and still one journey
seen from its two ends — and so are seven and eight, the second written as
the differences from the first. One to three can also be watched one, three,
two.
Part XI assumes two pages of Part X: the [client
loop](systems/client/the-client-loop.md), which is what says when a frame
happens, and [the client level](systems/client/the-client-level.md), for
what the thing being drawn actually is. Lecture six assumes Part II's [resource
system](systems/foundations/resource-system.md); lecture nine assumes Part
IV's [environment attributes and
timelines](systems/world/environment-attributes-and-timelines.md), which
owns the system this part only consumes.
## XII · World generation
A substrate, a pipeline, and a wing — and the wing runs first while being
watched last. The ten lectures below run against the chunk status ladder
rather than along it: a structure is *decided* two statuses before the
biomes and terrain it will stand in exist, and writes its blocks four
statuses later, so keeping the three structure lectures together at the end
costs one forward reference and buys a whole arc in one place.
1. [Density functions](systems/worldgen/density-functions.md)
2. [Biomes](systems/worldgen/biomes.md)
3. [Terrain](systems/worldgen/terrain.md)
4. [Blending at the old-chunk border](systems/worldgen/blending.md)
5. [Features and placement](systems/worldgen/features-and-placement.md)
6. [Trees](systems/worldgen/trees.md)
7. [Structure placement](systems/worldgen/structure-placement.md)
8. [Jigsaw and templates](systems/worldgen/jigsaw-and-templates.md)
9. [Hand-built structures](systems/worldgen/hand-built-structures.md)
10. [Creating a world](systems/worldgen/creating-a-world.md)
Two comes before three: `ChunkPyramid` makes `ChunkStatus.BIOMES` a
requirement of both `ChunkStatus.NOISE` and `ChunkStatus.SURFACE`, and the
surface pass reads the biome. Four needs both, and reaches one status forward
into five and six's. Seven
comes before eight and nine, which are alternatives to each other rather
than a sequence. Ten is the object the other nine read, told last because
it is a tree of everything they explain.
Part XII assumes Part IV's [chunk generation
pipeline](systems/world/chunk-generation-pipeline.md), and hard: it is the
only page that says when any of this runs and what the twelve statuses are,
and eight of the ten lectures here name one. It also assumes Part IV's
[chunk anatomy](systems/world/chunk-anatomy.md) for what is being written
into and Part IV's [environment attributes and
timelines](systems/world/environment-attributes-and-timelines.md) for lecture
two, where `Biome` has been hollowed out into one layer of a modifier stack;
and three Part II lectures — codecs, registries and [the data-driven type
pattern](systems/foundations/data-driven-types.md), whose fifty-six instances
this part owns twenty-six of — because worldgen is the most thoroughly
data-driven system in the game.
## XIII · Commands and data packs
A stack of three floors, and the dependency runs strictly one way: *parse*,
then *execute*, then the four systems built on both. The last four are peers
rather than a sequence — watch them in any order, or only the ones you care
about — but neither of the first two floors is optional for any of them.
1. [Brigadier and commands](systems/commands/brigadier-and-commands.md)
2. [Permissions](systems/commands/permissions.md)
3. [Entity selectors](systems/commands/entity-selectors.md)
4. [The execution engine](systems/commands/the-execution-engine.md)
5. [Functions and macros](systems/commands/functions-and-macros.md)
6. [Advancements](systems/commands/advancements.md)
7. [Scores, teams and stored data](systems/commands/scoreboard-and-data.md)
8. [Dialogs](systems/commands/dialogs.md)
9. [Game tests](systems/commands/game-tests.md)
Two and four are the pair that most changes how a viewer reads everything
else, and two is the one an existing mod author most needs; three sits
between them because it needs the atom two defines and hands its fan-out to
four. Six, seven, eight and nine each assume one through five and nothing
else in this part.
Part XIII assumes Part III's [server tick](systems/server/server-tick.md)
twice over — command functions run near the top of `MinecraftServer.tickChildren`, before any
level ticks, and the connection phase that calls `ServerPlayer.doTick` runs
after the levels, which is what puts a periodic advancement trigger one tick
behind the packet that should have carried it. It assumes Part II's [codecs](systems/foundations/codecs-nbt-json.md)
and [the data-driven type pattern](systems/foundations/data-driven-types.md),
of which dialogs and game tests are the two clearest instances; Part IX's
[connection](systems/networking/the-connection.md) for the Netty/server
thread boundary the command packets cross two different ways; and, for
advancements alone, Part VII's
[contexts and predicates](systems/items/contexts-and-predicates.md), because
a trigger's conditions are loot conditions.
## The dependencies between parts
Every arrow below is a *before you start* entry on a landing page: the part
at the tail is one the part at the head assumes, and not optionally. The
two dependencies every part shares are drawn as boxes but not as edges,
because their arrows would reach almost every node — Part I's [anatomy](systems/anatomy/anatomy.md), for
the threads every diagram's lanes are on, and Part II's
[codecs](systems/foundations/codecs-nbt-json.md) and
[registries](systems/foundations/identifiers-and-registries.md), assumed
wherever something is written to disk, sent on the wire or looked up by
name. Read a solid arrow as *watch before*.
```mermaid
flowchart TB
P1["I · Anatomy"]
P2["II · Foundations"]
P3["III · The server"]
P4["IV · The world"]
P5["V · Blocks"]
P6["VI · Entities"]
P7["VII · Items and inventories"]
P8["VIII · The player"]
P9["IX · Networking"]
P10["X · The client"]
P11["XI · Rendering"]
P12["XII · World generation"]
P13["XIII · Commands and data packs"]
P1 --> P2 --> P3
P3 --> P4
P3 --> P5
P3 --> P6
P3 --> P7
P3 --> P8
P3 --> P9
P3 --> P13
P4 --> P5
P4 --> P6
P4 --> P11
P4 --> P12
P5 --> P6
P5 --> P7
P5 --> P10
P6 --> P8
P6 --> P9
P6 --> P10
P7 --> P8
P7 --> P13
P9 --> P10
P9 --> P13
P10 --> P11
P4 -. "tickets and loading, environment attributes" .-> P3
P10 -. "prediction and acknowledgement, cut at Part V" .-> P5
```
The graph is a line with two knots in it, and the sidebar order is a valid
walk through it: no solid arrow points at an earlier part. The two dashed
arrows are the places where it does not hold, and each is cut on purpose
rather than solved by reordering.
**Part III assumes two pages of Part IV.** [The level
tick](systems/server/server-level-tick.md) uses *entity-ticking* and
*block-ticking* range, which [tickets and
loading](systems/world/tickets-and-loading.md) owns, and its first statement
about the day-night cycle rests on [environment attributes and
timelines](systems/world/environment-attributes-and-timelines.md). The first
is cut by definition — the level tick defines both ranges in two sentences
before it uses them — and the second by order: the environment page depends
on nothing but registries and codecs, so it is the first page of Part IV in
the sidebar, in that part's watch order and in the list above, and it is the
one lecture worth watching before its part.
**Part V and Part X assume each other.** The two click lectures in Part V
are the applications of [prediction and
acknowledgement](systems/client/prediction-and-acks.md), and that page's own
scenario is a block placed against a wall, which needs Part V's vocabulary.
The cut is at Part V: both click pages open with the same four-sentence
statement of the ledger's contract, which is all either needs, and the
machinery waits for Part X, so the whole of Parts V and VI is watched before
that one Part X lecture. Part VI hands something forward to a *different*
Part X page: [the client level](systems/client/the-client-level.md) opens by
saying it is not an authority either, which needs
[authority](systems/entities/authority.md) behind it.
Ten pages carry most of the graph — nine rows below, because the two server
ticks are one dependency in two lectures. The membership rule is
mechanical: **a page that two or more landing pages name under *before you
start***, less the three every part assumes, which are the boxes the figure
draws without edges. A viewer who has watched these ten can take the parts
they belong to in almost any order; a viewer who skips one of them will
find a later part's first surprise unexplained.
| the page | its part | the parts whose landing pages assume it |
|---|---|---|
| [The server tick](systems/server/server-tick.md) and [the level tick](systems/server/server-level-tick.md) | III | IV, V, VI, VII, VIII, IX, XIII — seven of the eight later parts that run on the Server thread, for *which phase* something ran in |
| [Environment attributes and timelines](systems/world/environment-attributes-and-timelines.md) | IV | III, VI, XI, XII — the clock, the schedule, and the colour of the sky |
| [Chunk anatomy](systems/world/chunk-anatomy.md) | IV | V, VI, XII — a block state's home, a ticking entity's chunk, and what terrain is written into |
| [Authority](systems/entities/authority.md) | VI | VIII, IX, X — the premise under every page about a player, and under *what the client is told* |
| [The resource system](systems/foundations/resource-system.md) | II | III, VII, XI — the staged load and its barrier: a server's own data at startup, where recipes and loot tables come from, and the reload the atlases are built by |
| [The connection](systems/networking/the-connection.md) | IX | X, XIII — the thread boundary every packet crosses |
| [Tickets and loading](systems/world/tickets-and-loading.md) | IV | III, VI — what *entity-ticking* means |
| [The data-driven type pattern](systems/foundations/data-driven-types.md) | II | XII, XIII — the *type* field in a data-pack file and the registry it dispatches on; these two parts own most of its instances |
| [Text components](systems/foundations/text-components.md) | II | IX, X — what a chat message and a screen's label are before anything draws them |
Three more pages are a single part's dependency, and each is named in that
part's *before you start* rather than here: [blocks and
states](systems/blocks/blocks-and-states.md) before Part VI, [contexts and
predicates](systems/items/contexts-and-predicates.md) before Part XIII's
advancements, and [the client loop](systems/client/the-client-loop.md)
before Part XI. The last two are the ones a viewer coming for that part
alone most often has to fetch from elsewhere in the book.
Watched straight through, the sidebar order still needs one departure from
itself, and it is now as small as it can be: *environment attributes and
timelines* is the first lecture of Part IV and wants watching before Part
III's second. A viewer coming for one part rather than the whole book takes
that part's *before you start* list as the order.
[What this book skips](systems/anatomy/what-this-book-skips.md) is the
second lecture and not the last: it is the only page that states the
series' boundary, and a boundary is drawn before the investment, not after.
Part XIII's game tests are the closing lecture because they are the game's
own answer to the question the whole book has been asking — how do you know
what it does — and because nothing later depends on them.