Chunk Format

This article describes in additional detail the format of the Chunk Data packet.

Chunks columns and Chunk sections
You've probably heard the term "chunk" before. Minecraft uses chunks to store and transfer world data. However, there are actually 2 different concepts that are both called "chunks" in different contexts: chunk columns and chunk sections.

A chunk column is a 16&times;256&times;16 collection of blocks, and is what most players think of when they hear the term "chunk". However, these are not the smallest unit data is stored in in the game; chunk columns are actually 16 chunk sections aligned vertically.

Chunk columns store biomes, block entities, entities, tick data, and an array of sections.

A chunk section is a 16&times;16&times;16 collection of blocks (chunk sections are cubic). This is the actual area that blocks are stored in, and is often the concept Mojang refers to via "chunk". Breaking columns into sections wouldn't be useful, except that you don't need to send all chunk sections in a column: If a section is empty, then it doesn't need to be sent (more on this later).

Chunk sections store blocks and light data (both block light and sky light). Additionally, they can be associated with a section palette. A chunk section can contain at maximum 4096 (16&times;16&times;16, or 212) unique IDs (but, it is highly unlikely that such a section will occur in normal circumstances).

Chunk columns and chunk sections are both displayed when chunk border rendering is enabled ( F3 + G ). Chunk columns borders are indicated via the red vertical lines, while chunk sections borders are indicated by the blue lines.

Empty sections and the primary bit mask
As previously mentioned, chunk sections can be empty. Sections which contain no useful data are treated as empty, and are not sent to the client, as the client is able to infer the contents. For the average world, this means around 60% of the world's data doesn't need to be sent, since it's all air; this is a significant save.

It is important to note that a chunk composed entirely of empty sections is different from an empty (ie, unloaded) chunk column. When a block is changed in an empty section, the section is created (as all air), and the block is set. When a block is changed in an empty chunk, the behavior is undefined (but generally, nothing happens).

The primary bit mask simply determines which sections are being sent. The least significant bit is for the lowest section (y=0 to y=15). Only 16 bits can be set in it (with the 16th bit controlling the y=240 to y=255 section); sections above y=255 are not valid for the notchian client. To check whether a section is included, use.

Global and section palettes


Minecraft also uses palettes. A palette maps numeric IDs to block states. The concept is more commonly used with colors in an image; Wikipedia's articles on color look-up tables, indexed colors, and palettes in general may be helpful for fully grokking it.

There are 2 palettes that are used in the game: the global palette and the section palette.

The global palette is the standard mapping of IDs to block states. Block state IDs are created in a linear fashion based off of order of assignment. One block state ID allocated for each unique block state for a block; if a block has multiple properties then the number of allocated states is the product of the number of values for each property. Note that the global palette is currently represented by 14 bits per entry. If a block is not found in the global palette, it will be treated as air. The Data Generators system can be used to generate a list of all values in the current global palette.

A section palette is used to map IDs within a chunk section to global palette IDs. Other than skipping empty sections, correct use of the section palette is the biggest place where data can be saved. Given that most sections contain only a few blocks, using 14 bits to represent a chunk section that is only stone, gravel, and air would be extremely wasteful. Instead, a list of IDs are sent mapping indexes to global palette IDs (for instance, ), and indexes within the section palette are used (so stone would be sent as , gravel  , and air  ). The number of bits per ID in the section palette varies from 4 to 8; if fewer than 4 bits would be needed it's increased to 4 and if more than 8 would be needed, the section palette is not used and instead global palette IDs are used.

Full chunk
The full chunk value is one of the more confusing properties of the chunk data packet. It controls two different behaviors of the chunk data packet, one that most people need, and one that most don't.

When full chunk is set to true, the chunk data packet is used to create a new chunk. This includes biome data, and all (non-empty) sections in the chunk. Sections not specified in the primary bit mask are empty sections.

When full chunk is false, then the chunk data packet acts as a large Multi Block Change packet, changing all of the blocks in the given section at once. This can have some performance benefits, especially for lighting purposes. Biome data is not sent when full chunk is false; that means that biomes can't be changed once a chunk is loaded. Sections not specified in the primary bit mask are not changed and should be left as-is.

Data structure
The data section of the packet contains most of the useful data for the chunk.

