Claude Skill

minecraft-resource-pack

Create and debug Minecraft 26.x and 1.21.x resource packs, including pack metadata, textures, models, blockstates, item definitions, sounds, fonts, animations, and shaders. Use for client-side visual or audio assets without gameplay code.

LLM Mart · 0 points · 13 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download jahrome907-minecraft-agent-skills-plugins_minecraft-codex-skills_skills_minecraft-resource-pack-40b1d4e.zip · 12 KB
Part of jahrome907/minecraft-agent-skills — 52 skills

Install

skills CLI npx skills add https://github.com/Jahrome907/minecraft-agent-skills/tree/main/plugins/minecraft-codex-skills/skills/minecraft-resource-pack
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install jahrome907-minecraft-agent-skills@llmmart
Git git clone https://github.com/Jahrome907/minecraft-agent-skills.git

The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole jahrome907/minecraft-agent-skills collection as a plugin from our marketplace. Git is the plain clone.

Skill manifest

Minecraft Resource Pack Skill

What Is a Resource Pack?

A resource pack is a folder (or .zip) that overrides or adds Minecraft's visual and audio assets: textures, models, sounds, language files, and fonts. No Java or mod loader is required for those vanilla assets.

Routing Boundaries

  • Use when: the deliverable is visual/audio assets (textures, models, sounds, fonts) in resource-pack format.
  • Do not use when: the task requires gameplay logic or runtime behavior changes (use minecraft-datapack, minecraft-plugin-dev, or minecraft-modding).
  • Do not use when: the task is server infrastructure/runtime administration (minecraft-server-admin).

Pack Metadata

Minecraft Version Preferred pack metadata
1.21 / 1.21.1 pack_format: 34
1.21.2 / 1.21.3 pack_format: 42
1.21.4 pack_format: 46
1.21.5 pack_format: 55
1.21.6 pack_format: 63
1.21.7 / 1.21.8 pack_format: 64
1.21.9 / 1.21.10 min_format: [69, 0], max_format: [69, 0]
1.21.11 min_format: [75, 0], max_format: [75, 0]
26.1 min_format: [84, 0], max_format: [84, 0]
26.2 min_format: [88, 0], max_format: [88, 0]

Use legacy pack_format only for resource formats below 65 (through 1.21.8). Resource format 65 and later require both min_format and max_format; use [major, minor] for exact patch targeting, including .0 such as [84, 0]. An integer or [major] min_format means [major, 0]; an integer or [major] max_format permits every minor version on that major line. Do not write decimal JSON numbers. A legacy-only pack can use its integer pack_format alone. A range whose min_format includes a legacy format needs integer pack_format and supported_formats; do not include supported_formats for a modern-only range.


Directory Layout

my-pack/
├── pack.mcmeta
├── pack.png                   ← 64×64 icon (optional)
└── assets/
    └── minecraft/             ← override vanilla (or <namespace>/ for new packs)
        ├── models/
        │   ├── block/
        │   │   └── stone.json
        │   └── item/
        │       └── diamond_sword.json
        ├── items/              ← 1.21.4+ item model definitions
        │   └── diamond_sword.json
        ├── blockstates/
        │   └── stone.json
        ├── textures/
        │   ├── block/
        │   │   └── stone.png
        │   ├── item/
        │   │   └── diamond_sword.png
        │   ├── gui/
        │   │   └── sprites/
        │   │       └── my_sprite.png
        │   └── entity/
        │       └── zombie/
        │           └── zombie.png
        ├── sounds/
        │   └── custom/
        │       └── my_sound.ogg
        ├── sounds.json
        ├── font/
        │   └── default.json
        └── lang/
            └── en_us.json

Client-mod-specific files such as OptiFine CIT and Iris shader packs use their own formats. Read conditional assets before adding them.


pack.mcmeta

1.21.8 and earlier

{
  "pack": {
    "pack_format": 64,
    "description": "My Custom Resource Pack v1.0"
  }
}

1.21.9 / 1.21.10

{
  "pack": {
    "min_format": [69, 0],
    "max_format": [69, 0],
    "description": "My Custom Resource Pack v1.0"
  }
}

1.21.11

{
  "pack": {
    "min_format": [75, 0],
    "max_format": [75, 0],
    "description": "My Custom Resource Pack v1.0"
  }
}

26.2

{
  "pack": {
    "min_format": [88, 0],
    "max_format": [88, 0],
    "description": "My Custom Resource Pack v1.0"
  }
}

Block Models

assets/minecraft/models/block/my_cube.json

Full cube — all six faces use the same texture:

{
  "parent": "minecraft:block/cube_all",
  "textures": {
    "all": "minecraft:block/stone"
  }
}

Column block (like logs):

{
  "parent": "minecraft:block/cube_column",
  "textures": {
    "end": "mypack:block/my_pillar_top",
    "side": "mypack:block/my_pillar_side"
  }
}

Different sides:

{
  "parent": "minecraft:block/cube",
  "textures": {
    "up":    "mypack:block/my_block_top",
    "down":  "mypack:block/my_block_bottom",
    "north": "mypack:block/my_block_side",
    "south": "mypack:block/my_block_side",
    "east":  "mypack:block/my_block_side",
    "west":  "mypack:block/my_block_side",
    "particle": "mypack:block/my_block_side"
  }
}

Cross model (flowers, plants):

{
  "parent": "minecraft:block/cross",
  "textures": {
    "cross": "mypack:block/my_flower"
  }
}

Custom geometry (elements)

{
  "credit": "Custom model",
  "ambientocclusion": true,
  "textures": {
    "0": "mypack:block/panel",
    "particle": "mypack:block/panel"
  },
  "elements": [
    {
      "from": [0, 0, 7],
      "to": [16, 16, 9],
      "faces": {
        "north": { "texture": "#0", "uv": [0, 0, 16, 16] },
        "south": { "texture": "#0", "uv": [0, 0, 16, 16] }
      }
    }
  ],
  "display": {
    "thirdperson_righthand": {
      "rotation": [75, 45, 0],
      "translation": [0, 2.5, 0],
      "scale": [0.375, 0.375, 0.375]
    }
  }
}

