Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Blocks and states

Verified against Minecraft 26.3 · Part V · A player right-clicks the top of a stone block holding oak stairs: one of the stair’s eighty pre-built states is chosen, and then written — and that write, drawn in two figures, is what the rest of this part points back at.

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 points into that table: each section keeps a small palette of those very states, and uses the table’s own numbers once it holds more than 256 different ones. Both of the surprises about choosing a state 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 table’s numbers are 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.

Then the state has to go in, and that is the page’s second half and its wider job: the write is where a block state stops being a value and becomes an event, and the two channels it can leave by are not the same channel on both sides of the game. Six other lectures in this part are applications of that write, so it is drawn here in full.

The cast

classwhat it decidesthread
Blockone kind of thing: which properties it has, what its default state is, and — through its static helpers — the id lookups, the drops and the shape-update helpers the rest of the game callsbuilt at class-initialisation, read from every thread after
BlockBehaviourmost of the hooks a block may override, from BlockBehaviour.onPlace to BlockBehaviour.updateShape. Block extends it and adds registration and hooks of its own, Block.getStateForPlacement among themas above
BlockBehaviour.Propertieshardness, sound, map colour, whether it ticks — and the ResourceKey without which no block can be built at allkept by the BlockBehaviour constructor, which copies about a third of its values out; each state copies others, hardness and map colour among them
StateDefinitionthe table: which properties this block has, in which order, and the full product of their valuesbuilt in the Block constructor
Propertyone axis — a name, a value type, and where a value sits in that axisimmutable, shared between blocks
StateHolderone state’s property values, and the table that answers what state am I if this property becomes that valuefilled once by StateDefinition, read-only after
BlockBehaviour.BlockStateBaseevery hook asked of a state and forwarded to its block, and the caches that make collision and occlusion cheaphalf-built in its constructor, finished by BlockBehaviour.BlockStateBase.initCache
Block.BLOCK_STATE_REGISTRYthe 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 propertiesappended once per state, in the Blocks class initialiser

Eleven classes and one Cartesian product

classDiagram
    class BlockBehaviour.Properties {
        hardness, sound, map colour, whether it ticks
        setId first, or the constructor throws
    }
    class BlockBehaviour {
        <<abstract>>
        most hooks a block may override
    }
    class Block {
        the registry holder and the static helpers
        one StateDefinition, one default state
        BLOCK_STATE_REGISTRY, over every state of every block
    }
    class Property {
        <<abstract>>
        a name, a value type, an index per value
    }
    class BooleanProperty {
        two values, true at index 0
    }
    class IntegerProperty {
        min to max, min never below zero
    }
    class EnumProperty {
        any StringRepresentable enum
    }
    class StateDefinition {
        propertiesByName, sorted by name
        states, the whole Cartesian product
    }
    class StateHolder {
        <<abstract>>
        this state's own property values
        the neighbours table
    }
    class BlockBehaviour.BlockStateBase {
        <<abstract>>
        every hook, forwarded to the block
        the caches initCache fills
    }
    class BlockState {
        a constructor, asState, two public codecs
    }
    BlockBehaviour.Properties ..> BlockBehaviour : kept by the constructor, a third copied out
    BlockBehaviour <|-- Block
    Property <|-- BooleanProperty
    Property <|-- IntegerProperty
    Property <|-- EnumProperty
    Block --> StateDefinition : builds exactly one, in its constructor
    StateDefinition --> Property : one axis each, collected by the Builder
    StateDefinition --> BlockState : one per cell, built once and never again
    StateHolder <|-- BlockBehaviour.BlockStateBase
    BlockBehaviour.BlockStateBase <|-- BlockState
    StateDefinition ..> StateHolder : fillNeighborsForState fills the table

The eleven classes a block state is made of, and the two hierarchies that meet in it: a kind three classes deep down the left and a state three classes deep down the right. StateDefinition joins them, building one BlockState per cell of the product and filling each state’s table of neighbours.

Two things in that picture are the section’s: Property is extended by those three classes and by nothing else, and BlockState inherits only down the state side, though its parent class is nested inside BlockBehaviour. The rest of this section is what the product is and how big it gets.

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 block update carries, that a model is chosen for. The kind is spread over three classes, and the third is the one a reader does not expect. BlockBehaviour is the base and holds most of the hooks; Block extends it and adds the registry holder, the state table, hooks of its own and the statics everything else in the game reaches for. The third is BlockBehaviour.Properties, which both are constructed from — 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 every block the game builds goes through 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: a block has no codec at all, so 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 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. Three 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 on disk: NBT is a hash map there, and the alphabetical forms are text: the command syntax BlockStateParser writes and the one StateHolder.toString prints, neither of which goes near the codec, and any SNBT print, which sorts every compound’s keys. 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 (naming drift). 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.