Chunk Section structure
A Chunk Section is defined in terms of other data types. A Chunk Section consists of the following fields:

Data Array is given for each block with increasing x coordinates, within rows of increasing z coordinates, within layers of increasing y coordinates.

Assuming BitsPerBlock is notchian, Data Array is BitsPerBlock x 512 bytes long.

Light values are handled by a separate Update Light packet.

Palettes
The bits per block value determines what format is used for the palette. In most cases, invalid values will be interpreted as a different value when parsed by the notchian client, meaning that chunk data will be parsed incorrectly if you use an invalid bits per block. Servers must make sure that the bits per block value is correct.

Indirect
There are two variants of this:


 * For bits per block <= 4, 4 bits are used to represent a block.
 * For bits per block between 5 and 8, the given value is used.

This is an actual palette which lists the block states used. Values in the chunk section's data array are indices into the palette, which in turn gives a proper block state.

The format is as follows:

Direct
This format is used for bits per block values greater than or equal to 9. The number of bits used to represent a block are the base 2 logarithm of the number of block states, rounded up. For the current vanilla release, this is 14 bits per block.


 * The "palette" uses the following format:
 * {| class="wikitable"

|- ! Field Name ! Field Type ! Notes |- |colspan="3"| no fields |}

If Minecraft Forge is installed and a sufficiently large number of blocks are added, the bits per block value for the global palette will be increased to compensate for the increased ID count. This increase can go up to 16 bits per block (for a total of 4096 block IDs; when combined with the 16 damage values, there are 65536 total states). You can get the number of blocks with the "Number of ids" field found in the RegistryData packet in the Forge Handshake.

Compacted data array
The data array stores several entries within a single long, and sometimes overlaps one entry between multiple longs. For a bits per block value of 14, the data is stored such that bits 1 through 14 are the first entry, 15 through 28 are the second, and so on. Note that bit 1 is the least significant bit in this case, not the most significant bit. The same behavior applies when a value stretches between two longs: for instance, block 5 would be bits 57 through 64 of the first long and then bits 1 through 6 of the second long.

The Data Array, although varying in length, will never be padded due to the number of blocks being evenly divisible by 64, which is the number of bits in a long.

Example
5 bits per block, containing the following references to blocks in a palette (not shown): 1 2 2 3 4 4 5 6 6 4 8 0 7 4 3 13 <span style="border: solid 2px hsl(120, 90%, 30%)">15 <span style="border: solid 2px hsl(150, 90%, 30%)">16 <span style="border: solid 2px hsl(180, 90%, 30%)">9 <span style="border: solid 2px hsl(210, 90%, 30%)">14 <span style="border: solid 2px hsl(240, 90%, 30%)">10 <span style="border: solid 2px hsl(270, 90%, 30%)">12 <span style="border: solid 2px hsl(300, 90%, 30%)">0 <span style="border: solid 2px hsl(330, 90%, 30%)">2 <span style="border: solid 2px rgb(60%, 60%, 60%)">11 <span style="border: solid 2px rgb(30%, 30%, 30%)">4 (although note that 4 could instead be any other value ending in those bits another value ending with those bits)

-

A second older example: 13 bits per block, using the global palette.

The following two longs would represent...

=  =

