<!-- https://minecraftdocs.dev/systems/blocks/block-breaking — this page as markdown. The book's index: https://minecraftdocs.dev/llms.txt; the whole book in one file: https://minecraftdocs.dev/llms-full.txt -->

# Block breaking

> Verified against **Minecraft 26.3** · 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 breaker's own connection** — a `ServerboundPunchPacket` goes up and a
`ClientboundLevelEventPacket` comes down every tick, for the swing and for the
particles and the sound, and neither says how far along the dig is; the only
progress that goes on the wire at all is the server's, to *other* players, so
that they see your cracks — 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: **once the client has finished,
releasing the button cancels nothing.** A client that finishes 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 deferred break lands, 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 — once the server considers the client loaded, 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#two-state-machines-running-against-each-other) owns that machinery; [block interaction](block-interaction) 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 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 nine `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 particles and sound event, the stat, the exhaustion and the loot roll | Server, and the breaker's own client for the particles and sound |

## One dig, end to end

```mermaid
sequenceDiagram
    box Client
        participant MC as Minecraft
        participant MPGM as MultiPlayer<br/>GameMode
        participant CL as ClientLevel
    end
    box Server
        participant SGPL as ServerGamePacket<br/>ListenerImpl
        participant SPGM as ServerPlayer<br/>GameMode
        participant SL as ServerLevel
    end

    Note over MC,SL: client tick 1: the button goes down, and the same lap runs the first continue too
    MC->>MPGM: startDestroyBlock, the block is not air
    MPGM->>CL: destroyBlockProgress, stage -1 at progress 0
    MPGM->>SGPL: ServerboundPlayerAction<br/>Packet START, N
    MC->>MPGM: continueDestroyBlock adds the first 0.133
    MPGM->>CL: destroyBlockProgress, my own crack at stage 1
    SGPL->>SPGM: handleBlockBreakAction, the start tick recorded
    SPGM->>SL: destroyBlockProgress, everyone else's crack within 32 blocks
    SGPL-->>CL: ClientboundBlockChangedAck<br/>Packet N, nothing to reconcile

    loop client ticks 2-8, beside server ticks
        par the client adds
            MC->>MPGM: continueDestroyBlock adds 0.133
            MPGM->>CL: destroyBlockProgress, my own stage
            MC->>SGPL: ServerboundPunchPacket, no fields at all
        and the server recomputes the whole dig and throws the answer away
            SPGM->>SPGM: incrementDestroy<br/>Progress
            SPGM->>SL: destroyBlockProgress, only when the tenth changes
            SPGM->>SL: levelEvent with no source, sound every fourth tick
            SL-->>CL: ClientboundLevelEventPacket, the face but no progress
        end
    end
```

*The two clocks, with the machines boxed and the loop in parallel
compartments that have no order between them: the client adds a fraction, the
server recomputes the lot, and what crosses between the boxes, a punch up and a
level event down, carries neither clock's count, which is the page's whole
argument.*

The eighth add is where the two stop being symmetrical. It reaches about 1.067
on the client, and the client acts on its own number first.

```mermaid
sequenceDiagram
    box Client
        participant MPGM as MultiPlayer<br/>GameMode
        participant CL as ClientLevel
    end
    box Server
        participant SGPL as ServerGamePacket<br/>ListenerImpl
        participant SPGM as ServerPlayer<br/>GameMode
        participant SL as ServerLevel
        participant Block as Block
    end

    Note over MPGM,Block: still client tick 8: the eighth add reaches about 1.067
    MPGM->>CL: setBlock to air under flags 11, inside prediction M
    MPGM->>SGPL: ServerboundPlayerAction<br/>Packet STOP, M
    Note over MPGM,Block: a server tick, STOP handled off the task queue
    SGPL->>SPGM: handleBlockBreakAction STOP, its own 1.067 clears the 0.7 bar
    SPGM->>Block: playerWillDestroy, event 2001 to all but the breaker
    SPGM->>SL: removeBlock, the fluid-or-air state under flags 3
    SPGM->>Block: playerDestroy, once ItemStack.mineBlock has spent a durability point
    Block->>SL: the blocks/stone roll, then addFreshEntity with the ItemEntity
    Note over MPGM,Block: same tick, later: the levels broadcast, then the connections flush
    SL-->>CL: ClientboundBlockUpdatePacket air, absorbed
    SGPL-->>CL: ClientboundBlockChangedAck<br/>Packet M, air is already there
```

