Files
claude-timemachine 02a249a379 verify pass 2: resolve markers via ViaBackwards/ViaRewind/ViaLegacy
Closed most remaining UNCONFIRMED items + caught corrections: 1.7 data[]=proto5,
1.8 multi-block nibble order (z/x), stance y+1.62, 1.15 wolf idx18=begging,
1.18 below-zero sections NOT stripped (claim was wrong), CONTAINER_SLOT_STATE
1.20.3, + pinned first-appeared versions (report details/server links=1.21,
fluidCount=26.1, block-state id14=1.19.4, itemCount VarInt=1.20.5).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 19:13:19 +02:00

22 KiB
Raw Permalink Blame History

Java Edition 1.18.x — Protocol Deep-Dive

Release Protocol # Release date minecraft-data dir ViaVersion package (into this version)
1.18 757 2021-11-30 data/pc/1.18 v1_17_1to1_18
1.18.1 757 2021-12-10 data/pc/1.18.1 (same protocol; no VV package)
1.18.2 758 2022-02-28 data/pc/1.18.2 v1_18to1_18_2

Protocol numbers confirmed from minecraft-data version.json for each dir (1.18→757, 1.18.1→757, 1.18.2→758) and ViaVersion source. 1.18 and 1.18.1 share protocol 757 — 1.18.1 was a critical security fix (Log4j RCE, fog) that did not touch the wire format. Release dates from minecraft.wiki release articles (fetched 2026-06-19): 1.18, 1.18.1, 1.18.2. ViaVersion source at /tmp/mcproto-refs/ViaVersion/.


Headline — Caves & Cliffs Part II: world height expansion

1.18 ("Caves & Cliffs Part II", 2021-11-30) completed the biome and terrain overhaul begun in 1.17. The defining change is world height expansion: the overworld now spans Y = 64 to Y = 319 (384 blocks total), up from 0255 (256 blocks). This is not just a gameplay detail — it fundamentally restructures the chunk data packet:

  • The chunk now carries 24 sections (384 ÷ 16) instead of 16.
  • Biome data moved out of the flat array at chunk level and into a per-section paletted container alongside block states, matching the per-section structure of block data. In 1.17 biomes were a flat varint[] at the top of the chunk packet; in 1.18 each section contains its own 4×4×4 biome palette.
  • The Light Update and Chunk Data packets were merged into a single LEVEL_CHUNK_WITH_LIGHT (0x22), collapsing what had been two separate packets into one.
  • The section presence bitmask was removed; all sections are always serialised, even if empty (filled with the single-value palette of air).

The actual number of packet types is nearly unchanged from 1.17.1 — one new packet (SET_SIMULATION_DISTANCE) and one merge (LEVEL_CHUNK_WITH_LIGHT) are the whole structural delta. The heavy cost is in the chunk encoding / decoding work, not in new protocol concepts.

Source: minecraft.wiki Java Edition 1.18 (fetched 2026-06-19); ViaVersion v1_17_1to1_18/; minecraft-data data/pc/1.17.1 vs data/pc/1.18.


Protocol changes: 1.17.1 (756) → 1.18 (757)

New packet: SET_SIMULATION_DISTANCE (0x57)

1.18 introduced a distinction between view distance (chunks loaded around the player) and simulation distance (chunks in which game logic runs). A new SET_SIMULATION_DISTANCE packet carries a single varint distance field.

minecraft-data confirms: packet_simulation_distance exists in 1.18 types but not in 1.17.1data/pc/1.18/protocol.json play toClient types. This insertion at 0x57 pushed all subsequent clientbound IDs up by one: the IDs from 0x57 onward in 1.18 are one higher than in 1.17.1.

// SET_SIMULATION_DISTANCE (0x57, clientbound, Play)
VarInt   simulationDistance

ViaVersion: Protocol1_17_1To1_18.java line 5775 (serverbound CLIENT_INFORMATION handler) synthesises the simulation distance from view distance since the older client doesn't send it. EntityPacketRewriter1_18.java line 5758 writes simulationDistance (duplicating the view-distance value) into the LOGIN packet.