9 blocks, with the start of a 10th (that would be finished in the next long).


 * 1) Grass, <span style="border: solid 2px hsl(0, 90%, 65%)">2 :<span style="border: solid 2px hsl(0, 90%, 75%)">0 (0x020)
 * 2) Dirt, <span style="border: solid 2px hsl(40, 90%, 60%)">3 :<span style="border: solid 2px hsl(40, 90%, 70%)">0 (0x030)
 * 3) Dirt, <span style="border: solid 2px hsl(80, 90%, 60%)">3 :<span style="border: solid 2px hsl(80, 90%, 70%)">0 (0x030)
 * 4) Coarse dirt, <span style="border: solid 2px hsl(120, 90%, 60%)">3 :<span style="border: solid 2px hsl(120, 90%, 70%)">1 (0x031)
 * 5) Stone, <span style="border: solid 2px hsl(160, 90%, 60%)">1 :<span style="border: solid 2px hsl(160, 90%, 70%)">0 (0x010)
 * 6) Stone, <span style="border: solid 2px hsl(200, 90%, 60%)">1 :<span style="border: solid 2px hsl(200, 90%, 70%)">0 (0x010)
 * 7) Diorite, <span style="border: solid 2px hsl(240, 90%, 65%)">1 :<span style="border: solid 2px hsl(240, 90%, 75%)">3 (0x013)
 * 8) Gravel, <span style="border: solid 2px hsl(280, 90%, 65%)">13 :<span style="border: solid 2px hsl(280, 90%, 75%)">0 (0x0D0)
 * 9) Gravel, <span style="border: solid 2px hsl(320, 90%, 60%)">13 :<span style="border: solid 2px hsl(320, 90%, 70%)">0 (0x0D0)
 * 10) Stone, <span style="border: solid 2px rgb(60%, 60%, 60%)">1 :<span style="border: solid 2px rgb(70%, 70%, 70%)">0 (or potentially emerald ore, <span style="border: solid 2px rgb(60%, 60%, 60%)">129 :<span style="border: solid 2px rgb(70%, 70%, 70%)">0 ) (0x010 or 0x810)

Biomes
The biomes array is only present when full chunk is set to true. Biomes cannot be changed unless a chunk is re-sent.

The structure is an array of 1024 integers, each representing a (it is recommended that 127 for "Void" is used if there is no set biome). The array is ordered by x then z then y, in 4&times;4&times;4 blocks. The array is indexed by.

Tips and notes
There are several things that can make it easier to implement this format.


 * The  value for full bits per block is likely to change in the future, so it should not be hardcoded (instead, it should either be calculated or left as a constant).
 * Servers do not need to implement the palette initially (instead always using 14 bits per block), although it is an important optimization later on.
 * The Notchian server implementation does not send values that are out of bounds for the palette. If such a value is received, the format is being parsed incorrectly.  In particular, if you're reading a number with all bits set (15, 31, etc), you might be reading skylight data (or you may have a sign error and you're reading negative numbers).
 * The number of longs needed for the data array can be calculated as ((16&times;16&times;16 blocks)&times;Bits per block)&divide;64 bits per long (which simplifies to 64&times;Bits per block). For instance, 14 bits per block requires 896 longs.

Also, note that the Notchian client does not render chunks that lack neighbors. This means that if you only send a fixed set of chunks (and not chunks around the player) then the last chunk will not be seen, although you can still interact with it. This is intended behavior, so that lighting and connected blocks can be handled correctly.

Sample implementations
How the chunk format can be implemented varies largely by how you want to read/write it. It is often easier to read/write the data long-by-long instead of pre-create the data to write; however, storing the chunk data arrays in their packed form can be far more efficient memory- and performance-wise. These implementations are simple versions that can work as a base (especially for dealing with the bit shifting), but are not ideal.

Shared code
This is some basic pseudocode that shows the various types of palettes. It does not handle actually populating the palette based on data in a chunk section; handling this is left as for the implementer since there are many ways of doing so. (This does not apply for the direct version).

Deserializing
When deserializing, it is easy to read to a buffer (since length information is present). A basic example:

Serializing
Serializing the packet is more complicated, because of the palette. It is easy to implement with the full bits per block value; implementing it with a compacting palette is much harder since algorithms to generate and resize the palette must be written. As such, this example does not generate a palette. The palette is a good performance improvement (as it can significantly reduce the amount of data sent), but managing that is much harder and there are a variety of ways of implementing it.

Also note that this implementation doesn't handle situations where full is false (ie, making a large change to one section); it's only good for serializing a full chunk.

Full implementations

 * Java, 1.12.2, writing only, with palette
 * Rust, 1.13.2, with palette
 * Java, 1.9, both sides
 * Python, 1.7 through 1.13. Read/write, paletted/unpaletted, packets/arrays
 * Python, 1.9, reading only
 * C, 1.9, reading only
 * C, 1.11.2, writing only
 * C++, 1.12.2, writing only

Sample data

 * some sample data from 1.13.2, with both complete packets and just the data structures

Old format
The following implement the previous (before 1.9) format:


 * Java, 1.8
 * Python, 1.4
 * Node.js, 1.8