That pool, the four property classes and the twenty-four StringRepresentable enums of its own that EnumPropertys range over — ChestType, NoteBlockInstrument, StairsShape and the rest — are nearly the whole of the state/properties sub-package: no behaviour, just the axes and their values. The rest is two records that describe a family of blocks, BlockSetType and WoodType, and RotationSegment’s arithmetic for sixteen-way rotations. Two classes beside it read a state rather than being part of one, and are worth a name because their scenarios are elsewhere: BlockPattern with BlockPatternBuilder matches a three-dimensional arrangement of BlockInWorld, which is how the game recognises a built wither, and BlockStatePredicate is a StateDefinition turned into a test.

The state, a leaf

StateHolder is the generic state, shared with FluidState (fluids). 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.

That reference comparison is why a property lookup can throw on a property that looks right. States match their properties by identity and properties match each other by value: 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. The constant in BlockStateProperties is the object the table was built with; a look-alike is not.

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, and asks the block one question, BlockBehaviour.useShapeForLightOcclusion. Everything else 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 almost empty: a constructor, a BlockState.asState that returns this, and two public codecs — BlockState.FULL_CODEC, and BlockState.CODEC, which writes a default state as the bare block id. 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 Render 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.

The id that answers air

That registry is where the opening’s second surprise lives, and it is worth being exact about how far it goes, because the tolerance is a property of two static methods and not of the id. Block.getId answers 0 for a state the table has never seen and Block.stateById answers Blocks.AIR’s default state for a number it does not know, so a disagreement between the two sides raises nothing and quietly becomes air. That pair is behind block-break particles, the falling-block spawn packet and EntityDataSerializers.OPTIONAL_BLOCK_STATE. The single-block update is stricter in both directions: ClientboundBlockUpdatePacket.STREAM_CODEC reads and writes the same table through ByteBufCodecs.idMapper, which uses IdMap.byIdOrThrow and IdMap.getIdOrThrow and fails the connection instead. The section update is not: ClientboundSectionBlocksUpdatePacket writes through the tolerant Block.getId and decodes with IdMapper.byId, which answers null.

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 and prediction and acks. 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 (and one that reaches its neighbours, below), 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.

A write reads its flag word by bit, so here are the eight bits it gates on before you meet them: 1 is Block.UPDATE_NEIGHBORS, 2 Block.UPDATE_CLIENTS, 4 Block.UPDATE_INVISIBLE, 16 Block.UPDATE_KNOWN_SHAPE, 32 Block.UPDATE_SUPPRESS_DROPS, 64 Block.UPDATE_MOVE_BY_PISTON, 256 Block.UPDATE_SKIP_BLOCK_ENTITY_SIDEEFFECTS and 512 Block.UPDATE_SKIP_ON_PLACE. Placement’s 11 is therefore neighbours, clients and immediate — the combination Block.UPDATE_ALL_IMMEDIATE — so the stair’s write passes every flag test below, and the steps it skips, it skips for other reasons: there is no block entity to remove or make, and a stair has no analog output. The figures are the order; the table under them is what each step waits for.

flowchart TD
    IN["Level.setBlock"]
    IN -- "out of bounds, or debug" --> FALSE["returns false"]
    IN --> EMPTY

    subgraph CHUNK["LevelChunk.setBlockState"]
        EMPTY{"air into an empty section"}
        SEC["write the section"]
        NOOP{"this exact state already"}
        HM["the four live heightmaps"]
        LIGHT["tell the light engine what moved"]
        PRE["BlockEntity.preRemoveSideEffects"]:::server
        AFT["BlockBehaviour.BlockStateBase.affectNeighborsAfterRemoval"]:::server
        GUARD{"is the new block still there"}
        ONP["BlockBehaviour.BlockStateBase.onPlace"]:::server
        BE["create, keep or replace the block entity"]
        NOTHING["hand back nothing"]
        EMPTY -- "yes" --> NOTHING
        EMPTY -- "no" --> SEC --> NOOP
        NOOP -- "yes" --> NOTHING
        NOOP -- "no" --> HM
        HM --> LIGHT --> PRE --> AFT --> GUARD
        GUARD -- "no" --> NOTHING
        GUARD -- "yes" --> ONP
        ONP -- "the state has a block entity, and the block is still there" --> BE
    end

    NOTHING --> FALSE
    BE --> READ{"re-read: is it the state we wrote"}
    ONP -- "otherwise" --> READ

The first half-write: what LevelChunk.setBlockState does belongs to the position, bar the outgoing block’s word to its neighbours, and it has three ways out that write nothing further — the two no-ops at the top and the guard on the block, which all land in the same dead end. The three steps drawn in the server colour are the server’s alone.

The re-read at the foot is the joint the whole section turns on. Only if the state that comes back is the one that went in does the second half run, and that second half is where most of a write’s reach past the position lives.

flowchart TD
    READ{"re-read: is it the state we wrote"}
    READ -- "no, and the tail is skipped" --> TRUE["Level.setBlock returns true"]
    READ -- "yes" --> DIRTY

    subgraph TAIL["the tail of Level.setBlock"]
                DIRTY["Level.setBlocksDirty"]:::client

        SEND["Level.sendBlockUpdated"]
        NB["Level.updateNeighborsAt"]:::server
        SHAPE["three shape passes"]
        POI["Level.updatePOIOnBlockStateChange"]:::server
        DIRTY --> SEND --> NB --> SHAPE --> POI
    end

    POI --> TRUE