Source: v1_17_1to1_18/Protocol1_17_1To1_18.java:63-75; v1_17_1to1_18/rewriter/EntityPacketRewriter1_18.java:55-59; minecraft-data data/pc/1.18/protocol.json.

Serverbound CLIENT_INFORMATION: new field + rename

The CLIENT_SETTINGS serverbound packet gained one field and had one renamed:

Field 1.17.1 1.18
locale (string)
viewDistance (i8)
chatFlags (varint)
chatColors (bool)
skinParts (u8)
mainHand (varint)
disableTextFiltering (bool) ✓ (inverted name)
enableTextFiltering (bool) ✓ (renamed)
enableServerListing (bool) added

Source: minecraft-data data/pc/1.17.1/protocol.json vs data/pc/1.18/protocol.json packet_settings; ViaVersion Protocol1_17_1To1_18.java:63-75 strips enableServerListing when translating 1.18 clients toward a 1.17 server (the read(Types.BOOLEAN) at line 73 drops it).

BLOCK_ENTITY_DATA: action field type change (u8 → varint)

The BLOCK_ENTITY_DATA clientbound packet changed its action field type from unsigned byte to varint:

// BLOCK_ENTITY_DATA (0x0A, clientbound, Play) — 1.17.1
BlockPos   location
u8         action    // <-- byte
OptNBT     nbtData

// BLOCK_ENTITY_DATA (0x0A, clientbound, Play) — 1.18
BlockPos   location
VarInt     action    // <-- varint
OptNBT     nbtData

Source: minecraft-data data/pc/1.17.1/protocol.json vs data/pc/1.18/protocol.json packet_tile_entity_data; ViaVersion WorldPacketRewriter1_18.java:56-64 reads the incoming 1.17.1 UNSIGNED_BYTE and writes VAR_INT.

Chunk packet restructure: LEVEL_CHUNK_WITH_LIGHT (0x22)

This is the core change. In 1.17.1 there were two separate packets:

  • LEVEL_CHUNK — chunk data (block states + biomes + block entities, no light)
  • LIGHT_UPDATE — light data for a chunk column (separate timing)

In 1.18 these are merged into LEVEL_CHUNK_WITH_LIGHT (0x22). The light data is appended directly after the chunk section data within the same packet. This is why ViaVersion must buffer the LIGHT_UPDATE, cache it keyed by (chunkX, chunkZ), then emit it when the corresponding LEVEL_CHUNK arrives (or vice versa — see ChunkLightStorage).

Wire layout of LEVEL_CHUNK_WITH_LIGHT (1.18, 0x22):

Int          chunkX
Int          chunkZ
NBT          heightMaps
VarInt       dataLength      // byte count of the sections buffer
[sections]   <ySectionCount sections, each:>
  Short          nonAirBlocksCount
  [blockPalette] paletted container (blocks)
  [biomePalette] paletted container (biomes)   // NEW in 1.18
VarInt       blockEntityCount
[blockEntities] <each: packed_xz (u8) + y (i16) + typeId (varint) + NBT>
// Light data (was separate LIGHT_UPDATE in 1.17.1):
Boolean      trustEdges
LongArray    skyLightMask
LongArray    blockLightMask
LongArray    emptySkyLightMask
LongArray    emptyBlockLightMask
VarInt       skyLightArrayCount
  [byte[2048]] per section
VarInt       blockLightArrayCount
  [byte[2048]] per section

Source: ChunkType1_18.java:49-67 (read), ChunkType1_18.java:71-86 (write); ChunkSectionType1_18.java:51-54 (section read); WorldPacketRewriter1_18.java:105-196.

Comparison with 1.17.1 LEVEL_CHUNK wire layout:

Int          chunkX
Int          chunkZ
LongArray    sectionsMask    // which of the 16 sections are present
NBT          heightMaps
VarInt[]     biomeData       // flat array (1024 ints = 4×4×4 × 16 sections) -- GONE in 1.18
VarInt       dataLength
[sections]   only sections where bit set in mask:
  Short          nonAirBlocksCount
  [blockPalette] paletted container (blocks only; no biome palette here)