from and to are in 1/16th block units (0–16). uv is [x1, y1, x2, y2] in 0–16 units.


Item Models

For 1.21.4 and later, use item definitions in assets/<namespace>/items/. For 1.21.3 and earlier, use the legacy model overrides array. Read conditional assets for the string-based current custom_model_data selector and the legacy numeric predicate; their values are not interchangeable.


Blockstate Definitions

Simple block (no variants)

{
  "variants": {
    "": { "model": "mypack:block/my_block" }
  }
}

Facing block (4 rotations)

{
  "variants": {
    "facing=north": { "model": "mypack:block/my_block" },
    "facing=south": { "model": "mypack:block/my_block",  "y": 180 },
    "facing=east":  { "model": "mypack:block/my_block",  "y": 90  },
    "facing=west":  { "model": "mypack:block/my_block",  "y": 270 }
  }
}

Random texture (multipart)

{
  "variants": {
    "": [
      { "model": "minecraft:block/grass_block",  "weight": 3 },
      { "model": "minecraft:block/grass_block_2" }
    ]
  }
}

Multipart (slabs, fences, walls)

{
  "multipart": [
    { "apply": { "model": "mypack:block/my_slab_bottom" }, "when": { "type": "bottom" } },
    { "apply": { "model": "mypack:block/my_slab_top"    }, "when": { "type": "top"    } },
    { "apply": { "model": "mypack:block/my_block"        }, "when": { "type": "double" } }
  ]
}

Textures

  • Format: PNG, RGBA (32-bit)
  • Standard block/item size: 16×16 px
  • Textures can be larger (32×32, 64×64) — Minecraft scales them, but stick to powers of 2
  • Animation requires height = N × width (e.g., 16×64 for 4 frames)
  • Place block textures in assets/<namespace>/textures/block/
  • Place item textures in assets/<namespace>/textures/item/
  • All textures are referenced without the .png extension in JSON

Animated texture MCMETA

assets/minecraft/textures/block/fire_0.png.mcmeta:

{
  "animation": {
    "frametime": 2,
    "frames": [0, 1, 2, 3, 4, 5, 6, 7]
  }
}

If frames is omitted, all frames play sequentially. frametime is in game ticks (default 1).

GUI sprites (1.20.2+)

Place sprites at assets/minecraft/textures/gui/sprites/<category>/<name>.png. Reference them with <category>/<name> in code/JSON.

26.1 block-model texture entries

For 26.1, a block-model textures entry may remain a sprite string or use an object. The object must have a string sprite; force_translucent, when set, must be a boolean.

{
  "textures": {
    "all": {
      "sprite": "mypack:block/frosted_panel",
      "force_translucent": true
    }
  }
}

Both forms identify a sprite without .png. Use force_translucent only when the geometry must render in the translucent pass despite its sprite pixels.


Sounds

An event's namespace comes from the namespace containing sounds.json; a sound entry's name identifies the sound-file namespace. Sound files are Vorbis .ogg under assets/<namespace>/sounds/. Read conditional assets for a correct event example, aliases, sound-source selection, and replacement behavior.


Language Files

assets/minecraft/lang/en_us.json:

{
  "block.mypack.my_block": "My Custom Block",
  "item.mypack.my_item": "Magic Wand",
  "entity.mypack.my_mob": "Forest Guardian",
  "death.attack.mypack.laser": "%1$s was zapped by %2$s"
}
  • Use the exact translation key format for your mod/datapack namespace
  • File name is the locale code (e.g., fr_fr.json, de_de.json)
  • Always provide en_us.json as the primary fallback

Fonts

Read conditional assets for bitmap provider layout and private-use icon guidance.


OptiFine and shaders

OptiFine CIT and Iris shader packs have client-mod-specific formats. Read conditional assets before adding either; they are not portable vanilla resource-pack features.


Installation

# Singleplayer: place in
~/.minecraft/resourcepacks/my-pack/
# or
~/.minecraft/resourcepacks/my-pack.zip

# Server-side (forces on clients):
# Set in server.properties:
resource-pack=https://example.com/my-pack.zip
resource-pack-sha1=<sha1 hash>
resource-pack-prompt={"text":"Required pack","color":"gold"}

Common Issues

Problem Cause Fix
Model not showing Wrong JSON path or syntax error Check assets/<namespace>/models/ path; validate JSON
Black/pink checkerboard Texture path wrong or missing Check textures/ path, file extension not in JSON
Blockstate not applying Wrong state property name Use F3 to inspect block state; use /data get block <x> <y> <z> for block-entity NBT
Animation not working Wrong MCMETA location Must be same folder as texture, named texture.png.mcmeta
Custom sound not playing Not in sounds.json Register sound event in sounds.json, match namespace
Pack not loading Wrong pack_format or min_format / max_format values Update pack.mcmeta for the exact 1.21.x patch

Validator Script

Use the bundled validator script before shipping a resource-pack update:

Unbundled references to another namespace's models, textures, sounds, or fonts are warnings requiring runtime verification with the dependency present. Missing files in the current namespace fail; strict mode also fails unresolved warnings.

# Run from the installed skill directory (for example `.claude/skills/minecraft-resource-pack`):
./scripts/validate-resource-pack.sh --root /path/to/resource-pack

# Strict mode treats warnings as failures:
./scripts/validate-resource-pack.sh --root /path/to/resource-pack --strict

What it checks:

  • JSON validity for pack.mcmeta and assets/**/*.json
  • Legacy versus modern pack.mcmeta field shape, including integer, [major], and [major, minor] versions
  • Model/blockstate/font/sounds references resolve to real files
  • Current custom_model_data select cases use strings; 26.1 texture objects use a string sprite and optional boolean force_translucent
  • Same-namespace type: "event" sound aliases name an event in that sounds.json
  • Every *.png.mcmeta has a matching *.png

The validator cannot resolve a sound event owned by another namespace, or prove that a command or mod code invokes an event with the intended sound source. Those cases warn and require an exact-client runtime check.


References

