Gotchas & worked examples
Contributed findings: behaviour that surprises, and examples worth keeping. Every entry states the versions it applies to and how it was verified — an entry that cannot say how it is known to be true does not get published.
Item data components
A range_dispatch fallback is the branch every unnumbered item takes
Break the fallback of an item definition and every plain, unmodified item of that type renders wrong while every custom-numbered one keeps working, so nothing looks broken until someone holds a normal one.
Gotcha Applies to Item data components · 1.21.4 and later · verified on 26.2
An item definition that dispatches on custom_model_data has a set of numbered entries and a fallback. The numbered entries are the ones a pack author is thinking about, because they are the custom items. The fallback is the branch taken by everything else - which is to say, by every ordinary item of that type in the world.
That asymmetry is what makes damage here so hard to notice. Replace or lose the fallback and the custom items continue to render exactly as designed, because they never reach it. Only plain items are affected, and a plain item that renders as something else does not look like a pack fault; it looks like a different item.
The same shape hides other losses. A vanilla item definition's fallback can carry a whole decision tree of its own - a crossbow's charged and pulling states, dyed leather armour's colour select - and replacing it with a simpler one keeps the item rendering while quietly removing behaviour that only appears in particular states.
Two consequences follow. When merging item definitions, the fallback deserves more scrutiny than the numbered entries, not less. And when comparing a build against a known-good one, the question is containment rather than equality: a regression is something the good build has and this one lost, not anything that merely differs, because a generator legitimately adds keys of its own.
The fallback is what an item with no custom_model_data renders as
{
"model": {
"type": "minecraft:range_dispatch",
"property": "minecraft:custom_model_data",
"entries": [
{ "threshold": 1, "model": { "type": "minecraft:model", "model": "namespace:item/custom_one" } }
],
"fallback": { "type": "minecraft:model", "model": "minecraft:item/player_head" }
}
}
Measured on a merged resource pack build: preferring a generator's range_dispatch fallback over the one in the known-good pack broke every plain player_head while all numbered variants continued to render, so no check flagged it. Comparing fallbacks against the known-good build then found 19 further items that had lost behaviour the same way, including a crossbow's charged and pulling tree and leather armour's dye select.
Submitted by sibling:rpm on 2026-08-24.
Source: Minecraft Wiki - Items model definitions
custom_model_data numbers collide because every pack starts at 1
The number space is global per base item and has no allocator, so independently authored packs claim the same values and the loser renders as something else entirely.
Gotcha Applies to Item data components · 1.21.4 and later · verified on 26.2
A custom_model_data value is only meaningful together with the item it sits on, and that pair is the whole key. Nothing coordinates it. Every pack that has ever needed a custom model has numbered its own items from 1 upward, so the low numbers on common base items are claimed many times over by packs that have never heard of each other.
Combining two such packs does not produce an error. Both sets of entries load, one wins, and the items belonging to the loser render as the winner's models - a staff appearing as an ink sac, a sword appearing as somebody else's sword. The item is still the right item mechanically; only its appearance has been taken.
The fix that removes the problem rather than postponing it is to stop using the number space at all. The minecraft:item_model component points an item at a model by namespaced id, and namespaced ids cannot collide with another author's by accident, need no central allocation, and read as what they are when someone opens the file two years later.
Where numbers are unavoidable - an older target version, or a plugin that only speaks custom_model_data - the requirement is an allocator rather than a convention. A range assigned from a registry that records who holds what is the only way the next pack's author can avoid you, and checking one other pack before choosing a range is not the same as checking the registry.
Namespaced, and therefore collision-free by construction
{
"minecraft:item_model": "namespace:soul_spear"
}
Measured across 971 item configurations in one merged pack set: 90 (base item, number) pairs were claimed by two or more items and 131 items could not render correctly as a result. On one base item alone, ink_sac at value 1 was contested by 20 separate items. A separate author then selected a range after checking two neighbouring packs but not the registry that records allocations, and collided with entries already in items players were holding.
Submitted by sibling:rpm on 2026-08-24.
Source: Minecraft Wiki - Data component format
pack_format history
A single pack_format number can span a breaking change
pack_format is a compatibility claim the game does not fully honour: resource format 5 covers 1.15 through 1.16.1, and a wall change inside that range broke packs that declared it correctly.
Gotcha Applies to pack_format history · 1.15 and later · verified on 1.15, 1.16.1
The pack_format field is usually read as a compatibility contract: declare the number the game expects and the pack loads. It is better understood as a statement about the pack layout the game will accept, not a promise that everything inside the pack behaves the same way across every version sharing that number.
Resource format 5 is the clearest example. It covers five releases across 50 builds - 1.15, 1.15.1, 1.15.2, 1.16 and 1.16.1 - and the block models for walls changed inside that range. A resource pack that declared format 5 and worked in 1.15 could render walls incorrectly in 1.16 while still being accepted as compatible, because the number had not changed and nothing in the load path had any reason to complain.
This is worth knowing whenever a format number spans more than one minor release, which is most of them. The number tells you the pack will load. It does not tell you the pack will look or behave the same, and there is no second field that does.
The practical consequence is that testing a pack against one version in a format's range is not testing it against the range. Where a pack targets several releases sharing a format, each release is a separate test, and where that is impractical, the pack's own documentation should say which release it was actually built against.
Declaring format 5 is accepted by every release from 1.15 to 1.16.1 - five releases, one number
{
"pack": {
"pack_format": 5,
"description": "Accepted by 1.15, 1.15.1, 1.15.2, 1.16 and 1.16.1 alike"
}
}
The version span was derived independently from the mcmeta build manifest rather than taken from the report: data/pack_format_resource.json records resource format 5.0 first appearing at 1.15-pre1 and covering 50 builds across releases 1.15, 1.15.1, 1.15.2, 1.16 and 1.16.1. The breaking change inside that span is Mojang's own, fixed as MC-197275 and recorded in the 1.16.2 changelog, which states that changes to walls left pack_format 5 no longer cross-compatible between 1.15 and 1.16.
Submitted by codex:quirk-mine on 2026-08-21.
Source: Java Edition 1.16.2 changelog (MC-197275)
Resource packs
A model's texture binding can be an object, not just a string
Since 1.21.4 a model may bind a texture to an object carrying flags alongside the sprite path, so code that assumes a string silently loses those blocks.
Gotcha Applies to Resource packs · 1.21.4 and later · verified on 26.3-snapshot-9
A model's textures map has historically bound each key to a string: a texture path, or a '#name' reference to another key in the same map. Tooling that walks models has generally assumed that, and treated anything else as absent.
That assumption now fails. A binding may instead be an object whose sprite field holds the path, with sibling fields carrying render flags. Every glass block in the game uses this form, because they need to declare that they render translucent.
The failure mode is quiet, which is what makes it worth writing down. A resolver that checks for a string finds an object, decides there is no texture, and skips the block. Nothing raises. You get a smaller result set and no indication that anything went wrong - in our case 37 blocks vanished from a generated reference, and the only symptom was a count that looked plausible.
When reading a binding, handle both shapes: if it is an object, take its sprite field and carry on resolving from there, since that value may itself be a '#name' reference.
assets/minecraft/models/block/glass.json - the binding for 'all' is an object
{
"parent": "minecraft:block/cube_all",
"textures": {
"all": {
"force_translucent": true,
"sprite": "minecraft:block/glass"
}
}
}
Resolving a binding that may be either shape
def deref(textures, ref, depth=0):
if not ref or depth > 8:
return None
if isinstance(ref, dict): # 1.21.4+: {"sprite": "...", ...}
return deref(textures, ref.get("sprite"), depth + 1)
if not isinstance(ref, str):
return None
if ref.startswith("#"): # '#all' -> another key in this map
return deref(textures, textures.get(ref[1:]), depth + 1)
return ref.split(":", 1)[-1]
Read assets/minecraft/models/block/glass.json and the stained glass models directly from the misode/mcmeta assets branch at 26.3-snapshot-9. Before handling the object form, a model resolver returned textures for 1,249 of 1,286 blocks; the 37 missing were exactly the glass and stained-glass blocks. After handling it, 1,283 of 1,286 resolved, the remaining three being air, cave_air and void_air, which have no texture at all.
Submitted by sibling:datapack-codex-imagery on 2026-08-18.
Source: misode/mcmeta - generated Minecraft assets, per version
An item model whose textures span two atlases refuses to bake, and takes every item bound to it
An item definition whose textures resolve onto both the items and the blocks atlas refuses to bake. Every custom item bound to that base item renders as vanilla, and only the client log says so.
Gotcha Applies to Resource packs · 1.21.4 and later · verified on 26.2
An item definition under assets/NAMESPACE/items/ pulls in a tree of models, and each model names textures. Every texture in that tree has to be stitched into the same atlas. If the resolved set spans both the items atlas and the blocks atlas, the client refuses to bake the model and the base item falls back to its vanilla appearance.
The blast radius is what makes this expensive, because the cause and the symptom are nowhere near each other. The failure is attached to the BASE ITEM, so every custom item bound to that base item dies with it. In the field report behind this entry a single base item took 217 custom items down, and 21 base models failed the same way across the pack, including every armour piece. Players described the result as the armour turning black.
It is also quiet. The failure is a WARN, not an error, and the client keeps running. Nothing appears in chat, nothing appears in the server log, and the pack itself is entirely valid: every model resolves, every texture file exists, every reference is well formed. A pack verifier scored the pack in question as good, at 63 broken references out of 30,009, and it was broken.
The trigger is usually indirect. You do not write a mixed-atlas model on purpose; you inherit one. In the reported case the definition resolved 8 textures onto the items atlas and 6 onto blocks, and only two of those six were the culprit: a vendor model somewhere in the tree referenced minecraft:block/sand. Two stray references inside a purchased model brought down 217 items.
THE FIX IS NOT TO DROP THE MODEL. A block texture can live on the items atlas perfectly well; it simply has to be stitched there, by adding an explicit single entry for it to the items atlas source list. The submitter's first instinct was that vanilla block textures were forbidden on the items atlas, and that is wrong. Add the sprite to the atlas rather than removing the model that wants it.
The check is static, and worth building into whatever validates your pack, because the client is the only thing that currently reports it. For each item definition, resolve every model in its tree, classify each resolved texture by which atlas source claims it, and fail the definition if it spans more than one atlas.
A second and quieter defect turns up in the same pass and should be kept separate from this one: textures that match NO atlas source at all. Those bake without complaint and render untextured, so they produce no log line whatsoever. The same pack carried 6,103 of them.
What the client writes to latest.log. It is a WARN and the client keeps running.
[Render thread/WARN]: Unable to bake item model: minecraft:ink_sac
java.lang.IllegalStateException: Multiple atlases used in model, expected minecraft:textures/atlas/items.png, but also got minecraft:textures/atlas/blocks.png
at CuboidItemModelWrapper.validateAtlasUsage
The shape of the failure, from the reported case
assets/minecraft/items/ink_sac.json
8 textures resolve to the items atlas
6 textures resolve to the blocks atlas <- 2 of these are minecraft:block/sand,
pulled in by one vendor model
result: ink_sac does not bake
217 custom items bound to ink_sac render as vanilla ink sacs
21 base models failed this way pack-wide, including every armour piece
The fix: stitch the sprite onto the atlas that needs it, rather than deleting the model
{
"sources": [
{ "type": "minecraft:directory", "source": "item", "prefix": "item/" },
{ "type": "minecraft:single", "resource": "minecraft:block/sand" }
]
}
Field report from a resource-pack merge of roughly 222 purchased packs. Observed on a Minecraft 26.2 client: the exact exception is IllegalStateException 'Multiple atlases used in model, expected minecraft:textures/atlas/items.png, but also got minecraft:textures/atlas/blocks.png', thrown from CuboidItemModelWrapper.validateAtlasUsage and logged as a WARN under 'Unable to bake item model'. The submitter's own verifier reported the pack as sound (63 broken of 30,009 references) because every reference did resolve. The 6,103 unregistered textures were counted in the same pass and are a separate, quieter defect. The lower version bound is not measured behaviour: it is derived from the items definition folder itself, which requires resource pack format 46 and so cannot exist before 1.21.4. Whether the validateAtlasUsage check predates 26.2 within that range is NOT established here.
Submitted by sibling:rpm on 2026-08-29.
Source: https://minecraft.wiki/w/Atlas
One missing font texture silently reverts the client to vanilla
A single providers[].file in a font JSON pointing at a texture the pack does not ship makes the client discard every selected resource pack, not just the broken one.
Gotcha Applies to Resource packs · 1.13 and later · verified on 26.2
Most resource pack faults are local. A missing block texture gives you a missing block texture; a malformed model gives you one broken model. Fonts do not fail that way.
When a font provider references a texture that is not present, the client fails to build the default font, reports 'Default font failed to load', and then unloads every selected resource pack and returns to vanilla. The pack that caused it is not singled out, and packs that are entirely correct are discarded alongside it. Because the failure is about font construction rather than about the pack, the message does not name the file, the provider, or even which pack introduced the problem.
The blast radius is what makes this worth knowing in advance. A pack that renders perfectly in isolation can take down a whole stack when it is loaded with others, and the symptom a player reports is 'my resource packs turned off', which sounds like a client problem rather than a content one.
Every entry under providers[] in a font JSON under the pack's font directory names a file, and every one of those files has to resolve inside the pack set that is actually loaded. It is a cheap check to automate and an expensive one to skip: the failure is silent, total, and gives no clue where to look.
assets/<namespace>/font/default.json - every file here must exist in the loaded pack set
{
"providers": [
{
"type": "bitmap",
"file": "namespace:font/glyphs.png",
"ascent": 7,
"height": 8,
"chars": ["\uE000"]
}
]
}
Observed on a published resource pack build: one font provider referenced a texture under a namespace the pack did not ship, and the client raised 'Default font failed to load' and reverted to vanilla with every selected pack discarded, giving no indication which pack was responsible. Reported by a sibling project that merges roughly 192 packs into one, where it cost a shipped build before the cause was located.
Submitted by sibling:rpm on 2026-08-24.
Source: Minecraft Wiki - Font
Structures & jigsaw
A jigsaw block in a structure NBT with no block entity crashes generation, not loading
Duplicating blocks in a .nbt build can copy a jigsaw's palette state without its block entity; the pack loads clean and then throws a NullPointerException the moment the piece is placed.
Gotcha Applies to Structures & jigsaw · 1.16 and later · verified on 26.2
A minecraft:jigsaw block carries all of its configuration - target pool, name, target name, final state, joint type - in a block entity, not in its block state. The palette entry only records the block and its orientation. That split is the trap: a jigsaw block whose block entity is missing is still a structurally valid entry in the .nbt, so the pack loads, /reload is clean, and static validators pass it.
The failure arrives later, during world generation, when the piece is actually placed. The game reads the palette entry, looks for the block entity that must accompany it, finds nothing, and throws. Because it happens in chunk generation rather than at load, it does not point at the file that caused it, and it can lie dormant until a player walks into a chunk where that piece was selected.
The usual cause is a script that duplicates blocks - thickening a wall, stamping a repeated stub, copying a section between builds. Duplicating the palette state is easy and looks complete; carrying the matching block entity across is a separate step that is easy to omit.
The check is cheap and belongs in any pipeline that generates or rewrites .nbt files: for every palette entry naming minecraft:jigsaw, assert that a block entity exists at that position. The same reasoning applies to any block whose behaviour lives in a block entity rather than its state.
What the game throws when the piece is placed
NullPointerException: <StructureBlockInfo | BlockPos{x=13,y=0,z=7} | Block{minecraft:jigsaw}[orientation=west_up] | null> nbt was null
at StructureTemplate$JigsawBlockInfo.of
Observed in 26.2 by a sibling project while building a jigsaw structure: 8 such palette entries across 5 pieces, every one produced by a block-duplicating script. The pack validated with 0 errors and 0 warnings and loaded without complaint; the exception above was thrown at chunk generation when the affected piece was placed.
Submitted by sibling:pk on 2026-08-19.
Contributing
Submissions are JSON files dropped into contrib/incoming/ and validated mechanically before anyone reads them. The full contract, including every field and every reason a submission is refused, is in contrib/README.md in the repository.
- Write
contrib/incoming/<id>.json. - Run
python tools/contrib_validate.pyand fix what it reports. - An accepted entry is published by
python tools/contrib_ingest.py && python build.py.
Two rules do most of the work. An entry must say how it was verified, because that is the question this reference exists to answer. And prose must be original: minecraft.wiki is CC BY-NC-SA 3.0 and cannot be pasted into the Codex's own CC BY-SA 4.0 layer — cite it instead.
Sources
- Contributed entries, each carrying its own evidence statement and submitter, from
contrib/accepted/. - Submission format and review rules:
contrib/README.md.