NBT[]        blockEntities   // full NBT per entity -- changed in 1.18

Source: ChunkType1_17.java:49-72 (read).

Key structural differences:

Aspect 1.17.1 (756) 1.18 (757)
World height Y 0255, 16 sections Y 64319, 24 sections
Section presence bitmask (BitSet from LongArray) all sections always present
Biome data flat VarInt[] of 1024 at chunk level per-section 4×4×4 paletted container
Block entities full NBT array packed xz (u8) + y (i16) + typeId (varint) + NBT
Light separate LIGHT_UPDATE packet merged into chunk packet
Section count 16 (overworld) 24 (overworld, +2 for border sections in light)

Source: ChunkType1_17.java, ChunkType1_18.java, ChunkSectionType1_18.java; ViaVersion v1_17_1to1_18/.

Biome palette (per-section, 4×4×4)

In 1.18 each section in the chunk carries its own biome data as a paletted container using the same palette encoding as block states. The container covers a 4×4×4 sub-grid (64 biome cells per section), using the PaletteType.BIOMES type with ChunkSection.BIOME_SIZE = 64.

The palette format is identical to block palettes (PaletteType1_18):

  • bits-per-value = 0 → single-value palette (one VarInt entry, zero-length data array) — used when an entire section is one biome
  • 13 bpv → indirect palette (VarInt array of biome IDs + compact long array)
  • direct/global → compact long array, no palette array

The highestBitsPerValue for biomes is 3 in 1.18 (global palette otherwise). Confirmed: PaletteType.java:27 declares BIOMES(ChunkSection.BIOME_SIZE, 3), so any bpv > 3 falls through to the global/direct palette (PaletteType1_18.java:55-56). The global palette width = MathUtil.ceilLog2(tracker.biomesSent()) — for 1.18's 61 registered biomes this is 6 bits.

Source: PaletteType1_18.java:38-171; ChunkSection.java:37 (BIOME_SIZE = 4*4*4); PaletteType.java:27 (BIOMES(ChunkSection.BIOME_SIZE, 3)); WorldPacketRewriter1_18.java:146-155 (ViaVersion filling biome palette from old flat array).

World height in the dimension type NBT

The server communicates world height to clients via the dimension type NBT sent in the LOGIN and RESPAWN packets. The dimension type includes two fields:

  • height (int) — total block height of the dimension
  • min_y (int) — minimum Y coordinate (negative for the expanded overworld: 64)

ViaVersion reads both to determine section count and minimum Y:

// EntityRewriter.java:512-526
CompoundTag registryData = wrapper.get(Types.NAMED_COMPOUND_TAG, nbtIndex);
NumberTag height = registryData.getNumberTag("height");
// height >> 4 = section count (e.g. 384 >> 4 = 24)
tracker.setCurrentWorldSectionHeight(blockHeight >> 4);
NumberTag minY = registryData.getNumberTag("min_y");
tracker.setCurrentMinY(minY.asInt()); // -64 for overworld

The section count tracker.currentWorldSectionHeight() is then passed to ChunkType1_17 / ChunkType1_18 constructors at runtime so chunk reading is dimension-aware — not hardcoded to 24.

Source: EntityRewriter.java:508-531; EntityPacketRewriter1_18.java:43-68 (LOGIN handler calling worldDataTrackerHandler(1)).

Light update caching

Because 1.17.1 sends light and chunk data as separate packets, ViaVersion must buffer one while waiting for the other. The ChunkLightStorage per-connection object:

  • On LIGHT_UPDATE for an unloaded chunk: cancels the packet (does not forward it) and caches the light data keyed by (chunkX, chunkZ).
  • On LIGHT_UPDATE for an already-loaded chunk: passes through (and optionally caches, controlled by cache-1_17-light config).
  • On LEVEL_CHUNK: looks up the cached light, appends it to the merged LEVEL_CHUNK_WITH_LIGHT, then removes it from the cache.
  • On FORGET_LEVEL_CHUNK: clears both the cached light and the loaded-chunk marker.

