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

Owned item values

ItemStack is the mod API's own nonempty value, not an engine inventory slot or a borrowed rendering view. It contains an ItemKey, a nonzero count, an optional numeric damage override and a lazily allocated component patch. Plain stacks allocate no component storage. An empty slot or a consumed stack is None; it is never a zero-count ItemStack.

Every loaded item, vanilla or modded, has an ItemKey. Resolve identifiers through Host::item_key once when loading data and retain the key. Forward references requested during registration keep their slots when the load completes; unresolved references refuse the load. Keys are process-local identities, not persistent names. Tags likewise have retained TagKey identities, while their item and fluid membership is read through Host::tags and changes on reload. Unknown tags are empty; unavailable authoritative mappings return None.

Stored values and effective components

Count and component edits do not consult the game. new refuses zero; set_count and grow refuse invalid or overflowing counts without changing the original value. Consuming split and shrink return optional portions so consuming the last item needs no empty sentinel.

The damage representation has three states:

Stored stateEffective meaning
No numeric override and no removalInherit the item's prototype, including its default damage of zero when present. A new plain sword needs no Components allocation.
Some(0)An explicitly present zero-damage component, even on an item without a damage default. set_damage(0) creates this state without allocating a patch.
No numeric override and !minecraft:damage in the patchThe component is explicitly removed, including an inherited default. clear_damage creates this state.

damage() returns the stored number or zero. damage_component() distinguishes an explicit zero from no numeric override. set_damage(u32) is infallible; the game validates its effective maximum when accepting a write. Adding only a maximum-damage override to an item does not invent a damage component: explicitly adding Some(0) is what makes that component present.

All numeric damage lives in the optional field. Raw damage entries normalize out of the component patch; malformed numbers, conflicting numeric values, or a simultaneous value and removal refuse the entire replacement. Generic component setters and raw set_components therefore return Result. A refusal leaves the stack unchanged. Typed damage setters do not need an error path.

ItemResource::with_component is also fallible for raw component values. Its default-value elision and without_component preserve the normal prototype-plus-patch semantics. The latter records real NBT removal entries for default components; it is not an unknown-value fallback.

Game boundaries

A consume or death-protection override whose opaque native effect payload cannot be decoded refuses the whole stack write with StackWriteError::InvalidEffectPayload, before any resident state changes. The engine's infallible preserved-component setter remains infallible: invalid replacements retain its prior raw component and decoded cache rather than erasing them.

The items host area owns metadata reads such as max_stack_size(ItemKey) and max_damage(ItemKey). A stack does not take a host just to read a maximum, and an unavailable maximum never turns into a fabricated limit of 64.

The game validates effective count and durability limits, including component overrides, when accepting a resident write. An effective max_stack_size must be in 1..=99, and the presence of max_damage forbids a stack limit above one, even when the current count is one. Removing max_stack_size uses the absent limit of one. Invalid values produce typed errors, not clamped values. A transfer request is different: ItemResource plus a separate amount can request or return more than one resident stack's capacity. The resident writes it produces still have to pass validation.

Game inventory, menus, trades and models keep their engine-owned representations. Native hooks and host methods exchange owned API snapshots. The game converts only after a listener guard, and stages every mutable result before committing any captured output. Invalid hook edits retain those captured engine values. Consuming mutable events such as bonemeal and item_after_vertical_collision lend Option<ItemStack> so a listener can consume the value by setting None.

The item_stacked_on_other bridge commits its clicked slot, cursor and cancellation flags as one unit, including the menu's slot-operation queues and map-id reservations. The block-entity tick, menu-button and menu-quick-move bridges validate their captured snapshot outputs together before applying them. These are output guarantees, not whole-world transactions: independent ServerHost storage writes, commands, messages and other effects are not rolled back on a refused output. Carrier-form callbacks such as bonemeal and item_after_vertical_collision also retain their captured stack outputs on refusal but do not journal independent host writes. The storage-journal follow-up covers that gap. Restricted in-write LevelHost callbacks keep their existing write and random-draw order.

Item-model callbacks receive the owned stack and a separate bundle_selected number (-1 means none). Runtime selection is not a persistent component. Drawing receives the API-owned ItemDisplayContext, not the renderer's enum.

The NBT escape

ItemStack::from_nbt(host, compound) and to_nbt(host) are the NBT escape. They live on the plain API value, not as a second engine implementation in the items host area. They use only typed host key lookup and canonical id metadata, so native and WebAssembly mods execute the same parsing and serialization code. Persistent NBT contains identifiers, never process-local key slots. Unknown ids, malformed values and zero counts fail decoding; unavailable id metadata fails encoding.

The escape preserves explicit zero damage, default-component removals and opaque component NBT. It does not apply resident maxima or materialize prototype defaults. Those remain the responsibility of the metadata service and game write boundary respectively.