<!-- https://minecraftdocs.dev/systems/items/recipes — 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 -->

# Recipes

> Verified against **Minecraft 26.3** · 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, every recipe it has not
unlocked, and any authority whatever 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 worker pool for its constructor; the Server thread after, the first finalize on whichever thread builds the server |
| `RecipeMap` | the immutable store, holding exactly two indexes: `RecipeMap.byType` and `RecipeMap.byKey` | built on the worker pool, swapped in and read on the Server thread |
| `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, and a result slot's `ResultSlot.onTake` builds one on the Render thread too |
| `ResultSlot` | the order of the endgame: award first, then look the recipe up again, then decrement | Server, mirrored on the Render thread's menu |
| `ServerRecipeBook` | which recipes this player has unlocked, and which of them still glow | Server |
| `ClientRecipeContainer` | everything the client knows about recipes *as recipes*: nine `RecipePropertySet`s and the stonecutter's input set | Render, rebuilt wholesale from each packet |

## Loading: one scan, one swap, and four indexes built later

`RecipeManager` is not a reload listener. `RegistryDataLoader` reads the recipe
files into a reloadable registry, `Registries.RECIPE`, and a fresh
`RecipeManager` builds its `RecipeMap` from that registry in its constructor,
both on the worker pool; on a reload the Server thread then swaps the new manager in.
That is the two-phase shape of
[the resource system](../foundations/resource-system#prepare-every-listener-at-once) without a listener — and
the second phase does something unusual.

```mermaid
flowchart TD
    W["RegistryDataLoader reads data/ns/recipe into Registries.RECIPE"]:::worker
    W --> M["RecipeManager constructor builds its RecipeMap"]:::worker
    M --> A["MinecraftServer.reloadResources swaps in the new manager"]:::server
    A -. "the four indexes below are empty, not stale" .-> F
    F["RecipeManager.finalizeRecipeLoading, called by MinecraftServer"]:::server
    F --> P1["nine RecipePropertySet indexes"]:::server
    F --> P2["SelectableRecipe.SingleInputSet"]:::server
    F --> P3["RecipeManager.allDisplays, indexed by RecipeDisplayId"]:::server
    P3 --> P4["RecipeManager.recipeToDisplay, key to displays"]:::server
```

*The reload's two phases — the worker pool's two boxes above, the Server thread's below — and the gap between them. The dotted edge is the one
the section is about: the swap is not what builds the four
indexes, and between those two boxes they hold nothing.*

Each index applies its own feature-flag filter as it is built: an ingredient
is dropped from a `RecipePropertySet` unless every item in it is enabled, the
stonecutter's set keeps an entry only if input and result display are both
enabled, and a display is kept only if its result and its crafting station
are. Three tests of three different things, all against one `FeatureFlagSet`, the
level's, and the same filter `ItemStack.isItemEnabled` applies to an assembled
stack two sections below.

Three things in that picture are worth saying out loud.

**The scan is sorted, and the sort is the whole ordering story.**
`ResourceManagerRegistryLoadTask.load` registers the recipes sorted by their
files' `Identifier`s, `RecipeMap` keeps that order, 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 with the map.**
`RecipeManager.finalizeRecipeLoading` has exactly two call sites, both of them in `MinecraftServer`, and neither is on the worker pool. Between the
swap and that call the four derived indexes are **empty** rather than stale: a
reload builds a fresh `RecipeManager`, and its constructor builds the
`RecipeMap` but leaves all four at their empty values, so the recipe book, the
property sets and the stonecutter index describe nothing at all rather than
describing the pack you just replaced. Nothing can catch the game in that
state — the swap and the call are five statements apart in one lambda on the Server thread — which is the only reason the gap is allowed to exist.

The last two indexes are a pair: `RecipeManager.allDisplays` is the flat
list, and `RecipeManager.recipeToDisplay` maps a recipe key to the entries it
produced in it.

**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 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`, where its empty ingredient matches no stack and so no
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 as craftable a recipe nobody can make. Only gate six of the auto-fill, below, stops the click.

### It is not only shaped and shapeless