Source: ChunkLightStorage.java:28-68; WorldPacketRewriter1_18.java:66-103 (LIGHT_UPDATE handler), 105-196 (LEVEL_CHUNK → LEVEL_CHUNK_WITH_LIGHT).

LOGIN packet: simulationDistance added

The LOGIN clientbound packet gained one field between 1.17.1 and 1.18:

// 1.17.1 LOGIN (0x26)                // 1.18 LOGIN (0x26)
Int     entityId                       Int     entityId
Bool    isHardcore                     Bool    isHardcore
Byte    gameMode                       Byte    gameMode
Byte    previousGameMode               Byte    previousGameMode
String[]  worldNames                   String[]  worldNames
NBT     dimensionCodec                 NBT     dimensionCodec
NBT     dimension                      NBT     dimension
String  worldName                      String  worldName
Long    hashedSeed                     Long    hashedSeed
VarInt  maxPlayers                     VarInt  maxPlayers
VarInt  viewDistance                   VarInt  viewDistance
                                       VarInt  simulationDistance  // NEW
Bool    reducedDebugInfo               Bool    reducedDebugInfo
Bool    enableRespawnScreen            Bool    enableRespawnScreen
Bool    isDebug                        Bool    isDebug
Bool    isFlat                         Bool    isFlat

Source: minecraft-data data/pc/1.17.1/protocol.json vs data/pc/1.18/protocol.json packet_login; EntityPacketRewriter1_18.java:43-68.

Particle: barrier (id 2) → block_marker (id 3)

A small entity-data / particle change: the barrier particle (ID 2 in 1.17) became the block_marker particle in 1.18 with an embedded block state, and what was block_marker (ID 3) got a different ID. ViaVersion maps these:

// EntityPacketRewriter1_18.java:86-92
if (particle.id() == 2) { // Barrier
    particle.setId(3); // Block marker
    particle.add(Types.VAR_INT, 7754); // Barrier block state
} else if (particle.id() == 3) { // Light block
    particle.add(Types.VAR_INT, 7786); // Light block state
}

Source: v1_17_1to1_18/rewriter/EntityPacketRewriter1_18.java:85-93.

Tag renames

One block tag was renamed between 1.17 and 1.18:

  • minecraft:lava_pool_stone_replaceablesminecraft:lava_pool_stone_cannot_replace

Source: Protocol1_17_1To1_18.java:82 (tagRewriter.renameTag).


757 — 1.18 (2021-11-30) and 1.18.1 (2021-12-10)

1.18 shipped on 2021-11-30. It is the full Caves & Cliffs Part II release: expanded world height, noise caves, the new terrain generator, new mountain sub-biomes (Meadow, Grove, Snowy Slopes, Jagged Peaks, Frozen Peaks, Stony Peaks), and the 3D biome system decoupled from terrain generation. All protocol changes vs 1.17.1 are documented in the section above.

1.18.1 shipped 2021-12-10, ten days later, as an emergency patch addressing:

  • Critical Log4j2 RCE vulnerability (CVE-2021-44228 / Log4Shell) exploitable via in-game chat messages logged by Log4j on the server side
  • Fog rendering change: fog now applies cylindrically (not spherically) and starts farther from the player
  • Eight bug fixes

Protocol 757 is unchanged between 1.18 and 1.18.1. The Log4j fix is entirely server-side (library update); no wire-format changes. There is no ViaVersion translation package between 1.18 and 1.18.1.

Source: minecraft.wiki Java Edition 1.18.1 (fetched 2026-06-19); minecraft-data data/pc/1.18.1/version.json (version: 757).


758 — 1.18.2 (2022-02-28)

1.18.2 shipped 2022-02-28, bumping the protocol from 757 to 758. The gameplay additions are modest (custom world-gen tag expansion, /placefeature command, new structure and biome tags), and the protocol wire changes are minimal:

Effect ID type: byte → varint (UPDATE_MOB_EFFECT and REMOVE_MOB_EFFECT)

The only structural packet change from 1.18 to 1.18.2 is that the effectId field in two packets changed type from byte (i8) to varint:

UPDATE_MOB_EFFECT (0x65 in 1.18):

// 1.18 (757)                    // 1.18.2 (758)
VarInt  entityId                  VarInt  entityId
byte    effectId   // <-- i8      VarInt  effectId  // <-- varint
byte    amplifier                 byte    amplifier
VarInt  duration                  VarInt  duration
byte    hideParticles             byte    hideParticles

REMOVE_MOB_EFFECT (0x3B in 1.18):

// 1.18 (757)                    // 1.18.2 (758)
VarInt  entityId                  VarInt  entityId
byte    effectId   // <-- i8      VarInt  effectId  // <-- varint

ViaVersion maps both in Protocol1_18To1_18_2.java:

// Protocol1_18To1_18_2.java:45-58
registerClientbound(ClientboundPackets1_18.UPDATE_MOB_EFFECT, new PacketHandlers() {
    public void register() {
        map(Types.VAR_INT); // Entity id
        map(Types.BYTE, Types.VAR_INT); // Effect id  byte→varint
    }
});
registerClientbound(ClientboundPackets1_18.REMOVE_MOB_EFFECT, new PacketHandlers() {
    public void register() {
        map(Types.VAR_INT); // Entity id
        map(Types.BYTE, Types.VAR_INT); // Effect id  byte→varint
    }
});

Source: v1_18to1_18_2/Protocol1_18To1_18_2.java:45-58; minecraft-data data/pc/1.18/protocol.json vs data/pc/1.18.2/protocol.json packet_entity_effect + packet_remove_entity_effect.

LOGIN / RESPAWN: infiniburn tag prefix added

Dimension type NBT now requires the infiniburn block tag to be prefixed with #:

// Protocol1_18To1_18_2.java:86-91
private void addTagPrefix(CompoundTag tag) {
    final Tag infiniburnTag = tag.get("infiniburn");
    if (infiniburnTag instanceof final StringTag infiniburn) {
        infiniburn.setValue("#" + infiniburn.getValue());
    }
}

This is applied to all dimension entries in the registry NBT (in LOGIN) and to the current dimension NBT in RESPAWN. The # prefix marks it as a tag reference rather than a direct block ID.

Source: v1_18to1_18_2/Protocol1_18To1_18_2.java:61-91.

Tags: fall_damage_resetting block tag added

A new block tag minecraft:fall_damage_resetting is injected into UPDATE_TAGS with a fixed list of block IDs:

// Protocol1_18To1_18_2.java:42
tagRewriter.addTagRaw(RegistryType.BLOCK, "minecraft:fall_damage_resetting",
    169, 257, 680, 713, 714, 715, 716, 859, 860, 696, 100);

Source: v1_18to1_18_2/Protocol1_18To1_18_2.java:41-43.

No packet additions or removals in 758