Files (minecraft-agent-skills)
  • references
    • conditional-assets.md 3.7 KB
      # Conditional Resource-Pack Assets
      
      Read this reference only when the requested pack uses current or legacy item
      models, sounds, fonts, OptiFine, or shaders. Keep each feature within the target
      client's supported format.
      
      ## Current and legacy item models
      
      For **1.21.4 and later**, place item definitions in
      `assets/<namespace>/items/<item>.json`. The `minecraft:custom_model_data`
      string property reads the component's `strings` list. A `when` value can be one
      string or a non-empty list of strings; numeric values such as `1001` are not
      valid for this selector.
      
      ```json
      {
        "model": {
          "type": "minecraft:select",
          "property": "minecraft:custom_model_data",
          "fallback": {
            "type": "minecraft:model",
            "model": "minecraft:item/stick"
          },
          "cases": [
            {
              "when": ["magic_wand"],
              "model": {
                "type": "minecraft:model",
                "model": "mypack:item/magic_wand"
              }
            }
          ]
        }
      }
      ```
      
      Give the matching item component with a string value:
      
      ```mcfunction
      /give @s minecraft:stick[custom_model_data={strings:["magic_wand"]}]
      ```
      
      For **1.21.3 and earlier**, use a model's legacy `overrides` array and numeric
      `predicate.custom_model_data` values. Do not mix the legacy override format into
      the current `items/` definition.
      
      ```json
      {
        "parent": "minecraft:item/handheld",
        "textures": { "layer0": "minecraft:item/stick" },
        "overrides": [
          {
            "predicate": { "custom_model_data": 1001 },
            "model": "mypack:item/magic_wand"
          }
        ]
      }
      ```
      
      ## Sounds
      
      The namespace of an event is the namespace containing its `sounds.json`. The
      namespace in a sound entry's `name` identifies the sound-file namespace. Put a
      custom event in `assets/mypack/sounds.json` and its file at
      `assets/mypack/sounds/ui/click.ogg`.
      
      ```json
      {
        "ui.click": {
          "sounds": [
            { "name": "mypack:ui/click", "volume": 1.0 }
          ]
        },
        "ui.click_alias": {
          "sounds": [
            { "name": "mypack:ui.click", "type": "event" }
          ]
        }
      }
      ```
      
      An omitted `type` is a sound-file entry and resolves to an `.ogg` file. An
      `event` entry references another sound event and must never be treated as a file
      path. `sounds.json` maps events to sound objects; it does not choose the sound
      source. A command chooses its source, for example
      `/playsound mypack:ui.click ui @s` (the `ui` source is available from 1.21.6),
      and mod code supplies the relevant `SoundSource`. Use `replace: true` only when
      intentionally replacing an event inherited from a lower-priority pack.
      
      ## Fonts
      
      Place font definitions at `assets/<namespace>/font/<name>.json`. A bitmap
      provider's `file` is a texture resource location including `.png`.
      
      ```json
      {
        "providers": [
          {
            "type": "bitmap",
            "file": "mypack:font/icons.png",
            "ascent": 8,
            "height": 9,
            "chars": ["\uE000", "\uE001"]
          }
        ]
      }
      ```
      
      Use private-use characters deliberately and keep each bitmap cell consistent
      with its declared height.
      
      ## Client-mod and shader boundaries
      
      OptiFine CIT files under `assets/minecraft/optifine/` are OptiFine-specific and
      do not work in vanilla or Iris. Do not present them as a portable item system.
      
      Iris shader packs are separate ZIPs with a top-level `shaders/` directory. They
      are selected through Iris, not loaded as `assets/iris/shaders` inside a resource
      pack. Vanilla resource packs do not provide a supported way to override core
      render shaders. Keep shader packs separate from a vanilla resource-pack
      deliverable, and test them with the exact Iris version named by the user.
      
      ## Sources
      
      - [Mojang: Java Edition 1.21.4 item models](https://www.minecraft.net/en-us/article/minecraft-java-edition-1-21-4)
      - [NeoForge 1.21.8 sounds](https://docs.neoforged.net/docs/1.21.8/resources/client/sounds/)
      - [Iris documentation](https://irisshaders.dev/)
      
  • scripts
    • jq-shim.mjs 12.7 KB · in bundle
    • validate-resource-pack.sh 13.9 KB
      #!/usr/bin/env bash
      set -euo pipefail
      
      PASS='[PASS]'
      WARN='[WARN]'
      FAIL='[FAIL]'
      
      ROOT='.'
      STRICT=0
      
      while [[ $# -gt 0 ]]; do
        case "$1" in
          --root)
            ROOT="${2:-}"
            shift 2
            ;;
          --strict)
            STRICT=1
            shift
            ;;
          --help|-h)
            cat <<'USAGE'
      Usage: validate-resource-pack.sh [--root <path>] [--strict]
      
      Checks resource pack integrity:
      - JSON validity for assets/** and pack.mcmeta
      - model/blockstate/item-definition/font/sounds references point to existing files
      - every .png.mcmeta has a matching .png
      USAGE
            exit 0
            ;;
          *)
            echo "$FAIL unknown arg: $1" >&2
            exit 1
            ;;
        esac
      done
      
      if ! command -v jq >/dev/null 2>&1; then
        SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
        JQ_SHIM="$SCRIPT_DIR/jq-shim.mjs"
        if command -v node >/dev/null 2>&1 && [[ -f "$JQ_SHIM" ]]; then
          jq() {
            node "$JQ_SHIM" "$@"
          }
        else
          echo "$FAIL jq is required"
          exit 1
        fi
      fi
      
      if [[ ! -d "$ROOT" ]]; then
        echo "$FAIL root path does not exist: $ROOT"
        exit 1
      fi
      
      FAILURES=0
      WARNINGS=0
      
      pass() { echo "$PASS $*"; }
      warn() { echo "$WARN $*"; WARNINGS=$((WARNINGS + 1)); }
      fail() { echo "$FAIL $*"; FAILURES=$((FAILURES + 1)); }
      strip_cr() { printf '%s' "${1%$'\r'}"; }
      
      check_json() {
        local file="$1"
        if jq empty "$file" >/dev/null 2>&1; then
          pass "valid JSON: ${file#$ROOT/}"
        else
          fail "invalid JSON: ${file#$ROOT/}"
        fi
      }
      
      check_pack_metadata() {
        local file="$1"
        local min_parts max_parts min_major min_minor max_major max_minor pack_format
        local has_pack_format=0
        local has_min_format=0
        local has_max_format=0
        local has_supported_formats=0
      
        if jq -e '.pack | has("pack_format")' "$file" >/dev/null 2>&1; then
          has_pack_format=1
        fi
        if jq -e '.pack | has("min_format")' "$file" >/dev/null 2>&1; then
          has_min_format=1
        fi
        if jq -e '.pack | has("max_format")' "$file" >/dev/null 2>&1; then
          has_max_format=1
        fi
        if jq -e '.pack | has("supported_formats")' "$file" >/dev/null 2>&1; then
          has_supported_formats=1
        fi
      
        if jq -e '.pack.pack_format | type == "number" and . == floor' "$file" >/dev/null 2>&1; then
          pass "pack.mcmeta uses integer pack.pack_format"
        elif [[ "$has_pack_format" -eq 1 ]]; then
          fail "pack.mcmeta pack.pack_format must be an integer when present"
        fi
      
        if jq -e '.pack.min_format | ((type == "number" and . == floor) or (type == "array" and (length == 1 or length == 2) and all(.[]; type == "number" and . == floor)))' "$file" >/dev/null 2>&1; then
          pass "pack.mcmeta uses valid pack.min_format"
        elif [[ "$has_min_format" -eq 1 ]]; then
          fail "pack.mcmeta pack.min_format must be an integer or a one/two-integer array"
        fi
      
        if jq -e '.pack.max_format | ((type == "number" and . == floor) or (type == "array" and (length == 1 or length == 2) and all(.[]; type == "number" and . == floor)))' "$file" >/dev/null 2>&1; then
          pass "pack.mcmeta uses valid pack.max_format"
        elif [[ "$has_max_format" -eq 1 ]]; then
          fail "pack.mcmeta pack.max_format must be an integer or a one/two-integer array"
        fi
      
        if [[ "$has_supported_formats" -eq 1 ]] && ! jq -e '.pack.supported_formats | ((type == "number" and . == floor) or (type == "array" and length == 2 and all(.[]; type == "number" and . == floor)) or (type == "object" and (.min_inclusive | type == "number" and . == floor) and (.max_inclusive | type == "number" and . == floor)))' "$file" >/dev/null 2>&1; then
          fail "pack.mcmeta pack.supported_formats must be an integer, integer range, or min_inclusive/max_inclusive object"
        fi
      
        if [[ "$has_min_format" -ne "$has_max_format" ]]; then
          fail "pack.mcmeta must define both .pack.min_format and .pack.max_format together"
          return
        fi
      
        if [[ "$has_min_format" -eq 1 ]]; then
          if ! jq -e '.pack.min_format | ((type == "number" and . == floor) or (type == "array" and (length == 1 or length == 2) and all(.[]; type == "number" and . == floor)))' "$file" >/dev/null 2>&1 || ! jq -e '.pack.max_format | ((type == "number" and . == floor) or (type == "array" and (length == 1 or length == 2) and all(.[]; type == "number" and . == floor)))' "$file" >/dev/null 2>&1; then
            return
          fi
      
          min_parts="$(jq -r '.pack.min_format | if type == "number" then "\(.)\t0" elif type == "array" and length == 1 then "\(.[0])\t0" else "\(.[0])\t\(.[1])" end' "$file")"
          max_parts="$(jq -r '.pack.max_format | if type == "number" then "\(.)\t2147483647" elif type == "array" and length == 1 then "\(.[0])\t2147483647" else "\(.[0])\t\(.[1])" end' "$file")"
          IFS=$'\t' read -r min_major min_minor <<<"$min_parts"
          IFS=$'\t' read -r max_major max_minor <<<"$max_parts"
      
          if (( min_major > max_major || (min_major == max_major && min_minor > max_minor) )); then
            fail "pack.mcmeta pack.min_format must not be greater than pack.max_format"
            return
          fi
      
          if (( min_major < 65 )) && ! jq -e '.pack.pack_format | type == "number" and . == floor' "$file" >/dev/null 2>&1; then
            fail "pack.mcmeta ranges that include legacy resource pack formats below 65 require integer pack.pack_format"
            return
          fi
      
          if (( min_major < 65 )) && [[ "$has_supported_formats" -ne 1 ]]; then
            fail "pack.mcmeta ranges that include legacy resource pack formats below 65 require pack.supported_formats"
            return
          fi
      
          if (( min_major >= 65 )) && [[ "$has_supported_formats" -eq 1 ]]; then
            fail "pack.mcmeta must not define pack.supported_formats for modern-only resource pack formats"
            return
          fi
      
          return
        fi
      
        if jq -e '.pack.pack_format | type == "number" and . == floor' "$file" >/dev/null 2>&1; then
          pack_format="$(jq -r '.pack.pack_format | numbers' "$file")"
          if (( pack_format < 65 )); then
            return
          fi
          fail "modern resource pack formats 65 and newer require both .pack.min_format and .pack.max_format"
          return
        fi
      
        fail "pack.mcmeta must define legacy integer .pack.pack_format or both .pack.min_format and .pack.max_format"
      }
      
      resolve_texture() {
        local current_ns="$1"
        local ref="$2"
        local ns path target
      
        [[ -z "$ref" || "$ref" == \#* ]] && return
        if [[ "$ref" == *:* ]]; then
          ns="${ref%%:*}"
          path="${ref#*:}"
        else
          ns="$current_ns"
          path="$ref"
        fi
      
        target="$ROOT/assets/$ns/textures/$path.png"
        if [[ -f "$target" ]]; then
          pass "texture exists: $ns:$path"
        elif [[ "$ns" != "$current_ns" ]]; then
          warn "external texture not locally verifiable: $ns:$path"
        else
          fail "missing texture: $ns:$path (expected ${target#$ROOT/})"
        fi
      }
      
      resolve_model() {
        local current_ns="$1"
        local ref="$2"
        local ns path target
      
        [[ -z "$ref" || "$ref" == builtin/* ]] && return
        if [[ "$ref" == *:* ]]; then
          ns="${ref%%:*}"
          path="${ref#*:}"
        else
          ns="$current_ns"
          path="$ref"
        fi
      
        if [[ "$ns" == "minecraft" ]]; then
          return
        fi
      
        target="$ROOT/assets/$ns/models/$path.json"
        if [[ -f "$target" ]]; then
          pass "model exists: $ns:$path"
        elif [[ "$ns" != "$current_ns" ]]; then
          warn "external model not locally verifiable: $ns:$path"
        else
          fail "missing model: $ns:$path (expected ${target#$ROOT/})"
        fi
      }
      
      resolve_sound() {
        local current_ns="$1"
        local sound_type="$2"
        local ref="$3"
        local sounds_file="$4"
        local ns path target event
      
        if [[ -z "$ref" ]]; then
          fail "sounds.json entry is missing a name"
          return
        fi
        if [[ "$sound_type" == "event" ]]; then
          if [[ "$ref" == *:* ]]; then
            ns="${ref%%:*}"
            event="${ref#*:}"
          else
            ns="$current_ns"
            event="$ref"
          fi
          if [[ "$ns" != "$current_ns" ]]; then
            warn "external sound event not locally verifiable: $ns:$event"
          elif jq -r 'keys[]' "$sounds_file" 2>/dev/null | grep -Fqx "$event"; then
            pass "sound event alias exists: $ns:$event"
          else
            fail "missing sound event alias target: $ns:$event"
          fi
          return
        fi
        if [[ "$sound_type" != "file" ]]; then
          fail "unsupported sounds.json entry type '$sound_type' for $ref (expected file or event)"
          return
        fi
        if [[ "$ref" == *:* ]]; then
          ns="${ref%%:*}"
          path="${ref#*:}"
        else
          ns="$current_ns"
          path="$ref"
        fi
      
        target="$ROOT/assets/$ns/sounds/$path.ogg"
        if [[ -f "$target" ]]; then
          pass "sound exists: $ns:$path"
        elif [[ "$ns" != "$current_ns" ]]; then
          warn "external sound not locally verifiable: $ns:$path"
        else
          fail "missing sound: $ns:$path (expected ${target#$ROOT/})"
        fi
      }
      
      echo "=== Resource Pack Validator ==="
      
      if [[ -f "$ROOT/pack.mcmeta" ]]; then
        check_json "$ROOT/pack.mcmeta"
        check_pack_metadata "$ROOT/pack.mcmeta"
      else
        fail "missing pack.mcmeta"
      fi
      
      if [[ ! -d "$ROOT/assets" ]]; then
        fail "missing assets/ directory"
      fi
      
      echo "Checking JSON files under assets/..."
      while IFS= read -r -d '' json_file; do
        check_json "$json_file"
      done < <(find "$ROOT/assets" -type f -name '*.json' -print0 2>/dev/null)
      
      echo "Checking model and blockstate references..."
      while IFS= read -r -d '' model_file; do
        rel="${model_file#"$ROOT/assets/"}"
        ns="${rel%%/*}"
      
        while IFS= read -r tex; do
          IFS=$'\t' read -r texture_status texture_key texture_ref <<< "$(strip_cr "$tex")"
          if [[ "$texture_status" == 'valid' ]]; then
            resolve_texture "$ns" "$texture_ref"
          else
            fail "invalid texture entry for $texture_key in ${model_file#$ROOT/} (use a string or {sprite: string, force_translucent?: boolean})"
          fi
        done < <(jq -r '(.textures // {} | to_entries[]? | [(if (.value | type) == "string" then "valid" elif (.value | type) == "object" then if ((.value.sprite? | type) == "string" and ((.value.force_translucent? | type) == "null" or (.value.force_translucent | type) == "boolean")) then "valid" else "invalid" end else "invalid" end), .key, (if (.value | type) == "string" then .value elif (.value | type) == "object" then (.value.sprite? // empty) else empty end)] | @tsv)' "$model_file")
      
        while IFS= read -r parent; do
          parent="$(strip_cr "$parent")"
          resolve_model "$ns" "$parent"
        done < <(jq -r '.parent? // empty' "$model_file")
      
        while IFS= read -r over_model; do
          over_model="$(strip_cr "$over_model")"
          resolve_model "$ns" "$over_model"
        done < <(jq -r '.overrides[]?.model? // empty' "$model_file")
      done < <(find "$ROOT/assets" -type f -path '*/models/*.json' -print0 2>/dev/null)
      
      echo "Checking item definition model references..."
      while IFS= read -r -d '' item_file; do
        rel="${item_file#"$ROOT/assets/"}"
        ns="${rel%%/*}"
        while IFS= read -r model_ref; do
          model_ref="$(strip_cr "$model_ref")"
          resolve_model "$ns" "$model_ref"
        done < <(jq -r '.. | objects | select(.type? == "minecraft:model" and (.model? | type) == "string") | .model' "$item_file")
      
        while IFS= read -r custom_model_data_case; do
          custom_model_data_case="$(strip_cr "$custom_model_data_case")"
          [[ "$custom_model_data_case" == 'valid' ]] || fail "minecraft:custom_model_data select cases must use a string or a non-empty list of strings in ${item_file#$ROOT/}"
        done < <(jq -r '.. | objects | select(.type? == "minecraft:select" and .property? == "minecraft:custom_model_data") | .cases[]? | if ((.when? | type) == "string") or ((.when? | type) == "array" and (.when | length > 0) and all(.when[]?; type == "string")) then "valid" else "invalid" end' "$item_file")
      done < <(find "$ROOT/assets" -type f -path '*/items/*.json' -print0 2>/dev/null)
      
      while IFS= read -r -d '' blockstate_file; do
        rel="${blockstate_file#"$ROOT/assets/"}"
        ns="${rel%%/*}"
        while IFS= read -r model_ref; do
          model_ref="$(strip_cr "$model_ref")"
          resolve_model "$ns" "$model_ref"
        done < <(jq -r '(
            .variants? // {} | .. | objects | .model? // empty
          ), (
            .multipart[]?.apply? | if type == "array" then .[]?.model? // empty else .model? // empty end
          )' "$blockstate_file")
      done < <(find "$ROOT/assets" -type f -path '*/blockstates/*.json' -print0 2>/dev/null)
      
      echo "Checking sounds.json references..."
      while IFS= read -r -d '' sounds_file; do
        rel="${sounds_file#"$ROOT/assets/"}"
        ns="${rel%%/*}"
        while IFS=$'\t' read -r sound_type sound_ref; do
          sound_type="$(strip_cr "$sound_type")"
          sound_ref="$(strip_cr "$sound_ref")"
          resolve_sound "$ns" "$sound_type" "$sound_ref" "$sounds_file"
        done < <(jq -r '.. | objects | select(has("sounds")) | .sounds[]? | if type == "string" then ["file", .] else [(.type // "file"), (.name // empty)] end | @tsv' "$sounds_file")
      done < <(find "$ROOT/assets" -type f -name 'sounds.json' -print0 2>/dev/null)
      
      echo "Checking font provider file references..."
      while IFS= read -r -d '' font_file; do
        rel="${font_file#"$ROOT/assets/"}"
        ns="${rel%%/*}"
        while IFS= read -r font_ref; do
          font_ref="$(strip_cr "$font_ref")"
          [[ -z "$font_ref" ]] && continue
          if [[ "$font_ref" == *:* ]]; then
            target_ns="${font_ref%%:*}"
            target_path="${font_ref#*:}"
          else
            target_ns="$ns"
            target_path="$font_ref"
          fi
      
          target="$ROOT/assets/$target_ns/textures/$target_path"
          if [[ -f "$target" ]]; then
            pass "font texture exists: $target_ns:$target_path"
          elif [[ "$target_ns" != "$ns" ]]; then
            warn "external font texture not locally verifiable: $target_ns:$target_path"
          else
            fail "missing font texture: $target_ns:$target_path (expected ${target#$ROOT/})"
          fi
        done < <(jq -r '.providers[]? | .file? // empty' "$font_file")
      done < <(find "$ROOT/assets" -type f -path '*/font/*.json' -print0 2>/dev/null)
      
      echo "Checking .png.mcmeta pairs..."
      while IFS= read -r -d '' mcmeta_file; do
        check_json "$mcmeta_file"
        png_file="${mcmeta_file%.mcmeta}"
        if [[ -f "$png_file" ]]; then
          pass "animation pair exists: ${mcmeta_file#$ROOT/}"
        else
          fail "orphan .png.mcmeta: ${mcmeta_file#$ROOT/} (missing ${png_file#$ROOT/})"
        fi
      done < <(find "$ROOT/assets" -type f -name '*.png.mcmeta' -print0 2>/dev/null)
      
      echo ""
      if [[ "$FAILURES" -gt 0 ]]; then
        echo "$FAIL resource-pack validation failed with $FAILURES error(s) and $WARNINGS warning(s)"
        exit 1
      fi
      
      if [[ "$STRICT" -eq 1 && "$WARNINGS" -gt 0 ]]; then
        echo "$FAIL resource-pack validation strict mode failed on $WARNINGS warning(s)"
        exit 1
      fi
      
      echo "$PASS resource-pack validation passed with $WARNINGS warning(s)"
      
  • SKILL.md 12.6 KB
    ---
    name: minecraft-resource-pack
    description: "Create and debug Minecraft 26.x and 1.21.x resource packs, including pack metadata, textures, models, blockstates, item definitions, sounds, fonts, animations, and shaders. Use for client-side visual or audio assets without gameplay code."
    ---
    
    # Minecraft Resource Pack Skill
    
    ## What Is a Resource Pack?
    
    A resource pack is a folder (or `.zip`) that overrides or adds Minecraft's visual and
    audio assets: textures, models, sounds, language files, and fonts. No Java or mod
    loader is required for those vanilla assets.
    
    ### Routing Boundaries
    - `Use when`: the deliverable is visual/audio assets (textures, models, sounds, fonts) in resource-pack format.
    - `Do not use when`: the task requires gameplay logic or runtime behavior changes (use `minecraft-datapack`, `minecraft-plugin-dev`, or `minecraft-modding`).
    - `Do not use when`: the task is server infrastructure/runtime administration (`minecraft-server-admin`).
    
    ---
    
    ## Pack Metadata
    
    | Minecraft Version | Preferred `pack` metadata |
    |-------------------|---------------------------|
    | 1.21 / 1.21.1     | `pack_format: 34` |
    | 1.21.2 / 1.21.3   | `pack_format: 42` |
    | 1.21.4            | `pack_format: 46` |
    | 1.21.5            | `pack_format: 55` |
    | 1.21.6            | `pack_format: 63` |
    | 1.21.7 / 1.21.8   | `pack_format: 64` |
    | 1.21.9 / 1.21.10  | `min_format: [69, 0]`, `max_format: [69, 0]` |
    | 1.21.11           | `min_format: [75, 0]`, `max_format: [75, 0]` |
    | 26.1              | `min_format: [84, 0]`, `max_format: [84, 0]` |
    | 26.2              | `min_format: [88, 0]`, `max_format: [88, 0]` |
    
    Use legacy `pack_format` only for resource formats below 65 (through 1.21.8).
    Resource format 65 and later require both `min_format` and `max_format`; use
    `[major, minor]` for exact patch targeting, including `.0` such as `[84, 0]`.
    An integer or `[major]` `min_format` means `[major, 0]`; an integer or
    `[major]` `max_format` permits every minor version on that major line. Do not
    write decimal JSON numbers. A legacy-only pack can use its integer
    `pack_format` alone. A range whose `min_format` includes a legacy format needs
    integer `pack_format` and `supported_formats`; do not include
    `supported_formats` for a modern-only range.
    
    ---
    
    ## Directory Layout
    
    ```
    my-pack/
    ├── pack.mcmeta
    ├── pack.png                   ← 64×64 icon (optional)
    └── assets/
        └── minecraft/             ← override vanilla (or <namespace>/ for new packs)
            ├── models/
            │   ├── block/
            │   │   └── stone.json
            │   └── item/
            │       └── diamond_sword.json
            ├── items/              ← 1.21.4+ item model definitions
            │   └── diamond_sword.json
            ├── blockstates/
            │   └── stone.json
            ├── textures/
            │   ├── block/
            │   │   └── stone.png
            │   ├── item/
            │   │   └── diamond_sword.png
            │   ├── gui/
            │   │   └── sprites/
            │   │       └── my_sprite.png
            │   └── entity/
            │       └── zombie/
            │           └── zombie.png
            ├── sounds/
            │   └── custom/
            │       └── my_sound.ogg
            ├── sounds.json
            ├── font/
            │   └── default.json
            └── lang/
                └── en_us.json
    ```
    
    Client-mod-specific files such as OptiFine CIT and Iris shader packs use their
    own formats. Read [conditional assets](references/conditional-assets.md) before
    adding them.
    
    ---
    
    ## `pack.mcmeta`
    
    ### 1.21.8 and earlier
    
    ```json
    {
      "pack": {
        "pack_format": 64,
        "description": "My Custom Resource Pack v1.0"
      }
    }
    ```
    
    ### 1.21.9 / 1.21.10
    
    ```json
    {
      "pack": {
        "min_format": [69, 0],
        "max_format": [69, 0],
        "description": "My Custom Resource Pack v1.0"
      }
    }
    ```
    
    ### 1.21.11
    
    ```json
    {
      "pack": {
        "min_format": [75, 0],
        "max_format": [75, 0],
        "description": "My Custom Resource Pack v1.0"
      }
    }
    ```
    
    ### 26.2
    
    ```json
    {
      "pack": {
        "min_format": [88, 0],
        "max_format": [88, 0],
        "description": "My Custom Resource Pack v1.0"
      }
    }
    ```
    
    ---
    
    ## Block Models
    
    ### `assets/minecraft/models/block/my_cube.json`
    Full cube — all six faces use the same texture:
    ```json
    {
      "parent": "minecraft:block/cube_all",
      "textures": {
        "all": "minecraft:block/stone"
      }
    }
    ```
    
    Column block (like logs):
    ```json
    {
      "parent": "minecraft:block/cube_column",
      "textures": {
        "end": "mypack:block/my_pillar_top",
        "side": "mypack:block/my_pillar_side"
      }
    }
    ```
    
    Different sides:
    ```json
    {
      "parent": "minecraft:block/cube",
      "textures": {
        "up":    "mypack:block/my_block_top",
        "down":  "mypack:block/my_block_bottom",
        "north": "mypack:block/my_block_side",
        "south": "mypack:block/my_block_side",
        "east":  "mypack:block/my_block_side",
        "west":  "mypack:block/my_block_side",
        "particle": "mypack:block/my_block_side"
      }
    }
    ```
    
    Cross model (flowers, plants):
    ```json
    {
      "parent": "minecraft:block/cross",
      "textures": {
        "cross": "mypack:block/my_flower"
      }
    }
    ```
    
    ### Custom geometry (elements)
    ```json
    {
      "credit": "Custom model",
      "ambientocclusion": true,
      "textures": {
        "0": "mypack:block/panel",
        "particle": "mypack:block/panel"
      },
      "elements": [
        {
          "from": [0, 0, 7],
          "to": [16, 16, 9],
          "faces": {
            "north": { "texture": "#0", "uv": [0, 0, 16, 16] },
            "south": { "texture": "#0", "uv": [0, 0, 16, 16] }
          }
        }
      ],
      "display": {
        "thirdperson_righthand": {
          "rotation": [75, 45, 0],
          "translation": [0, 2.5, 0],
          "scale": [0.375, 0.375, 0.375]
        }
      }
    }
    ```
    
    > `from` and `to` are in 1/16th block units (0–16). `uv` is `[x1, y1, x2, y2]` in 0–16 units.
    
    ---
    
    ## Item Models
    
    For **1.21.4 and later**, use item definitions in
    `assets/<namespace>/items/`. For **1.21.3 and earlier**, use the legacy model
    `overrides` array. Read [conditional assets](references/conditional-assets.md)
    for the string-based current `custom_model_data` selector and the legacy numeric
    predicate; their values are not interchangeable.
    
    ---
    
    ## Blockstate Definitions
    
    ### Simple block (no variants)
    ```json
    {
      "variants": {
        "": { "model": "mypack:block/my_block" }
      }
    }
    ```
    
    ### Facing block (4 rotations)
    ```json
    {
      "variants": {
        "facing=north": { "model": "mypack:block/my_block" },
        "facing=south": { "model": "mypack:block/my_block",  "y": 180 },
        "facing=east":  { "model": "mypack:block/my_block",  "y": 90  },
        "facing=west":  { "model": "mypack:block/my_block",  "y": 270 }
      }
    }
    ```
    
    ### Random texture (multipart)
    ```json
    {
      "variants": {
        "": [
          { "model": "minecraft:block/grass_block",  "weight": 3 },
          { "model": "minecraft:block/grass_block_2" }
        ]
      }
    }
    ```
    
    ### Multipart (slabs, fences, walls)
    ```json
    {
      "multipart": [
        { "apply": { "model": "mypack:block/my_slab_bottom" }, "when": { "type": "bottom" } },
        { "apply": { "model": "mypack:block/my_slab_top"    }, "when": { "type": "top"    } },
        { "apply": { "model": "mypack:block/my_block"        }, "when": { "type": "double" } }
      ]
    }
    ```
    
    ---
    
    ## Textures
    
    - Format: **PNG**, RGBA (32-bit)
    - Standard block/item size: **16×16 px**
    - Textures can be larger (32×32, 64×64) — Minecraft scales them, but stick to powers of 2
    - Animation requires height = N × width (e.g., 16×64 for 4 frames)
    - Place block textures in `assets/<namespace>/textures/block/`
    - Place item textures in `assets/<namespace>/textures/item/`
    - All textures are referenced without the `.png` extension in JSON
    
    ### Animated texture MCMETA
    `assets/minecraft/textures/block/fire_0.png.mcmeta`:
    ```json
    {
      "animation": {
        "frametime": 2,
        "frames": [0, 1, 2, 3, 4, 5, 6, 7]
      }
    }
    ```
    If `frames` is omitted, all frames play sequentially. `frametime` is in game ticks (default 1).
    
    ### GUI sprites (1.20.2+)
    Place sprites at `assets/minecraft/textures/gui/sprites/<category>/<name>.png`.
    Reference them with `<category>/<name>` in code/JSON.
    
    ### 26.1 block-model texture entries
    For 26.1, a block-model `textures` entry may remain a sprite string or use an
    object. The object must have a string `sprite`; `force_translucent`, when set,
    must be a boolean.
    
    ```json
    {
      "textures": {
        "all": {
          "sprite": "mypack:block/frosted_panel",
          "force_translucent": true
        }
      }
    }
    ```
    
    Both forms identify a sprite without `.png`. Use `force_translucent` only when
    the geometry must render in the translucent pass despite its sprite pixels.
    
    ---
    
    ## Sounds
    
    An event's namespace comes from the namespace containing `sounds.json`; a
    sound entry's `name` identifies the sound-file namespace. Sound files are Vorbis
    `.ogg` under `assets/<namespace>/sounds/`. Read
    [conditional assets](references/conditional-assets.md) for a correct event
    example, aliases, sound-source selection, and replacement behavior.
    
    ---
    
    ## Language Files
    
    `assets/minecraft/lang/en_us.json`:
    ```json
    {
      "block.mypack.my_block": "My Custom Block",
      "item.mypack.my_item": "Magic Wand",
      "entity.mypack.my_mob": "Forest Guardian",
      "death.attack.mypack.laser": "%1$s was zapped by %2$s"
    }
    ```
    
    - Use the exact translation key format for your mod/datapack namespace
    - File name is the locale code (e.g., `fr_fr.json`, `de_de.json`)
    - Always provide `en_us.json` as the primary fallback
    
    ---
    
    ## Fonts
    
    Read [conditional assets](references/conditional-assets.md) for bitmap provider
    layout and private-use icon guidance.
    
    ---
    
    ## OptiFine and shaders
    
    OptiFine CIT and Iris shader packs have client-mod-specific formats. Read
    [conditional assets](references/conditional-assets.md) before adding either;
    they are not portable vanilla resource-pack features.
    
    ---
    
    ## Installation
    
    ```bash
    # Singleplayer: place in
    ~/.minecraft/resourcepacks/my-pack/
    # or
    ~/.minecraft/resourcepacks/my-pack.zip
    
    # Server-side (forces on clients):
    # Set in server.properties:
    resource-pack=https://example.com/my-pack.zip
    resource-pack-sha1=<sha1 hash>
    resource-pack-prompt={"text":"Required pack","color":"gold"}
    ```
    
    ---
    
    ## Common Issues
    
    | Problem | Cause | Fix |
    |---------|-------|-----|
    | Model not showing | Wrong JSON path or syntax error | Check `assets/<namespace>/models/` path; validate JSON |
    | Black/pink checkerboard | Texture path wrong or missing | Check `textures/` path, file extension not in JSON |
    | Blockstate not applying | Wrong state property name | Use F3 to inspect block state; use `/data get block <x> <y> <z>` for block-entity NBT |
    | Animation not working | Wrong MCMETA location | Must be same folder as texture, named `texture.png.mcmeta` |
    | Custom sound not playing | Not in `sounds.json` | Register sound event in `sounds.json`, match namespace |
    | Pack not loading | Wrong `pack_format` or `min_format` / `max_format` values | Update `pack.mcmeta` for the exact 1.21.x patch |
    
    ## Validator Script
    
    Use the bundled validator script before shipping a resource-pack update:
    
    Unbundled references to another namespace's models, textures, sounds, or fonts
    are warnings requiring runtime verification with the dependency present. Missing
    files in the current namespace fail; strict mode also fails unresolved warnings.
    
    ```bash
    # Run from the installed skill directory (for example `.claude/skills/minecraft-resource-pack`):
    ./scripts/validate-resource-pack.sh --root /path/to/resource-pack
    
    # Strict mode treats warnings as failures:
    ./scripts/validate-resource-pack.sh --root /path/to/resource-pack --strict
    ```
    
    What it checks:
    - JSON validity for `pack.mcmeta` and `assets/**/*.json`
    - Legacy versus modern `pack.mcmeta` field shape, including integer, `[major]`, and `[major, minor]` versions
    - Model/blockstate/font/sounds references resolve to real files
    - Current `custom_model_data` select cases use strings; 26.1 texture objects use a string `sprite` and optional boolean `force_translucent`
    - Same-namespace `type: "event"` sound aliases name an event in that `sounds.json`
    - Every `*.png.mcmeta` has a matching `*.png`
    
    The validator cannot resolve a sound event owned by another namespace, or prove
    that a command or mod code invokes an event with the intended sound source.
    Those cases warn and require an exact-client runtime check.
    
    ---
    
    ## References
    
    - [Mojang: Java Edition 1.21.4](https://www.minecraft.net/en-us/article/minecraft-java-edition-1-21-4)
    - [Mojang: Java Edition 1.21.9](https://www.minecraft.net/en-us/article/minecraft-java-edition-1-21-9)
    - [Mojang: Java Edition 26.1](https://www.minecraft.net/en-us/article/minecraft-java-edition-26-1)
    - [NeoForge 1.21.8: Sounds](https://docs.neoforged.net/docs/1.21.8/resources/client/sounds/)
    - Misode's model viewer: https://misode.github.io/
    - OptiFine CIT guide: https://optifine.readthedocs.io/cit.html
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related