`RecipeSerializers` registers twenty-two serializers and fourteen of them are
crafting-table recipes. **Nine** of those fourteen are `CustomRecipe`s — matching and assembly written in
Java, though eight of the nine read their ingredients and base result from JSON. `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. Eight of the nine register under an id beginning *crafting_special_*, which is
where the name comes from; the ninth, `DecoratedPotRecipe`, registers as
*crafting_decorated_pot* and is a `CustomRecipe` all the same. The nine are the crafting recipes the book never shows: `FireworkStarRecipe`,
`FireworkStarFadeRecipe`, `FireworkRocketRecipe`, `BookCloningRecipe`,
`BannerDuplicateRecipe`, `ShieldDecorationRecipe`, `MapExtendingRecipe`,
`DecoratedPotRecipe` and `RepairItemRecipe`, which is the one this part comes
back for: it fuses two items of one kind that have durability and carries every curse from
both inputs onto the result ([enchanting](enchanting)). Of
the five crafting-table serializers 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 dye
recipe, `SlotDisplay.WithAnyPotion` for the imbue). 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

Putting the last plank into a crafting grid is two traces, the second on a later
click, and only the second one reaches the recipe book.

```mermaid
sequenceDiagram
    participant CraftM as CraftingMenu
    participant TCC as TransientCrafting<br/>Container
    participant RM as RecipeManager
    participant ResultC as ResultContainer
    participant Wire as the network

    rect rgba(0, 0, 0, 0.04)
        Note over CraftM,Wire: the tick the eighth plank lands
        CraftM->>TCC: asCraftInput, trimming the empty rows and columns
        CraftM->>RM: getRecipeFor CRAFTING, this input, this level, no hint
        RM-->>CraftM: the first RecipeHolder that matches, in registry order, or nothing
        CraftM->>ResultC: setRecipeUsed, refused under LIMITED_CRAFTING
        CraftM->>ResultC: setItem 0, the assembled stack
        CraftM->>Wire: ClientboundContainerSetSlotPacket, bumping the state id
    end
```

*The result slot is filled and announced in the tick the grid changed,
and nothing has been taken yet. The packet at the foot is written by hand
rather than left to the menu's own diffing pass.*

Nothing above happens on the client: the client's `CraftingMenu` is handed
`ContainerLevelAccess.NULL` and matches nothing. What arrives is the last
arrow. The second half of the trace is a later click, and only then does a recipe reach
the book.

```mermaid
sequenceDiagram
    participant ResultS as ResultSlot
    participant ResultC as ResultContainer
    participant SRB as ServerRecipeBook
    participant RM as RecipeManager
    participant TCC as TransientCrafting<br/>Container
    participant Wire as the network

    rect rgba(0, 0, 0, 0.04)
        Note over ResultS,Wire: a later click, the player takes the result
        ResultS->>ResultS: checkTakeAchievements runs before a cell is emptied
        ResultS->>ResultC: awardUsedRecipes, which then nulls the stored holder
        ResultC->>SRB: addRecipes, through ServerPlayer.awardRecipes
        SRB->>Wire: ClientboundRecipeBookAddPacket
        ResultS->>RM: getRecipeFor again, holder or no holder
        ResultS->>TCC: Container.removeItem, one cell at a time, each followed by its remainder
        Note over ResultS,TCC: every removal re-enters CraftingMenu.slotsChanged
    end