*The break itself, in the order the server runs it. The client writes the air
first and on its own authority, and everything that comes back — the block
update and the receipt — finds a world that already agrees.*

## 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 a literal power of 0.3, with the amplifier plus one
as the exponent — 0.3, 0.09, 0.027 and on down 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, and the
reason is a matter of *when* the server is asked rather than what it computes.
The client accumulates: `MultiPlayerGameMode.continueDestroyBlock` adds one
tick's fraction each time it runs, and the first of those runs in the same
client tick as the `Minecraft.startAttack` that opened the dig through
`MultiPlayerGameMode.startDestroyBlock`, so after *k*
client ticks the client holds *k* fractions. The server keeps no accumulator.
For a live dig, `ServerPlayerGameMode.incrementDestroyProgress` multiplies the
per-tick fraction by *elapsed ticks plus one*, and that plus one is exactly the
client tick that both started and continued.

Now the timing. `ServerPlayerGameMode.tick` increments its own tick counter
before recomputing, so the number it reaches **inside the level tick** is one
fraction ahead of the client's — and it uses that number only for the crack
stage other players see, because the live branch throws the value away and
only the delayed branch compares a value with 1.0. What the live branch sends every tick is a level event with no source
entity, so the breaker is sent it too: `LevelEvent.PARTICLES_DESTROY_PROGRESS`,
or `LevelEvent.PARTICLES_AND_SOUND_DESTROY_PROGRESS` on every fourth, naming
the face being dug and not the progress. The comparison that decides anything
happens somewhere else: the STOP is handled in the packet drain, *before* that
tick's increment ([the server
tick](../server/server-tick#every-packet-since-last-time-in-one-drain)), so
`ServerPlayerGameMode.handleBlockBreakAction` measures elapsed ticks over the
same span the client counted and arrives at the same 1.067. The agreement is
not a coincidence in the arithmetic; it is the packet drain running before the
levels do.

Recomputing rather than accumulating has a second consequence: lose haste
mid-dig and the server rescales the *whole* dig retroactively, while the client
keeps the progress it already banked. Swapping tools is different: the client
sees a new stack in `MultiPlayerGameMode.sameDestroyTarget`, sends an ABORT and
a START, and both clocks begin again.

They agree without talking because almost 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, where the
player stands and, sharpest of all, 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** of the server's own clock is what 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: unless a delayed dig is already pending, when it does nothing at all,
the handler sets `ServerPlayerGameMode.hasDelayedDestroy`, copies the
position and the *original* start tick into
`ServerPlayerGameMode.delayedDestroyPos` and
`ServerPlayerGameMode.delayedTickStart`, and leaves the rest to the delayed
branch of `ServerPlayerGameMode.tick`. The sequence is acknowledged regardless
— `ServerGamePacketListenerImpl.handlePlayerAction` calls
`ServerGamePacketListenerImpl.ackBlockChangesUpTo` for all four break actions,
unconditionally, after the game mode has run. The delayed branch does not count
the ticks since the dig began: it multiplies the per-tick fraction by the start
tick itself, plus one, and that tick is the game mode's count since the player
joined or respawned, so the answer does not grow with waiting. For any dig
begun more ticks after that than it takes, the product clears 1.0 on the
branch's first run, later in the same server tick, and
`ServerPlayerGameMode.destroyBlock` runs — after the level has broadcast that
tick's block changes. So the receipt still 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 for a tick, until the next broadcast carries the air as an ordinary block
update. The prediction was not wrong — it was undone and then redone.

Letting go changes nothing: a client that has sent its STOP is no longer
digging, so releasing the button sends nothing, and an ABORT from a later dig clears
`ServerPlayerGameMode.isDestroyingBlock` and erases the crack without touching
`ServerPlayerGameMode.hasDelayedDestroy` — and `ServerPlayerGameMode.tick`
tests the delayed dig **first**, before the live one. A delayed dig begun too
soon after the player joined or respawned never clears on its own, and then
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 two of the three ways it can fail there
are silent, with no corrective block update. Inside the tick it ends only when
the block turns to air, the one condition `ServerPlayerGameMode.tick` tests
before recomputing progress, or when that recomputation, made with whatever the
player now holds and has, reaches 1.0; otherwise it lasts until the player
leaves or respawns.

### 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 but the receipt**, and it
guards STOP as well as START; only an abort gets past it, and only while a dig
is live, so an abort from too far away still ends one. 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.

Worth holding the two click lectures side by side here, because the same two
gates answer oppositely. Breaking above the build height sends the true state
back; *placing* above it sends an action-bar line and no block update at all.
Breaking inside spawn protection sends the message and no block update;
placing there sends the message *and* both updates ([block
interaction](block-interaction#the-gate-list-and-what-each-refusal-answers-with)).
The rule is not the gate but the pipeline. A placement's two corrective updates
sit in one branch below the build-height test and go out for every outcome that
reaches it, refusal or not. A break has no such branch: each of its five exits
decides for itself. Three send the true state back — the build height,
`ServerLevel.mayInteract` and `Player.blockActionRestricted`. Spawn protection
sends its message and no state. And being out of reach sends nothing but the
receipt, which puts the block back like any other; what a STOP refused for
reach leaves wrong is the server, whose live dig goes on sending the breaker its
level events until a START or ABORT it accepts, or until the block turns to air.

## 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, and the game builds its
rules in three shapes, each with a factory that says so:
`Tool.Rule.minesAndDrops` carries both, `Tool.Rule.deniesDrops` carries only
the verdict and `Tool.Rule.overrideSpeed` only the speed.
`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 |
|---|---|---|
| `Tool.Rule.deniesDrops` on `BlockTags.INCORRECT_FOR_IRON_TOOL` — no speed field | skips it | obsidian matches, answers **no** |
| `Tool.Rule.minesAndDrops` on `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 shapes at once, and the two that never share a `Tool`
are `Tool.Rule.deniesDrops` and `Tool.Rule.overrideSpeed`: the four mining
tools pair a deny rule with a *minesAndDrops* rule, while shears and the sword
— which deny nothing — are the only users of `Tool.Rule.overrideSpeed`. The
sword is also 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, and delivered before this method returns ([the broadcast is a
nested loop](../world/game-events-and-vibrations#the-broadcast-is-a-nested-loop-and-one-listener-is-the-exception)).

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 pair of flowcharts on [blocks and
states](blocks-and-states#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
conditional four ways over: a `DataComponents.TOOL` at all, the server side, a
non-zero hardness and a damage-per-block above zero. Not one of those four is
the drop verdict, which is the whole point and belongs to [items and
stacks](../items/items-and-stacks#a-pickaxes-last-point-of-durability).
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#where-the-randomness-comes-from)). 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 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: the one
thing it asks of the block is that it 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.

> **For a 1.21-era reader.** *ServerboundSwingPacket* is gone; on a dig,
> `ServerboundPunchPacket` does its work, and it names no hand.

## Where to look

The two clocks are the thing to read first, and they are three methods:
`BlockBehaviour.getDestroyProgress` is the shared formula,
`MultiPlayerGameMode.continueDestroyBlock` is the client's accumulator, and
`ServerPlayerGameMode.incrementDestroyProgress` is the server's recomputation.
`ServerPlayerGameMode.handleBlockBreakAction` is the state machine behind all
four break actions and the 0.7 bar, with `ServerPlayerGameMode.tick` for the
delayed destroy it can leave behind. Then the ends: `Minecraft.startAttack`
and `Minecraft.continueAttack` for where a dig begins each tick,
`ServerPlayerGameMode.destroyBlock` for the gauntlet, and
`Block.playerWillDestroy` with `Block.playerDestroy` and `Block.popResource`
for what a broken block leaves behind. `Tool.getMiningSpeed` and
`Tool.isCorrectForDrops` are the two walks of one rule 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`.*