The second half-write, which with the first is what the part’s other six lectures apply. Both edges out of the diamond return true — a write that fails its re-read skips the whole tail and still says it succeeded.

The figures are the order and the table is the gate: every step in them runs unconditionally unless a row here says otherwise, and the flag numbers are the ones the lead-in paired with their names.

stepsideflagsalso needs
write the sectionboth——
the four live heightmapsboth—the two worldgen heightmaps are never touched
tell the light engine what movedboth—the section’s emptiness flipped (LevelLightEngine.updateSectionStatus), or the light properties differ (LevelLightEngine.checkBlock is queued)
BlockEntity.preRemoveSideEffectsserver256 clearthe block changed, and the new state does not keep the old entity. The removal after it runs on both sides
BlockBehaviour.BlockStateBase.affectNeighborsAfterRemovalserver1 set, or 64 setthe block changed, or the new block is a BaseRailBlock
BlockBehaviour.BlockStateBase.onPlaceserver512 clear—
create, keep or replace the block entityboth—the new state has a block entity, and the block is still there after BlockBehaviour.BlockStateBase.onPlace; then, either way, ChunkAccess.markUnsaved
Level.setBlocksDirtyclient—Empty on Level; the client re-meshes through LevelExtractor.setBlockDirty
Level.sendBlockUpdatedboth2 set, and 4 clear on the clienton the server, a chunk at FullChunkStatus.BLOCK_TICKING or better
Level.updateNeighborsAtserver1 setempty on Level, overridden on ServerLevel
Level.updateNeighbourForOutputSignalserver1 setthe new state has an analog output
three shape passesboth16 clear, with 1 and 32 masked out of what they pass onthe update limit, which starts at Block.UPDATE_LIMIT, still positive. Indirect for the old state, direct for the new, indirect for the new
Level.updatePOIOnBlockStateChangeserver—empty on Level, overridden on ServerLevel

Inside the chunk write

The section write, the four heightmaps and the light checks are the same on both sides, and belong to chunk anatomy. 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). 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). 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. Rails are the exception because a rail carries its own geometry in a property: changing just the shape leaves the block the same and still moves the track, and BaseRailBlock.affectNeighborsAfterRemoval is what tells the positions that geometry reaches — above it when the old shape was a slope, and its own and the one below for the rail kinds that never curve (powered, activator and detector rails).

Then the chunk re-reads its own section. If a side effect has already replaced the block just written with another, 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 then, if the new state has a block entity and BlockBehaviour.BlockStateBase.onPlace has not replaced the block, the block entity is created, kept or replaced. A block entity that disagrees with the new state is logged as mismatched and thrown away.

Note that the diamond on the block and the re-read after it are not the same test asked twice. The one inside the chunk write asks whether the block just written is still there and answers false when it is not; the one back in Level.setBlock asks the stricter question, whether the very state is, and answers true anyway — it has a write to report, and what it skips is the whole tail. So false means the chunk had nothing to change or a side effect of the removal replaced the block, and true with nothing visible happening means something later, BlockBehaviour.BlockStateBase.onPlace included, changed the state or even the block first. Those three statements are the only ones that return false: a position out of bounds, the server side of a debug world, and the chunk write coming back with nothing.

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 next two are the two update channels proper, and the difference between them is the fact the rest of this part rests on. (The fifth and last step of the tail is neither: Level.updatePOIOnBlockStateChange, which keeps the village’s index of interesting blocks in step with the world and belongs to points of interest.)

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 NeighborUpdater — a CollectingNeighborUpdater on every real level, the alternative InstantNeighborUpdater being used by nothing the game ships — 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 (diodes and the observer).

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 this cascade — a recursion depth, and not the far larger per-request budget over the neighbour channel (block interaction).

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, which writes with flags 3 and posts GameEvent.BLOCK_DESTROY. So a shape update that turns a block into nothing deletes it on the server, with a neighbour fan-out of its own, and does nothing at all on the client, which then waits to be told.

The flag word

Those eight bits are not the whole word. There are ten, and what each is called, what reads it and how the four named combinations decompose are the catalogue’s: block update flags. One number on this page is not a bit at all — Block.UPDATE_LIMIT is 512 like Block.UPDATE_SKIP_ON_PLACE and means something entirely different.

Where to look

Follow the trace: Blocks.register and BlockBehaviour.Properties.setId build a block, StateDefinition.Builder.create builds its table and StateDefinition.StateCollection.fillNeighborsForState fills the cells StateHolder.setValue will later read. Block.BLOCK_STATE_REGISTRY is where the numbers come from and BlockBehaviour.BlockStateBase.initCache is what finishes each state. Then StairBlock.getStateForPlacement chooses one, BlockItem.placeBlock writes it, and LevelChunk.setBlockState and Level.setBlock are the two halves of the write. Block.updateOrDestroy is the end of a shape update, and NeighborUpdater.executeShapeUpdate — not named above — is where the updater hands a queued one to the block.


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