```

*Taking the stack is where the recipe reaches the book, and where the
grid is emptied a cell at a time — each removal starting the first figure
again.*

**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`. The two-argument `CraftingMenu` constructor — the client's — is
handed `ContainerLevelAccess.NULL`, which runs nothing at all ([containers and
menus](containers-and-menus#three-asymmetries-between-the-two-copies)), so the
client never matches anything.

**Trimming.** Every recipe kind is matched against a `RecipeInput`, and there
are exactly four implementations of it in the game: `CraftingInput` for a
grid, `SingleRecipeInput` for the one-slot stations — the four `AbstractCookingRecipe`
kinds and the stonecutter's, all of them `SingleItemRecipe`s —
`SmithingRecipeInput` for the smithing table's three, and `BrewingInput` for a
brewing stand's bottle and reagent.
`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`, and does it twice unusually: it accounts every stack as
**one** item, and it calls `StackedItemContents.accountStack` directly rather
than the filtered `StackedItemContents.accountSimpleStack` the recipe book
uses. Both choices point the same way — this index is a presence set for
shapeless matching, not the arithmetic the auto-fill does, and it deliberately
does not apply the auto-fill's filter. What that filter is, and who it catches
out, is the last section of this page.

**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 `StackedItemContents.canCraft`,
which is a thin wrapper: the bipartite search itself lives in the generic
`StackedContents` that `StackedItemContents` holds and delegates to.

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,
`BrewingStandBlockEntity` another, 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 every crafting recipe but the
hand-written ones it ignores its input entirely and materialises a stored
`ItemStackTemplate` ([items and
stacks](items-and-stacks#an-item-a-count-some-components--said-three-ways)) —
`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,
incrementing the state id on its own way past — outside the diffing that
[containers and
menus](containers-and-menus#two-paths-that-are-not-this-protocol-at-all)
describes, and outside `AbstractContainerMenu.suppressRemoteUpdates`, which
guards only that diffing and so cannot reach a hand-written send. 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

The client's entire recipe API is two methods. `RecipeAccess` declares
`RecipeAccess.propertySet` and `RecipeAccess.stonecutterRecipes` and nothing
else, and exactly two classes implement it: `RecipeManager` on the server and
`ClientRecipeContainer` on the client, the latter holding a map and a set and
having no other content at all. Neither method's type is a `Recipe`, but the type is not what keeps recipes off
the wire: on the server the stonecutter set carries a recipe in every entry, and
the codec below is what drops it.

`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.
The recipe classes behind those stations are as thin as the sets suggest:
`SingleItemRecipe` is the shape `StonecutterRecipe` and the cooking recipes extend, and `SmithingTransformRecipe`
and `SmithingTrimRecipe` are the smithing table's two — one that replaces the
item, one that writes an `ArmorTrim` component onto it.
`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 — `BrewingStandMenu` reads the two brewing sets,
and
`StonecutterMenu` asks the stonecutter set the same question through
`SelectableRecipe.SingleInputSet.acceptsInput`.

### The book gets more, and still no names

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
shows as craftable. `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 `ContextMap` that
`SlotDisplayContext` builds. The chest's single ingredient reaches the client
as a `SlotDisplay.TagSlotDisplay` over *minecraft:planks*
([tags](../foundations/tags#a-tag-is-a-key-and-a-file)) — 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#trusted-untrusted-and-validated)).
A fresh player's is genuinely empty, and stays empty until something fills it.
The ordinary route is not crafting at all: every recipe in vanilla that is not
special has an advancement whose reward names it, so unlocking the advancement is what fills
the book ([advancements](../commands/advancements)), and a new world's
opening minutes are that machinery running. Crafting a recipe you had not
unlocked adds it too, which is the path this trace took.
`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 thirteen kinds of plank.

The tabs are narrower than the recipe types. `RecipeBookType` has four values —
crafting, furnace, blast furnace and smoker — and exactly five concrete menus
extend `RecipeBookMenu` to claim them, under two abstract halves:
`AbstractCraftingMenu` gives `CraftingMenu` and `InventoryMenu`, both
answering `RecipeBookType.CRAFTING`, and `AbstractFurnaceMenu` gives
`FurnaceMenu`, `BlastFurnaceMenu` and `SmokerMenu`, one per remaining value. `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#the-five-paths-at-a-glance)).

### Craftability is decided twice

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 show as craftable. A lying client gains nothing by
it, because the placement re-checks and `CraftingMenu.slotChangedCraftingGrid`
runs a full `Recipe.matches` afterwards regardless.

### Seven gates before a single plank moves

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` calls `AbstractCraftingMenu.beginPlacingRecipe`,
which on a `CraftingMenu` raises a private flag that makes
`CraftingMenu.slotsChanged` a no-op while the shuffling happens — 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 maximum 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 whose inventory cannot take back what the grid holds 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.

### The filter that greys out a recipe you could craft by hand

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`. Matching does not gate on it at
all — that is what `CraftingInput`'s direct call to
`StackedItemContents.accountStack` buys. So the book can grey out a recipe
that a manual craft with those very items would have accepted without
complaint, and the difference between the two is one method name.

## Where to look

Read `RecipeAccess` first. It declares two methods, it is the whole of what a recipe
system has to offer a client, and everything else on this page is an argument
about why it is that short.

Then the server's side in loading order: `ResourceManagerRegistryLoadTask` for
the scan, `MinecraftServer.reloadResources` for the swap and `RecipeManager`
for the four indexes, with `RecipeMap` beneath it for the two the store keeps. `Recipe` is the interface every kind implements, and
`ShapedRecipePattern` is the one piece of matching worth reading line by line,
for the mirrored pass. `CustomRecipe` is the nine the book never shows.

For the trace, `CraftingMenu.slotChangedCraftingGrid` is the whole of what
happens when a plank lands, and `ResultSlot.onTake` is the whole of what
happens when you take the chest — in that surprising order. `ServerPlaceRecipe`
is the auto-fill, and `Inventory.isUsableForCrafting` is the filter under it.

For the client's half, `RecipeDisplay` and `SlotDisplay` are what a recipe
becomes when it may not be itself, and `ClientRecipeBook` is where they land.

Before this page: [containers and
menus](containers-and-menus#one-shift-click-end-to-end), for the
synchroniser that the result slot goes around, and [items and
stacks](items-and-stacks#an-item-a-count-some-components--said-three-ways)
for `ItemStackTemplate`.

---

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