Minecraft-data confirms: the clientbound packet list in data/pc/1.18/protocol.json and data/pc/1.18.2/protocol.json are identical in packet names and IDs. The serverbound list is also unchanged. The protocol bump is driven by the effect-ID type change alone (plus the NBT / tag-data changes that don't alter packet structure).


Proxy / translation impact

What proxies must do for height-aware chunk translation

A proxy bridging 1.17.1 clients to a 1.18 server (or vice-versa) faces the most expensive chunk translation in the 1.17/1.18 era:

  1. Section count change: must expand/shrink the section array from 16 to 24 (or back). For 1.17.1 clients receiving a 1.18 chunk, ViaBackwards does not strip the 8 below-zero sections — it passes all 24 sections through to the 1.17.1 client using ChunkType1_17(tracker.currentWorldSectionHeight()) where the tracker reflects the 1.18 world height (24 sections). The 1.17.1 client can accept extended-height chunks because the 1.17 chunk format already used a variable-length bitmask. The BlockItemPacketRewriter1_18.java:158209 (ViaBackwards Protocol1_18To1_17_1) reads the 1.18 chunk with ChunkType1_18, rebuilds the section array and bitmask (null sections replaced for non-air blocks, line 177180), then writes with ChunkType1_17 at the same 24-section height — below-zero sections are included as long as they are non-empty. Source: ViaBackwards BlockItemPacketRewriter1_18.java:158209; ChunkType1_17.java:4246 (parameterised ySectionCount).

  2. Biome re-encoding: The 1.17.1 biome format is a flat VarInt[] of 1024 entries (4×4×4 per section × 16 sections). The 1.18 format is per-section paletted containers. ViaVersion's conversion:

    • Receiving a 1.17.1 chunk: reads the flat biome array, slices it into 16-entry groups (one per section × 64 biome cells), and creates a DataPaletteImpl per section — WorldPacketRewriter1_18.java:146-155.
    • The 8 extra sections for Y < 0 are padded with biome ID 0 (fallback for invalid/missing): biome = biomeData[biomeArrayIndex]; biomePalette.setIdAt(biomeIndex, biome != -1 ? biome : 0).
  3. Light buffering: Two packets become one. The proxy must buffer light packets until the corresponding chunk packet arrives (or vice-versa), then emit the merged LEVEL_CHUNK_WITH_LIGHT. ViaVersion's ChunkLightStorage implements this — ChunkLightStorage.java:28-68, WorldPacketRewriter1_18.java:66-103.

  4. Block entity format: 1.17.1 sends block entities as raw NBT with x/y/z/id fields inside. 1.18 sends a packed struct: packedXZ (u8) = (x & 15) << 4 | (z & 15), y (i16), typeId (varint). ViaVersion converts: WorldPacketRewriter1_18.java:109-128.

  5. Section presence bitmask: In 1.17.1 null sections are skipped via bitmask; in 1.18 every section must be present in the serialised form (using single-value palette for air). ViaVersion fills null sections: WorldPacketRewriter1_18.java:133-143.

  6. simulationDistance injection: When a 1.18 server sends LOGIN, ViaVersion injects a simulationDistance field (copied from viewDistance) for old 1.17.1 clients that don't know about it — EntityPacketRewriter1_18.java:57-58. In the reverse direction (serverbound), CLIENT_INFORMATION from a 1.17.1 client drops the enableServerListing bool that 1.18 expects — Protocol1_17_1To1_18.java:63-74.

Source: all paths above in v1_17_1to1_18/.

757 → 758 proxy impact (minimal)

For a proxy bridging protocol 757 to 758:

  • Effect ID: remap effectId in UPDATE_MOB_EFFECT and REMOVE_MOB_EFFECT between i8 and varint. Simple field-type swap, no data loss (effect IDs fit in a byte).
  • NBT prefix: add/strip # on the infiniburn string tag in LOGIN registry and RESPAWN dimension NBT.
  • Tags: inject or strip the fall_damage_resetting block tag.
  • No chunk re-encoding needed; chunk format is identical between 757 and 758.

Source: v1_18to1_18_2/Protocol1_18To1_18_2.java:39-91.


ViaVersion git log summary

# v1_17_1to1_18 package (selected):
e15fc5953  Trim slightly more fastutil, use its interfaces for the long collections
8ee5d7e68  Add missing biome name translations in 1.17.1->1.18 (#4597)
12c773ede  Add missing translatable mappings (#4542)
b8a170873  Add more info to missing light data warning
c13b40a37  Add ParticleRewriter base (#4203)
bd4df2813  Refactor protocols to match template module (#3842)
5286efde1  Move type instances out of its enclosing class

# v1_18to1_18_2 package (selected):
cff9a8715  [ci skip] Update copyright header
91f31b578  Clean up tags rewriting (#3856)
501f65e21  Packet and entity type renames
e965e9713  Package/class renames and moves

Source: git -C /tmp/mcproto-refs/ViaVersion log --oneline -- common/.../v1_17_1to1_18/ and common/.../v1_18to1_18_2/, each | head -20.