# TiedUp! — 3D Item Creation Guide

> Everything you need to create custom bondage items for TiedUp! using Blender.
> No Java required — just a GLB file and a JSON definition.

---

## Table of Contents

1. [How It Works](#how-it-works)
2. [The Skeleton](#the-skeleton)
3. [Body Regions](#body-regions)
4. [Modeling Your Item](#modeling-your-item)
5. [Weight Painting](#weight-painting)
6. [Tint Channels](#tint-channels-dynamic-colors) — dynamic per-zone coloring
7. [Animations](#animations) — item poses, fallback chain, variants, context animations
8. [Animation Templates](#animation-templates)
9. [Exporting from Blender](#exporting-from-blender)
10. [The JSON Definition](#the-json-definition) — field reference, components, pose priority, movement styles
11. [Packaging as a Resource Pack](#packaging-as-a-resource-pack)
12. [Common Mistakes](#common-mistakes)
13. [Examples](#examples)
14. [Furniture Creation](#furniture-creation) — interactive furniture (cross, pillory, cage, etc.)
15. [Quick Reference Cards](#quick-reference-cards) — body items + furniture cheat sheets

---

## How It Works

TiedUp! uses a **2-layer animation system** to render 3D items on players:

```
Layer 1 — CONTEXT (provided by the mod, or by your GLB)
  Controls the player's overall posture: standing, sitting, kneeling, sneaking, walking.
  Affects body + legs. Never touches head (vanilla head tracking is preserved).
  Can be replaced by artist-made GLB files (see Context Animations).

Layer 2 — ITEM (provided by YOUR GLB file)
  Controls the bones your item owns (based on its body regions).
  Can ALSO animate free bones (not owned by any other equipped item).
  Overrides Layer 1 for those bones. Everything else stays vanilla.
```

**What this means for you:**
- You animate the bones your item affects (e.g., arms for handcuffs).
- You can also animate **free bones** — bones no other item claims (e.g., legs if no ankle cuffs are worn). See [Free Bones](#what-to-animate-in-idle).
- The mod handles the rest: legs walk, body leans when sneaking, head follows the mouse.
- Your 3D mesh is skinned to the player skeleton and follows it in real-time.
- You can create **context animation GLBs** — standalone animation packs that replace or extend the mod's default postures (walk cycles, sit poses, etc.). See [Context Animations](#context-animations).

---

## The Skeleton

Your GLB **must** use this exact skeleton. Names are **case-sensitive**.

```
PlayerArmature          ← armature root object (never keyframe this)
  └─ body               ← context layer by default; animate if you own TORSO/WAIST or if free
       ├─ torso         ← maps to same body part as 'body'; prefer using 'body' instead
       │    ├─ head
       │    ├─ leftUpperArm
       │    │    └─ leftLowerArm
       │    └─ rightUpperArm
       │         └─ rightLowerArm
       ├─ leftUpperLeg
       │    └─ leftLowerLeg
       └─ rightUpperLeg
            └─ rightLowerLeg
```

**11 skinned joints** (body through rightLowerLeg). `PlayerArmature` is the armature root object, not a skinned joint — it must never be keyframed.

| Bone | What it controls | Type |
|------|-----------------|------|
| `PlayerArmature` | Root transform | Never animate |
| `body` | Torso position/rotation | Full rotation — context layer by default, animate if owned or free |
| `torso` | Same as `body` (alias) | Full rotation — prefer `body` instead |
| `head` | Head | Full rotation (pitch/yaw/roll) |
| `leftUpperArm` | Left shoulder to elbow | Full rotation |
| `leftLowerArm` | Left elbow to wrist | Bend (angle + direction) |
| `rightUpperArm` | Right shoulder to elbow | Full rotation |
| `rightLowerArm` | Right elbow to wrist | Bend (angle + direction) |
| `leftUpperLeg` | Left hip to knee | Full rotation |
| `leftLowerLeg` | Left knee to ankle | Bend (angle + direction) |
| `rightUpperLeg` | Right hip to knee | Full rotation |
| `rightLowerLeg` | Right knee to ankle | Bend (angle + direction) |

### Upper vs Lower Bones

- **Upper bones** (head, body, upperArm, upperLeg) support full 3-axis rotation (pitch, yaw, roll).
- **Lower bones** (lowerArm, lowerLeg) support **bend angle + bend direction** — the mod extracts both values from your Blender pose. You can pose them freely in any direction; the mod faithfully reproduces both the magnitude and direction of the bend.

### What You Can and Cannot Animate

**Never animate:** `PlayerArmature` — it's the armature root object, not a bone.

**Everything else follows this rule:**
- **Standard animations** (`Idle`, `Struggle`, `Walk`): your item controls ONLY bones in its declared regions. Keyframes on other bones are ignored.
- **`Full` animations** (`FullIdle`, `FullStruggle`, `FullWalk`): your item also controls free bones (body, legs — not owned by another item). See [Full-Body Animations](#full-body-animations-naming-convention).
- **Head is protected by default**: vanilla head tracking is preserved unless your item owns a head region (HEAD, EYES, EARS, MOUTH). In `Full` animations, head stays protected. Use `FullHead` prefix (e.g., `FullHeadStruggle`) to explicitly opt into head animation as a free bone.
- Your item **cannot** override bones owned by another equipped item.

| Bone | Who Controls It |
|------|----------------|
| `body` / `torso` | Context layer by default. Your item if it owns TORSO or WAIST. Also available as a free bone in `Full` animations. |
| `head` | **Vanilla head tracking by default.** Your item if it owns HEAD, EYES, EARS, or MOUTH. Available as a free bone ONLY in `FullHead` animations (e.g., `FullHeadStruggle`). |
| Arms (`*UpperArm`, `*LowerArm`) | Vanilla by default. Your item if it owns ARMS or HANDS. Also available as free bones in `Full` animations. |
| Legs (`*UpperLeg`, `*LowerLeg`) | Context layer by default. Your item if it owns LEGS or FEET. Also available as free bones in `Full` animations. |

**Note:** `torso` and `body` both map to the same internal part. Prefer animating `body` — using `torso` produces the same result but is less intuitive. If your GLB contains **both** `body` and `torso` bones, the runtime will use only the first one encountered in the joint array and emit a WARN in the log: `"Bone 'torso' maps to PlayerAnimator part 'body' already written by an earlier bone — ignoring."` To avoid this, rig with one or the other, never both.

---

## Body Regions

Every item declares which **body regions** it occupies. This determines two things:
1. **Gameplay** — which equipment slots are taken (conflict resolution)
2. **Animation** — which bones your item layer controls

### Region Table

| Region | GLB Bones Controlled | Typical Items |
|--------|---------------------|---------------|
| `HEAD` | `head` | Hood, helmet, head harness |
| `EYES` | `head` | Blindfold |
| `EARS` | `head` | Earplugs |
| `MOUTH` | `head` | Gag, muzzle |
| `NECK` | *(none)* | Collar, choker |
| `TORSO` | `body` (+ `torso`) | Straitjacket, harness |
| `ARMS` | `rightUpperArm`, `rightLowerArm`, `leftUpperArm`, `leftLowerArm` | Handcuffs, armbinder |
| `HANDS` | `rightUpperArm`, `rightLowerArm`, `leftUpperArm`, `leftLowerArm` | Mittens, fist mitts |
| `FINGERS` | *(none)* | Finger cuffs |
| `WAIST` | `body` (+ `torso`) | Belt, chastity belt |
| `LEGS` | `rightUpperLeg`, `rightLowerLeg`, `leftUpperLeg`, `leftLowerLeg` | Ankle cuffs, leg binder |
| `FEET` | `rightUpperLeg`, `rightLowerLeg`, `leftUpperLeg`, `leftLowerLeg` | Forced shoes, foot cuffs |
| `TAIL` | *(none)* | Tail plug (pet play) |
| `WINGS` | *(none)* | Decorative wings |

### Global vs Sub Regions

Three regions are **global** — they encompass sub-regions:

| Global | Sub-regions |
|--------|------------|
| `HEAD` | EYES, EARS, MOUTH |
| `ARMS` | HANDS, FINGERS |
| `LEGS` | FEET |

**Important:** Blocking is **not automatic**. A hood that occupies `HEAD` does not automatically block `EYES`. You must explicitly declare blocked regions in your JSON definition. This gives you full control — a tiara occupies `HEAD` but blocks nothing.

### Regions Without Bones

`NECK`, `FINGERS`, `TAIL`, and `WINGS` don't control any animation bones. Items in these regions are purely cosmetic from an animation standpoint — they render as a mesh on the skeleton but don't change the player's pose. They still participate in gameplay (slot blocking, escape difficulty, etc.).

---

## Modeling Your Item

### Starting Point

Use the **TiedUp! Blender Template** (provided with the mod). It contains:
- The correct `PlayerArmature` skeleton with all 11 bones
- A reference player mesh (Slime model) for scale — toggle visibility as needed
- Pre-named Action slots

### Guidelines

1. **Model in rest pose.** The template skeleton is in the Minecraft rest pose (arms down, legs straight). Model your item to fit the player in this pose.

2. **Keep it tight.** Your mesh will deform with the skeleton. Loose geometry that isn't weight-painted to any bone will stay frozen in space while the player moves.

3. **Mind the polygon count.** Minecraft renders every player in range every frame. A 500-triangle mesh is ideal. 2000 is acceptable. 10,000 is going to cause lag in multiplayer.

4. **Textures are baked into the GLB.** Apply your materials and textures in Blender. The mod reads them directly from the GLB file. No separate texture file needed (unless you want server-side color variants).

5. **Slim model support.** Minecraft has two player models: Steve (4px arms) and Alex (3px arms). If your item wraps tightly around the arms, consider providing a slim variant. Otherwise, the Steve-width mesh works for both (minor clipping on Alex is usually acceptable).

---

## Weight Painting

Weight paint your mesh to the skeleton bones it should follow.

### Rules

- **Paint to the 11 standard bones** for parts that should follow the player's body.
- **You can also use custom bones** for decorative elements like chains, ribbons, pendants, or twist bones. Custom bones follow their parent in the bone hierarchy (rest pose) — they won't animate independently, but they move with the body part they're attached to. This is useful for better weight painting and mesh deformation.
- **Paint to the bones of your regions.** Handcuffs (ARMS region) should be weighted to `rightUpperArm`, `rightLowerArm`, `leftUpperArm`, `leftLowerArm`.
- **You can paint to bones outside your regions** for smooth deformation. For example, handcuffs might have small weights on `body` near the shoulder area for smoother bending. This is fine — the weight painting is about mesh deformation, not animation control.
- **Normalize your weights.** Each vertex's total weights across all bones must sum to 1.0. Blender does this by default.

### Tips

- For rigid items (metal cuffs), use hard weights — each vertex fully assigned to one bone.
- For flexible items (rope, leather), blend weights between adjacent bones for smooth bending.
- The chain between handcuffs? Weight it 50/50 to both arms, or use a separate mesh element weighted to `body`.
- Custom bones are great for chains, dangling locks, or decorative straps — add a bone parented to a standard bone, weight your mesh to it, and it'll follow the parent's movement automatically.

---

## Tint Channels (Dynamic Colors)

Tint channels let players change the color of specific parts of your item without replacing the texture. A ball gag with a red ball can become blue, green, purple — while the black strap stays black.

### How It Works

Your item's texture detail (highlights, shadows, stitching, scratches) is fully preserved. The mod multiplies the texture color by a **tint color** per-zone. Grayscale textures + color tint = any hue with full detail.

### Blender Workflow

Use **separate materials** for fixed and colorable zones:

1. **Fixed zones** — Name the material anything (e.g., `strap`, `buckle`, `metal`). These render as-is.
2. **Colorable zones** — Name the material `tintable_1`, `tintable_2`, etc. These get tinted by the mod.

**Example: Ball Gag**
- Select the strap faces → assign material `strap` → texture with leather detail (full color)
- Select the ball faces → assign material `tintable_1` → texture with **grayscale** surface detail (highlights, reflections, micro-scratches)
- Export GLB

The grayscale texture on `tintable_1` preserves all the surface detail. The mod multiplies it by the tint color: gray detail × red = detailed red ball.

### Multiple Tint Channels

You can have as many independently-colorable zones as you want:

```
Material "strap"       → fixed (leather, never changes)
Material "tintable_1"  → ball (player picks color)
Material "tintable_2"  → ring (player picks a different color)
Material "buckle"      → fixed (chrome metal)
```

Each `tintable_N` channel gets its own color. Players can customize each zone independently.

### Texturing Tips for Tintable Zones

- **Use grayscale textures** for tintable zones. The tint color replaces the hue entirely.
- Keep all the detail (bumps, scratches, reflections) in the luminance channel.
- A mid-gray base (not pure white) produces more natural-looking results.
- Dark areas in your grayscale texture stay dark regardless of tint — good for crevices and shadows.

**What happens:** `finalPixelColor = textureGray × tintColor`
- Gray (128,128,128) × Red (255,0,0) = Dark Red (128,0,0)
- White (255,255,255) × Red (255,0,0) = Bright Red (255,0,0)
- Black (0,0,0) × Red (255,0,0) = Black (0,0,0) — shadows stay dark

### JSON Definition

Declare default colors per channel in your item JSON:

```json
{
  "type": "tiedup:bondage_item",
  "display_name": "Ball Gag",
  "model": "mycreator:models/gltf/ball_gag.glb",
  "regions": ["MOUTH"],
  "supports_color": true,
  "tint_channels": {
    "tintable_1": "#FF0000",
    "tintable_2": "#C0C0C0"
  },
  "pose_priority": 10,
  "escape_difficulty": 3
}
```

Colors are hex RGB (`#RRGGBB`). These are the default colors — players can override them in-game via dyeing.

### Material Naming Rules

| Name Pattern | Behavior | Example |
|-------------|----------|---------|
| `tintable_1`, `tintable_2`, ... | Colorable zone, numbered | Ball, ring, strap |
| Anything else | Fixed, rendered as-is | `metal`, `buckle`, `leather` |

- Names are **case-sensitive** and must start with `tintable_` (lowercase).
- Numbers don't need to be sequential — `tintable_1` and `tintable_5` is fine.
- The number is part of the channel name: `tintable_1` in JSON matches `tintable_1` in Blender.

### Items Without Tint Channels

If your item has no `tintable_` materials, everything renders as-is. No `tint_channels` field needed in the JSON. Existing items are completely unaffected.

---

## Animations

Animations define how the player's body is posed when wearing your item.

### The Only Required Animation: `Idle`

Every item needs at least one animation named `Idle`. This is the default pose — what the player looks like while standing still with your item equipped.

**In Blender:** Create an Action named `PlayerArmature|Idle`.

The `PlayerArmature|` prefix is Blender's convention for armature-scoped actions. The mod strips it automatically — the resolved name is just `Idle`.

### What to Animate in Idle

**Only keyframe the bones your item affects.** Leave everything else untouched.

| Item Type | Bones to Keyframe | Example Pose |
|-----------|------------------|--------------|
| Handcuffs (ARMS) | Upper + lower arms | Arms behind back, wrists together |
| Ankle cuffs (LEGS) | Upper + lower legs | Legs closer together, slightly bent |
| Blindfold (EYES) | Head | Slight head tilt down |
| Gag (MOUTH) | *(none)* | No pose change — mesh only |
| Straitjacket (ARMS+TORSO) | Arms + body (+ legs if free) | Arms crossed, slight forward lean, optional waddle |

**Why only your bones?** In standard animations (`Idle`, `Struggle`), the mod only uses keyframes for bones in your declared regions. Keyframes on other bones are ignored — safe to leave them in your GLB.

To animate free bones (body, legs not owned by another item), use the `Full` prefix — see [Full-Body Animations](#full-body-animations-naming-convention). For example, a straitjacket's `FullWalk` can animate legs for a waddle, but only if no ankle cuffs are equipped.

**The rule:** Standard animations = owned bones only. `Full` animations = owned + free bones. `FullHead` animations = owned + free + head. Your item can never override another item's bones.

### Animation Frames

**Frame 0 is the base pose** — the minimum every animation must have. The mod always has a valid pose to display from frame 0.

**Multi-frame animations play fully** — struggle thrashing, walk cycles, breathing idles all animate at runtime. The converter iterates every keyframe and emits them at the correct MC tick.

#### Authoring at 20 FPS (strongly recommended)

Minecraft ticks at **20 Hz**. Authoring at 20 FPS gives you a 1:1 mapping — every source frame becomes one MC tick.

If you author at a higher rate (24 / 30 / 60 FPS — Blender defaults), the converter quantizes timestamps to MC ticks via rounding. Multiple source frames that round to the same tick are **deduplicated** — only the first is kept, the rest are skipped. Practical impact:

| Source FPS | Frames kept per second | Lost per second |
|---|---|---|
| 20 | 20 | 0 |
| 24 | ~20 | ~4 |
| 30 | 20 | ~10 |
| 60 | 20 | ~40 |

For smooth motion at any rate, set Blender's scene FPS to 20 and author accordingly. If you must author at 24+, put critical keyframes on integer multiples of `1/20s = 50ms` to ensure they land on unique ticks.

#### Timeline start

The converter **normalizes the timeline** so the first keyframe plays at tick 0, even if your Blender action's first keyframe is at a non-zero time (NLA strips, trimmed clips). You don't need to pre-shift your timelines.

#### What the converter reads

- **Rotations** per joint (full multi-frame). **This is the primary driver.**
- **Translations** are parsed but not yet applied to the player animation — use rotations for all motion. (Bone translations are used for the furniture seat skeleton anchor, not the player pose.)
- **Ease**: linear interpolation between keyframes. Blender's default F-Curve interpolation (Bezier) is sampled by the exporter at the authored framerate — if you need smooth motion, add keyframes at the sample rate, don't rely on curve-side smoothing.

### Optional Animations

Beyond `Idle`, you can provide animations for specific contexts. All are optional — if missing, the mod falls back through a chain (see below).

| Animation Name | Context | Notes |
|----------------|---------|-------|
| `Idle` | Standing still | **Required.** Single-frame or looped. Frame 0 = base pose. |
| `Struggle` | Player is struggling | Multi-frame loop recommended. 20-40 frames. |
| `Walk` | Player is walking | Multi-frame loop synced to walk speed. |
| `Sneak` | Player is sneaking | Single-frame or short loop. |
| `SitIdle` | Sitting (chair, minecart) | Single-frame or looped. |
| `SitStruggle` | Sitting + struggling | Multi-frame loop. |
| `KneelIdle` | Kneeling | Single-frame or looped. |
| `KneelStruggle` | Kneeling + struggling | Multi-frame loop. |
| `Crawl` | Crawling (dog pose) | Multi-frame loop. |

**Naming in Blender:** Always prefix with `PlayerArmature|`. Examples:
- `PlayerArmature|Idle`
- `PlayerArmature|Struggle`
- `PlayerArmature|SitIdle`

### Fallback Chain

If an animation doesn't exist in your GLB, the mod looks for alternatives. At each step, `Full` variants are tried first:

```
SIT + STRUGGLE:
  FullHeadSitStruggle → FullSitStruggle → SitStruggle
    → FullHeadStruggle → FullStruggle → Struggle
    → FullHeadSit → FullSit → Sit
    → FullHeadIdle → FullIdle → Idle

KNEEL + STRUGGLE:
  FullHeadKneelStruggle → FullKneelStruggle → KneelStruggle
    → FullHeadStruggle → FullStruggle → Struggle
    → FullHeadKneel → FullKneel → Kneel
    → FullHeadIdle → FullIdle → Idle

SIT + IDLE:      FullHeadSitIdle   → FullSitIdle   → SitIdle   → ... → FullHeadIdle → FullIdle → Idle
KNEEL + IDLE:    FullHeadKneelIdle → FullKneelIdle → KneelIdle → ... → FullHeadIdle → FullIdle → Idle
SNEAK:           FullHeadSneak     → FullSneak     → Sneak     → FullHeadIdle → FullIdle → Idle
WALK:            FullHeadWalk      → FullWalk      → Walk      → FullHeadIdle → FullIdle → Idle
STAND STRUGGLE:  FullHeadStruggle  → FullStruggle  → Struggle  → FullHeadIdle → FullIdle → Idle
STAND IDLE:      FullHeadIdle      → FullIdle      → Idle
```

At each step, `FullHead` is tried first (full body + head), then `Full` (full body, head preserved), then standard (owned bones only).

In practice, most items only need `Idle`. Add `FullWalk` or `FullStruggle` when your item changes how the whole body moves.

**Practical impact:** If you only provide `Idle`, your item works in every context. The player will hold the Idle pose while sitting, kneeling, sneaking, etc. It won't look perfect, but it will work. Add more animations over time to polish the experience.

### Animation Variants (Random Selection)

For variety, you can provide multiple versions of the same animation. The mod picks one **at random** each time the animation triggers.

**Convention:** Append `.1`, `.2`, `.3`, etc.

```
PlayerArmature|Struggle.1    ← variant 1
PlayerArmature|Struggle.2    ← variant 2
PlayerArmature|Struggle.3    ← variant 3
```

Works for any animation name: `Idle.1`/`Idle.2`, `SitIdle.1`/`SitIdle.2`, etc.

**Rules:**
- Number sequentially starting from `.1`. The mod stops scanning at the first gap **after `.1`** (missing `.1` does not stop the scan — it continues checking `.2`, `.3`, etc.).
- If only one version exists, don't number it — just use `Struggle`, not `Struggle.1`.
- The base name (e.g., `Struggle`) **is included** in the random pool alongside numbered variants if it exists. So `Struggle` + `Struggle.1` + `Struggle.2` = three candidates in the pool.

### Full-Body Animations (Naming Convention)

Some items affect the entire body — not just their declared regions. A straitjacket makes the player waddle, a full-body bind forces hopping. For these, you want to animate **all bones**, including free ones like legs and body.

**Convention:** Prefix the animation name with `Full`.

| Standard Name | Full-Body Name | Full + Head | What Changes |
|--------------|---------------|-------------|-------------|
| `Idle` | `FullIdle` | `FullHeadIdle` | Owned bones + body lean, leg stance (+ head if Head variant) |
| `Walk` | `FullWalk` | `FullHeadWalk` | Owned bones + leg waddle, body sway (+ head if Head variant) |
| `Struggle` | `FullStruggle` | `FullHeadStruggle` | Owned bones + full-body thrashing (+ head if Head variant) |
| `Sneak` | `FullSneak` | `FullHeadSneak` | Owned bones + custom sneak posture (+ head if Head variant) |

**In Blender:**
```
PlayerArmature|Idle              ← region-only: just arms for handcuffs
PlayerArmature|FullWalk          ← full-body: arms + legs waddle + body bob
PlayerArmature|FullStruggle      ← full-body: body + legs thrash (head free)
PlayerArmature|FullHeadStruggle  ← full-body + head: everything moves including head
```

**How the mod resolves this:**
1. Checks for `Full` variant first (e.g., `FullWalk`)
2. Falls back to standard name (e.g., `Walk`)
3. Follows the normal fallback chain (`Walk` → `Idle`)

**When to use `Full` vs standard:**

| Animation | Use Standard | Use `Full` |
|-----------|:------------:|:----------:|
| `Idle` | Your item only poses its own bones | Your item changes the whole resting posture |
| `Walk` | Legs should use vanilla/context walk | Your item needs a custom walk cycle (waddle, hop, shuffle) |
| `Struggle` | Only owned bones move | Whole body thrashes and writhes |
| `Sneak` | Default sneak lean is fine | Your item changes how sneaking looks |

**Key points:**
- **Standard animations** (`Idle`, `Struggle`, `Walk`) only animate your item's **owned bones** (from `regions`). Any keyframes on other bones are ignored. This is safe — you can keyframe everything in Blender without worrying about side effects.
- **`Full` animations** (`FullIdle`, `FullStruggle`, `FullWalk`) additionally enable **free bones** (body, legs — bones not owned by any other equipped item). This is how you create waddle walks, full-body struggles, etc.
- **Head is protected by default.** In `Full` animations, the `head` bone is NOT enabled as a free bone — vanilla head tracking (mouse look) is preserved. To animate the head in a Full animation, add `Head` to the animation name: `FullHeadStruggle`, `FullHeadIdle`, etc. Items that own a head region (HEAD, EYES, EARS, MOUTH) always control head regardless of naming.
- Free bones in `Full` animations are only used when no other item owns them.
- You can provide BOTH: `Idle` (region-only) and `FullWalk` (full-body). The mod picks the right one per context.
- `FullIdle` is rarely needed — most items only need a full-body version for movement animations.

### Context Animations

Context animations are **separate from item animations**. They control the player's **overall body posture** — how they walk, sit, sneak, kneel, etc. They are the foundation that item animations build on top of.

#### The Two-Layer System

```
Layer 1 — CONTEXT (body posture)       ← walk cycle, sit pose, sneak lean...
  Controls: body, legs (and any unowned bones)
  Bones owned by items are disabled on this layer.

Layer 2 — ITEM (your item's pose)      ← arms behind back, legs bound...
  Controls: only the bones in the item's declared regions.
  Overrides Layer 1 for those bones.
```

The mod ships with default context animations (currently as internal JSON files). These provide basic postures: a static standing pose, a simple walk, a sneak lean, sitting/kneeling positions.

#### Artists Can Replace or Extend Context Animations

This is where it gets interesting. **You can create GLB files that replace or add to the mod's context animations.** These are standalone GLBs — they are NOT tied to any specific item.

Use cases:
- **Replace the default walk cycle** with a smoother, more natural one
- **Add a new "hogtied" context** the mod doesn't ship with
- **Create an animation overhaul pack** that improves every default posture
- **Provide themed context packs** (e.g., "petplay contexts" with crawl animations)

#### Context GLB Format

A context GLB uses the same `PlayerArmature` skeleton as item GLBs. The difference is what it contains:

- **Item GLB:** mesh + animations for a specific item's bones
- **Context GLB:** no mesh — animations only, for **all bones** (body, legs, etc.)

The **filename** determines which context the GLB replaces. It must match one of the context suffixes:

| Filename | Context It Replaces |
|----------|-------------------|
| `stand_idle.glb` | Standing still |
| `stand_walk.glb` | Walking |
| `stand_sneak.glb` | Sneaking |
| `stand_struggle.glb` | Standing + struggling |
| `sit_idle.glb` | Sitting |
| `sit_struggle.glb` | Sitting + struggling |
| `kneel_idle.glb` | Kneeling |
| `kneel_struggle.glb` | Kneeling + struggling |
| `shuffle_idle.glb` | Shuffle style — standing still (legs close together) |
| `shuffle_walk.glb` | Shuffle style — tiny dragging steps |
| `hop_idle.glb` | Hop style — standing with feet bound together |
| `hop_walk.glb` | Hop style — small bunny hops |
| `waddle_idle.glb` | Waddle style — standing with slight sway |
| `waddle_walk.glb` | Waddle style — side-to-side waddling gait |
| `crawl_idle.glb` | Crawl style — on all fours, resting (petplay/dogwalk pose) |
| `crawl_move.glb` | Crawl style — on all fours, crawling forward |

Names are **exact and case-sensitive**. The mod strips the `.glb` extension and looks up the suffix.

You don't need to provide all of them. Missing contexts fall back to the mod's builtin defaults. The GLB itself should contain at least one animation clip — the mod uses the first clip found.

#### What to Animate in Context GLBs

Context animations should animate **body and legs** — the "posture" bones. Do NOT animate bones that items will control (arms, head), as your context keyframes would be overridden by any equipped item anyway.

| Bone | Recommended? | Why |
|------|:-----------:|-----|
| `body` | Yes | Lean, sway, bob |
| `leftUpperLeg` / `rightUpperLeg` | Yes | Walk stride, sit angle |
| `leftLowerLeg` / `rightLowerLeg` | Yes | Knee bend |
| `head` | Usually no | Overridden by any item owning HEAD/EYES/EARS/MOUTH, and by vanilla head tracking |
| `leftUpperArm` / `rightUpperArm` | Usually no | Overridden by any item owning ARMS/HANDS. Only useful as a fallback when arms are free |
| `leftLowerArm` / `rightLowerArm` | Usually no | Same as upper arms |
| `torso` | Rarely | Same effect as `body`; prefer `body` |

#### Example: Smooth Walk Cycle

The mod's default walk is basic. You want a nicer one:

**In Blender:**
1. Use the TiedUp! template (same skeleton)
2. Delete the reference player mesh — context GLBs have no mesh
3. Create `PlayerArmature|Walk`: a 20-frame walk cycle animating `body` (bob), `leftUpperLeg`/`rightUpperLeg` (stride), `leftLowerLeg`/`rightLowerLeg` (knee bend)
4. Export as GLB (armature only, no mesh)

Place it in a resource pack:
```
assets/mycreator/tiedup_contexts/stand_walk.glb
```

The filename `stand_walk.glb` matches the `stand_walk` context suffix. The mod discovers it at startup (or on F3+T reload) and uses your walk cycle instead of the builtin one. Players wearing handcuffs will have your smooth walk cycle on their legs/body, with arms still locked behind their back by the item.

#### Example: Custom Kneeling Context

The mod ships a basic kneel. You want a more expressive one with the body leaned slightly forward and legs tucked tighter:

**In Blender:**
1. Create `PlayerArmature|KneelIdle`: single-frame pose with body pitched forward 15°, legs folded tight
2. Create `PlayerArmature|KneelStruggle`: multi-frame animation with body rocking side to side while kneeling
3. Export as GLB

#### Replacing vs Extending

- **Replace**: Provide a context animation with the same name as a default. Your animation is used instead.
- **Extend**: Provide a context animation for a situation the mod doesn't cover yet (e.g., a "Hogtied" context). Items can then reference this new context.

#### Context Packs

Since context GLBs are loaded from resource packs, the community can create **context animation packs** — collections of improved or themed postures. Players install them like any resource pack. No code changes, no item modifications.

```
BetterAnimations Pack/
  assets/betteranims/tiedup_contexts/
    stand_walk.glb        ← replaces default walk
    stand_sneak.glb       ← replaces default sneak
    sit_idle.glb          ← replaces default sit
    kneel_idle.glb        ← replaces default kneel
    kneel_struggle.glb    ← replaces default kneel struggle
    crawl_idle.glb        ← replaces default crawl idle
    crawl_move.glb        ← replaces default crawl movement
```

#### Movement Style Contexts

The mod includes 8 movement style contexts used when a player wears leg-restraining items with a `movement_style` field. These control how the player animates while moving in a restricted way.

**The crawl style should animate the player on all fours in a petplay/dogwalk pose** — NOT a belly-down swimming pose. Think of a pet being walked on a leash, with knees and hands on the ground, body tilted forward. The vanilla SWIMMING hitbox is used for the 0.6-block height, but the animation completely overrides the vanilla swimming visuals.

| Style | Idle | Walk/Move | Visual |
|-------|------|-----------|--------|
| **Shuffle** | Legs close together, slight sway | Tiny dragging steps, short stride | Feet hobbled with a short chain |
| **Hop** | Feet together, standing | Small bunny hops, feet never separate | Feet bound together, forced to hop |
| **Waddle** | Slight lateral sway | Body rocks left-right, wide stance | Legs hobbled mid-thigh |
| **Crawl** | On all fours, resting | On all fours, crawling forward | Petplay/dogwalk, hands and knees |

These are excellent candidates for artist GLB replacements — the default animations are basic placeholders. A well-made walk cycle for `shuffle_walk.glb` or `crawl_move.glb` will dramatically improve the feel of the restraint system.

---

## Animation Templates

Not every item needs custom animations. Many items in the same category share identical poses — all handcuffs put arms behind the back, all ankle cuffs restrict leg movement.

TiedUp! provides **animation template GLBs** with pre-made animations:

```
assets/tiedup/models/gltf/templates/
  handcuff_anims.glb       ← Idle, Struggle, SitIdle for ARMS items
  leg_restraint_anims.glb  ← Idle, Walk, SitIdle for LEGS items
  full_body_anims.glb      ← Idle, Walk, Struggle, SitIdle for full-body items
```

### How to Use a Template

In your JSON definition, separate the mesh from the animations:

```json
{
  "model": "mycreator:models/gltf/my_fancy_cuffs.glb",
  "animation_source": "tiedup:models/gltf/templates/handcuff_anims.glb"
}
```

- `model` — your GLB with the **3D mesh** (no animations needed)
- `animation_source` — the template GLB with **animations**
- If `animation_source` is omitted, animations come from `model`

**Artist workflow:** Model your item, weight-paint it, skip all animation work, reference a template. Done.

### When NOT to Use a Template

- Your item has a unique silhouette that would clip with template poses
- You want a distinctive struggle animation
- Your item affects bones differently (e.g., arms in front vs behind back)
- You want to ship a premium, polished item

---

## Exporting from Blender

### Export Settings

**Set your Blender scene FPS to 20** (Scene Properties > Frame Rate > Custom = 20) before authoring animations. Minecraft ticks at 20 Hz; any source frame rate above 20 FPS will have frames silently deduplicated at load. See [Animation Frames](#animation-frames).

**File > Export > glTF 2.0 (.glb)**

| Setting | Value | Why |
|---------|-------|-----|
| Format | `glb` (binary) | Single file, faster loading |
| Include > Limit to | Selected Objects | Export only armature + item mesh |
| Transform > +Y Up | Checked | glTF standard |
| Mesh > Apply Modifiers | Checked | Bake subdivision, mirror, etc. |
| Mesh > Normals | Checked | Needed for lighting |
| Mesh > Vertex Colors | Checked (if used) | For tintable items |
| Animation > Export Actions | Checked | Include all named actions |
| Animation > Group by NLA Track | Unchecked | Avoids duplicate animations |
| Animation > Sampling Rate | 1 | One sample per frame |

### Pre-Export Checklist

- [ ] Scene FPS set to **20** (Blender default is 24 — change it)
- [ ] Armature is named `PlayerArmature`
- [ ] All 11 bones have correct names (case-sensitive)
- [ ] Actions are named `PlayerArmature|Idle`, `PlayerArmature|Struggle`, etc.
- [ ] Mesh is weight-painted to skeleton bones only
- [ ] Weights are normalized (the mod re-normalizes at load as a safety net, but authoring-normalized weights give the most predictable result)
- [ ] Custom bones (if any) are parented to a standard bone in the hierarchy
- [ ] Your item mesh is named `Item` in Blender (recommended — ensures the mod picks the correct mesh if your file has multiple objects)
- [ ] Materials/textures are applied (the GLB bakes them in)
- [ ] Scale is correct (1 Blender unit = 1 Minecraft block = 16 pixels)

---

## The JSON Definition

Every item needs a JSON file that declares its gameplay properties. The mod scans `assets/<namespace>/tiedup_items/*.json` at startup and on resource reload (F3+T).

### Minimal Example — Rope Gag

```json
{
  "type": "tiedup:bondage_item",
  "display_name": "Rope Gag",
  "model": "mycreator:models/gltf/rope_gag.glb",
  "regions": ["MOUTH"],
  "pose_priority": 10,
  "escape_difficulty": 2,
  "lockable": false
}
```

### Standard Example — Iron Handcuffs

```json
{
  "type": "tiedup:bondage_item",
  "display_name": "Iron Handcuffs",
  "model": "mycreator:models/gltf/iron_cuffs.glb",
  "slim_model": "mycreator:models/gltf/iron_cuffs_slim.glb",
  "animation_source": "tiedup:models/gltf/templates/handcuff_anims.glb",
  "regions": ["ARMS"],
  "animation_bones": {
    "idle": ["rightArm", "leftArm"],
    "struggle": ["rightArm", "leftArm"]
  },
  "pose_priority": 30,
  "escape_difficulty": 5,
  "lockable": true
}
```

### Complex Example — Straitjacket

```json
{
  "type": "tiedup:bondage_item",
  "display_name": "Leather Straitjacket",
  "model": "mycreator:models/gltf/straitjacket.glb",
  "slim_model": "mycreator:models/gltf/straitjacket_slim.glb",
  "regions": ["ARMS", "HANDS", "TORSO"],
  "blocked_regions": ["ARMS", "HANDS", "TORSO", "FINGERS"],
  "animation_bones": {
    "idle": ["rightArm", "leftArm", "body"],
    "struggle": ["rightArm", "leftArm", "body"]
  },
  "pose_priority": 50,
  "escape_difficulty": 7,
  "lockable": true
}
```

### Complex Example — Ankle Chains (with movement style)

```json
{
  "type": "tiedup:bondage_item",
  "display_name": "Ankle Chains",
  "model": "mycreator:models/gltf/ankle_chains.glb",
  "regions": ["FEET"],
  "animation_bones": {
    "idle": ["rightLeg", "leftLeg"],
    "walk": ["rightLeg", "leftLeg"]
  },
  "pose_priority": 30,
  "escape_difficulty": 5,
  "lockable": true,
  "movement_style": "shuffle"
}
```

The `movement_style` changes how the player physically moves — slower speed, different walking animation, and potentially disabled jumping. See [Movement Styles](#movement-styles) below.

### Complex Example — Hogtie (crawl style)

```json
{
  "type": "tiedup:bondage_item",
  "display_name": "Hogtie Harness",
  "model": "mycreator:models/gltf/hogtie.glb",
  "regions": ["ARMS", "HANDS", "LEGS", "FEET"],
  "blocked_regions": ["ARMS", "HANDS", "LEGS", "FEET", "FINGERS", "WAIST"],
  "animation_bones": {
    "idle": ["rightArm", "leftArm", "rightLeg", "leftLeg", "body"],
    "struggle": ["rightArm", "leftArm", "rightLeg", "leftLeg", "body"]
  },
  "pose_priority": 90,
  "escape_difficulty": 9,
  "lockable": true,
  "movement_style": "crawl",
  "movement_modifier": {
    "speed_multiplier": 0.15
  }
}
```

### Field Reference

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `type` | string | Yes | Always `"tiedup:bondage_item"` |
| `display_name` | string | Yes | Name shown in-game |
| `model` | string | Yes | ResourceLocation of the GLB mesh |
| `slim_model` | string | No | GLB for Alex-model players (3px arms) |
| `animation_source` | string | No | GLB to read animations from (defaults to `model`) |
| `regions` | string[] | Yes | Body regions this item occupies |
| `blocked_regions` | string[] | No | Regions blocked for other items (defaults to `regions`) |
| `pose_priority` | int | Yes | Higher = overrides lower-priority items (see below) |
| `escape_difficulty` | int | Yes | 1-10 scale. Higher = harder to struggle free |
| `lockable` | bool | No | Can a padlock be applied? Default: `true` |
| `supports_color` | bool | No | Whether this item has tintable zones. Default: `false` |
| `tint_channels` | object | No | Default colors per tintable zone: `{"tintable_1": "#FF0000"}` |
| `icon` | string | No | Inventory sprite model (see [Inventory Icons](#inventory-icons) below) |
| `movement_style` | string | No | Movement restriction: `"waddle"`, `"shuffle"`, `"hop"`, or `"crawl"` |
| `movement_modifier` | object | No | Override speed/jump for the movement style (requires `movement_style`) |
| `creator` | string | No | Author/creator name, shown in the item tooltip |
| `animation_bones` | object | No | Per-animation bone whitelist (see below). If omitted, all owned bones are enabled for all animations. |
| `components` | object | No | Gameplay behavior components (see [Components](#components-gameplay-behaviors) below) |

### animation_bones (optional)

Fine-grained control over which bones each animation is allowed to affect. Most items don't need this — if omitted, all owned bones (from your `regions`) are enabled for all animations automatically.

**When to use it:** When your item owns bones via its regions but you only want specific animations to affect specific bones. For example, an item owning ARMS + TORSO might only want the "idle" pose to affect the arms, and only the "struggle" animation to also move the body.

**Format:** A JSON object where each key is an animation name and each value is an array of bone names.

**Valid bone names:** `head`, `body`, `rightArm`, `leftArm`, `rightLeg`, `leftLeg`

**Example:**
```json
"animation_bones": {
  "idle": ["rightArm", "leftArm"],
  "struggle": ["rightArm", "leftArm", "body"]
}
```

At runtime, the effective bones for a given animation clip are computed as the **intersection** of `animation_bones[clipName]` and the item's owned parts (from region conflict resolution). If the clip name is not listed in `animation_bones`, the item falls back to using all its owned parts.

**If omitted entirely:** All owned bones are enabled for all animations. This is the correct default for most items — you only need `animation_bones` for advanced per-animation filtering.

### Components (Gameplay Behaviors)

Components add gameplay behaviors to your item without requiring Java code. Each component is a self-contained module you declare in the `"components"` block of your JSON definition.

**Format:** A JSON object where each key is a component name and each value is the component's configuration (an object, or `true` for defaults).

```json
"components": {
  "lockable": { "lock_resistance": 200 },
  "resistance": { "base": 150 },
  "gagging": { "comprehension": 0.2, "range": 10.0 }
}
```

Items without the `"components"` field work normally — components are entirely optional.

#### Available Components

| Component | Description | Config Fields |
|-----------|-------------|---------------|
| `lockable` | Item can be locked with a padlock. Locked items cannot be unequipped. | `lock_resistance` (int, default: 250) — resistance added by the lock for struggle mechanics |
| `resistance` | Struggle resistance. Higher = harder to escape. | `base` (int, default: 100) — base resistance value |
| `gagging` | Muffles the wearer's speech. | `comprehension` (0.0–1.0, default: 0.2) — how much speech is understandable. `range` (float, default: 10.0) — max hearing distance in blocks |
| `blinding` | Applies a blindfold overlay to the wearer's screen. | `overlay` (string, optional) — custom overlay texture path. Omit for default |
| `shock` | Item can shock the wearer (manually or automatically). | `damage` (float, default: 2.0) — damage per shock. `auto_interval` (int, default: 0) — ticks between auto-shocks (0 = manual only) |
| `gps` | GPS tracking and safe zone enforcement. | `safe_zone_radius` (int, default: 50) — safe zone in blocks (0 = tracking only). `public_tracking` (bool, default: false) — anyone can track, not just owner |
| `choking` | Drains air, applies darkness/slowness, deals damage when activated. | `air_drain_per_tick` (int, default: 8) — air drained per tick. `non_lethal_for_master` (bool, default: true) — won't kill if worn by a master's pet |
| `adjustable` | Allows Y-offset adjustment via GUI slider. | `default` (float, default: 0.0), `min` (float, default: -4.0), `max` (float, default: 4.0), `step` (float, default: 0.25) — all in pixels (1px = 1/16 block) |

#### Example: Shock Collar with GPS

```json
{
  "type": "tiedup:bondage_item",
  "display_name": "GPS Shock Collar",
  "model": "mycreator:models/gltf/gps_shock_collar.glb",
  "regions": ["NECK"],
  "pose_priority": 10,
  "escape_difficulty": 5,
  "components": {
    "lockable": { "lock_resistance": 300 },
    "resistance": { "base": 150 },
    "shock": { "damage": 3.0, "auto_interval": 200 },
    "gps": { "safe_zone_radius": 50 }
  }
}
```

This collar can be locked (300 resistance to break the lock), has 150 base struggle resistance, shocks every 200 ticks (10 seconds) automatically, and enforces a 50-block safe zone.

#### Example: Adjustable Blindfold

```json
{
  "type": "tiedup:bondage_item",
  "display_name": "Leather Blindfold",
  "model": "mycreator:models/gltf/blindfold.glb",
  "regions": ["EYES"],
  "animation_bones": {
    "idle": ["head"]
  },
  "pose_priority": 10,
  "escape_difficulty": 2,
  "components": {
    "blinding": {},
    "resistance": { "base": 80 },
    "adjustable": { "min": -2.0, "max": 2.0, "step": 0.5 }
  }
}
```

#### Component Tips

- **You can combine any components.** A gag with `gagging` + `lockable` + `resistance` + `adjustable` is perfectly valid.
- **Omit components you don't need.** A decorative collar with no shock/GPS just omits those components entirely.
- **Default values are sensible.** `"lockable": {}` gives you standard lock behavior with default resistance. You only need to specify fields you want to customize.
- **Components don't affect rendering.** They are purely gameplay — your GLB model and animations are independent of which components you use.

### Pose Priority

When multiple items affect the same bones, the highest `pose_priority` wins.

| Priority Range | Item Type | Example |
|---------------|-----------|---------|
| 1–10 | Light cosmetic | Collar, blindfold, gag |
| 20–40 | Standard restraint | Handcuffs, ankle cuffs |
| 40–60 | Heavy restraint | Armbinder, straitjacket |
| 60–80 | Full-body restraint | Leg binder + armbinder combo |
| 80–100 | Total restraint | Sleep sack, full bind |

### Blocked Regions

`blocked_regions` prevents other items from being equipped on those regions **while your item is worn**.

**Examples:**
- Hood → `regions: ["HEAD"]`, `blocked_regions: ["HEAD", "EYES", "EARS"]` — covers head, eyes, ears. Mouth is still accessible (for a gag underneath).
- Handcuffs → `regions: ["ARMS"]`, no `blocked_regions` needed — only blocks ARMS by default.
- Straitjacket → `regions: ["ARMS", "HANDS", "TORSO"]`, `blocked_regions: ["ARMS", "HANDS", "TORSO", "FINGERS"]` — also blocks finger accessories.

If `blocked_regions` is omitted, it defaults to the same as `regions`.

### Movement Styles

Items that restrict the player's legs can declare a `movement_style` to change how the player physically moves. This affects both server-side movement (speed, jumping) and client-side animation.

**Available styles:**

| Style | Speed | Jump | Animation | Typical Use |
|-------|-------|------|-----------|-------------|
| `waddle` | 0.6× | allowed | Side-to-side sway | Hobble skirt, thigh cuffs |
| `shuffle` | 0.4× | disabled | Tiny dragging steps | Short ankle chain, leg binder |
| `hop` | 0.35× (between hops) | auto-hop | Small bunny hops | Bound feet (rope, tape) |
| `crawl` | 0.2× | disabled | On all fours (petplay) | Hogtie, pet harness |

**Resolution:** If multiple equipped items have different styles, the most constraining one wins (crawl > hop > shuffle > waddle).

**Override defaults:** Use `movement_modifier` to fine-tune speed for a specific item:

```json
{
  "movement_style": "shuffle",
  "movement_modifier": {
    "speed_multiplier": 0.3,
    "jump_disabled": true
  }
}
```

`movement_modifier` only works when `movement_style` is set. Without a style, the modifier is ignored.

**For artists:** Each style has idle and walk/move context animations that can be replaced with GLB files (see [Movement Style Contexts](#movement-style-contexts) above). The default animations are basic placeholders — custom GLBs will dramatically improve the feel.

### Inventory Icons

By default, data-driven items show a generic placeholder icon in the inventory. To give your item a custom inventory sprite, use the `icon` field.

**How it works:** The `icon` field points to a **Minecraft item model** (not a raw texture). The mod resolves this model at render time and displays it as the inventory sprite.

**Step 1: Create a 16x16 PNG texture**

Place it in your resource pack:
```
assets/<namespace>/textures/item/my_armbinder_icon.png
```

**Step 2: Create a model JSON**

Create a simple `item/generated` model that references your texture:
```
assets/<namespace>/models/item/my_armbinder_icon.json
```

Contents:
```json
{
  "parent": "minecraft:item/generated",
  "textures": {
    "layer0": "<namespace>:item/my_armbinder_icon"
  }
}
```

**Step 3: Reference it in your item/furniture JSON**

```json
{
  "display_name": "Leather Armbinder",
  "icon": "<namespace>:item/my_armbinder_icon",
  ...
}
```

The `icon` value is the model's ResourceLocation: `<namespace>:item/<filename>` (without the `.json` extension, matching how Minecraft references models).

**Shortcut:** You can also reference any existing vanilla or mod item model. For example, `"icon": "minecraft:item/chain"` displays the vanilla chain icon. Useful for testing.

**If `icon` is omitted:** The item displays the default generic sprite. No crash, no error.

---

## Packaging as a Resource Pack

### File Structure

```
MyItemPack/
  pack.mcmeta
  assets/
    mycreator/
      tiedup_items/                  ← JSON definitions (scanned at startup + F3+T)
        rope_gag.json
        iron_cuffs.json
        straitjacket.json
      models/gltf/                   ← GLB mesh files
        rope_gag.glb
        iron_cuffs.glb
        straitjacket.glb
      models/item/                   ← Icon model JSONs (for inventory sprites)
        rope_gag_icon.json
        iron_cuffs_icon.json
      textures/item/                 ← Icon textures (16x16 PNG)
        rope_gag_icon.png
        iron_cuffs_icon.png
      tiedup_contexts/               ← Context GLBs (optional, replaces default postures)
        stand_walk.glb
        stand_sneak.glb
```

### pack.mcmeta

```json
{
  "pack": {
    "pack_format": 15,
    "description": "My Custom TiedUp! Items"
  }
}
```

### Namespace

Use your own namespace (e.g., `mycreator`) to avoid conflicts with the base mod or other packs. The mod scans all namespaces for `tiedup_items/` and `tiedup_contexts/` directories.

---

## Validation & Debugging

The mod includes built-in tools to help you catch issues with your GLB files.

### `/tiedup validate` Command

Run `/tiedup validate` in-game (client-side command) to see a diagnostic report for all loaded GLBs:

- **RED** errors — item won't work (missing GLB, invalid file, no skin)
- **YELLOW** warnings — item works but something looks wrong (bone typo, multiple meshes, no Idle animation)
- **GRAY** info — informational (custom bones detected, vertex count)

Filter by item: `/tiedup validate tiedup:leather_armbinder`

The validation runs automatically on every resource reload (F3+T). Check your game log for a summary line: `[GltfValidation] Validated N GLBs: X passed, Y with warnings, Z with errors`.

### Mesh Naming Convention

If your GLB contains multiple meshes, name your item mesh `Item` in Blender. The mod prioritizes a mesh named `Item` over other meshes. If no `Item` mesh is found, the last non-`Player` mesh is used (backward compatible, but may pick the wrong one in multi-mesh files).

### Parse-Time Warnings (watch the log)

Beyond the toast-based validator, the parser emits WARN-level log lines on load for specific malformations. Grep your `logs/latest.log` for `[GltfPipeline]` / `[FurnitureGltf]` to catch these:

| WARN message | Meaning | What to do |
|---|---|---|
| `Clamped N out-of-range joint indices in '<file>'` | The mesh references joint indices ≥ bone count. Clamped to joint 0 (root) to avoid a crash — affected vertices render at the root position, usually visibly wrong. | In Blender, select the mesh, `Weights > Limit Total` (set to 4), then re-normalize and re-export. |
| `WEIGHTS_0 array length N is not a multiple of 4` | Malformed skin data (not per-glTF-spec VEC4). Trailing orphan weights are ignored. | Re-export. If it persists, check your mesh for non-mesh attribute overrides in Blender's Object Data properties. |
| `GLB size X exceeds cap 52428800` | File too large (>50 MB cap). Parsing is refused; the asset won't render. | Decimate mesh, downsize textures, or split the model. Furniture meshes rarely need to exceed 200 KB. |
| `Accessor would read past BIN chunk` / `Accessor count * components overflows int` | Malformed or hostile GLB accessor declaring impossible sizes. Parse refused. | Re-export from Blender (not an authoring mistake — likely a corrupted export). |

---

## Common Mistakes

### Skeleton Issues

| Mistake | Symptom | Fix |
|---------|---------|-----|
| Bone name typo (`RightUpperArm` instead of `rightUpperArm`) | WARN in log with suggestion: "did you mean 'rightUpperArm'?" — bone treated as custom | Names are **camelCase**, not PascalCase. Check exact spelling. Run `/tiedup validate` to see warnings. |
| Both `body` and `torso` bones present | WARN in log: "maps to PlayerAnimator part 'body' already written" — only the first bone in the joint array drives the pose, the other is ignored | Use one or the other. Prefer `body`. Delete the redundant bone from the rig. |
| Extra bones in the armature | Custom bones follow their parent in rest pose | Intentional custom bones are fine (chains, decorations). Unintentional ones add file size — delete them. |
| Missing `PlayerArmature` root | Mesh renders at wrong position | Rename your armature root to `PlayerArmature` |
| Animating `body` bone without TORSO region | Body keyframes used only if `body` is free (no other item owns it) | Declare TORSO/WAIST region if you always want to control body, or use `Full` animations for free-bone effects |

### Animation Issues

| Mistake | Symptom | Fix |
|---------|---------|-----|
| Action not prefixed with `PlayerArmature\|` | Animation not found, falls back to first clip | Rename: `Idle` → `PlayerArmature\|Idle`. Note: the mod strips any `ArmatureName\|` prefix, so custom armature names also work. |
| Wrong case (`idle` instead of `Idle`) | Animation not found | Use exact PascalCase: `Idle`, `SitIdle`, `KneelStruggle` |
| Variant gap (`.1`, `.2`, `.4` — missing `.3`) | Only .1 and .2 are used | Number sequentially with no gaps |
| Animating bones outside your regions | Keyframes silently ignored | Only animate bones in your declared regions |
| Bone rotated a quarter-turn around its vertical axis (yaw ≈ ±90°) | Jitter or sudden flip in pitch/roll at the boundary | Gimbal-lock in the Euler ZYX decomposition used to feed PlayerAnimator. Keep bone yaw within ±85° of forward; if you need a 90° yaw, add a few degrees of pitch or roll. |
| Animation looks choppy or loses keyframes | Source FPS > 20 — multiple source frames round to the same MC tick and all but the first are deduplicated | Set Blender's scene FPS to 20 and re-export. See [Animation Frames](#animation-frames) for the mapping table. |

### Weight Painting Issues

| Mistake | Symptom | Fix |
|---------|---------|-----|
| Vertices not weighted to any bone | Part of mesh stays frozen in space | Weight paint everything to at least one bone |
| Weights not normalized | Mesh stretches or compresses oddly | Blender > Weights > Normalize All |
| Weighted to a non-standard bone | Mesh follows parent bone in rest pose | This is OK if intentional (custom bones). If not, re-weight to a standard bone. |

### JSON Issues

| Mistake | Symptom | Fix |
|---------|---------|-----|
| Wrong model path | Item invisible | Check ResourceLocation format: `namespace:path/to/file.glb` |
| Missing `regions` | Item can't be equipped | Every item needs at least one region |
| `pose_priority: 0` | Other items always override yours | Use at least 1. See priority guide above. |
| `blocked_regions` too broad | Players can't equip combinations you intended | Only block what your item physically covers |

---

## Examples

### Example 1: Simple Collar (No Animation)

A collar sits on the neck. It doesn't change the player's pose.

**Blender:**
- Model a ring mesh around the neck area
- Weight paint to `body` bone (so it follows torso movement)
- Create `PlayerArmature|Idle` with a single keyframe — don't move any bones (identity pose)
- Export GLB

**JSON:**
```json
{
  "type": "tiedup:bondage_item",
  "display_name": "Leather Collar",
  "model": "mycreator:models/gltf/leather_collar.glb",
  "regions": ["NECK"],
  "pose_priority": 5,
  "escape_difficulty": 3,
  "lockable": true
}
```

The collar renders on the player's neck, follows body movement, and takes the NECK slot. No bones are animated — the player moves normally.

### Example 2: Handcuffs (Arms Behind Back)

**Blender:**
- Model cuff meshes on both wrists + a chain between them
- Weight paint cuffs to `rightLowerArm` and `leftLowerArm`, chain to `body`
- Create `PlayerArmature|Idle`: pose both arms behind the back
- Optionally create `PlayerArmature|Struggle`: multi-frame animation of pulling against cuffs
- Export GLB

**JSON:**
```json
{
  "type": "tiedup:bondage_item",
  "display_name": "Iron Handcuffs",
  "model": "mycreator:models/gltf/iron_cuffs.glb",
  "animation_source": "tiedup:models/gltf/templates/handcuff_anims.glb",
  "regions": ["ARMS"],
  "animation_bones": {
    "idle": ["rightArm", "leftArm"],
    "struggle": ["rightArm", "leftArm"]
  },
  "pose_priority": 30,
  "escape_difficulty": 5,
  "lockable": true
}
```

Using `animation_source` means the cuffs mesh comes from your GLB but animations come from the official handcuff template. The arms go behind the back, the cuffs mesh follows. The player can still walk, look around, sit, and sneak — only the arms are locked.

### Example 3: Blindfold (Head Region, Cosmetic Pose)

**Blender:**
- Model a strip of cloth across the eyes
- Weight paint to `head` bone
- Create `PlayerArmature|Idle`: tilt head down ~10 degrees (subtle "I can't see" pose)
- Export GLB

**JSON:**
```json
{
  "type": "tiedup:bondage_item",
  "display_name": "Silk Blindfold",
  "model": "mycreator:models/gltf/silk_blindfold.glb",
  "regions": ["EYES"],
  "animation_bones": {
    "idle": ["head"]
  },
  "pose_priority": 10,
  "escape_difficulty": 1,
  "lockable": false
}
```

The blindfold occupies EYES (a sub-region of HEAD). A hood (HEAD) would block it, but a gag (MOUTH) wouldn't. The head tilt is subtle and combines with vanilla head tracking.

---

## Furniture Creation

> Create interactive furniture (St. Andrew's Cross, pillory, cage, etc.) using a **single GLB file** containing both the furniture model and player seat skeletons.

### How Furniture Works

Unlike body items (which are skinned onto the player's skeleton), furniture is a **standalone entity** in the world. It has its own mesh, its own skeleton, and its own animations. Players "sit" on it via the riding system, and the mod forces a pose on the player from the furniture's GLB.

```
One GLB file contains everything:

  Furniture_Armature      ← The furniture mesh (cross, pillory, cage...)
  │  Mesh: wood_frame, chains, locks...
  │  Animations: Idle, Occupied, LockClose, Shake...
  │
  Player_main             ← Player skeleton #1 (positioned on the furniture)
  │  Standard 11 bones (same as body items)
  │  Animations: main:Idle, main:Struggle, main:Enter...
  │
  Player_left (optional)  ← Player skeleton #2 (for multi-seat furniture)
  │  Same 11 bones
  │  Animations: left:Idle, left:Struggle...
  │
  Player_right (optional) ← Player skeleton #3
     ...
```

The artist sees the **exact result** in Blender — the player posed on the furniture. No guessing offsets in JSON.

> **Note:** There is currently no ghost preview when placing furniture in-game. The entity spawns immediately on right-click. Use Blender to check positioning.

### Furniture Skeleton

The furniture armature can have **any bones you want**. Unlike body items (which must use the standard 11-bone player skeleton), furniture bones are custom. Name them whatever makes sense: `door_hinge`, `chain_left`, `lock_bolt`, etc. The name `Furniture_Armature` used in this guide is a convention — any name works as long as it does NOT start with `Player_`.

```
Furniture_Armature       ← armature root (required, any name NOT starting with "Player_")
  └─ frame               ← main body of the furniture
       ├─ door_hinge      ← animated: opens/closes
       ├─ chain_left      ← animated: tightens when occupied
       ├─ chain_right
       └─ lock_bolt       ← animated: rotates when locked
```

**Rules:**
- The furniture armature name must NOT start with `Player_` (that prefix is reserved for seat skeletons)
- All bones are kept — no filtering (unlike body items which filter through `isKnownBone`)
- Weight paint your furniture mesh to these bones normally

### Seat Skeletons (Player_*)

Each seat is a **standard player skeleton** (the same 11 bones from body items) positioned on the furniture. The armature name determines the seat ID.

```
Player_main     → seat ID: "main"
Player_left     → seat ID: "left"
Player_right    → seat ID: "right"
Player_grab     → seat ID: "grab" (for monsters)
```

**How to set up a seat in Blender:**

1. **Duplicate the standard player armature** (from a body item template or the skeleton reference above)
2. **Rename it** to `Player_{seatId}` (e.g., `Player_main`)
3. **Position it** on the furniture exactly where you want the player to appear
4. **Pose it** in the `Idle` position (arms spread on a cross, bent over a pillory, etc.)
5. The root position + rotation of this armature = where the player sits in-game

**Important:**
- The seat skeleton uses the EXACT same 11 bone names as body items (`body`, `head`, `leftUpperArm`, etc.)
- You do NOT need a mesh for the seat skeleton — it's data-only (positioning + animation)
- The seat skeleton's world-space position relative to the furniture origin = the player's offset in-game
- Multiple seats = multiple `Player_*` armatures (each positioned differently on the furniture)

### Furniture Animations

Furniture uses a **naming convention** to separate furniture animations from player animations.

#### Furniture Mesh Animations

Target the `Furniture_Armature`. Blender exports them as `Furniture_Armature|AnimName`.

| Animation Name | When Played | Required? |
|---------------|------------|-----------|
| `Idle` | Default state, no passengers | Recommended |
| `Occupied` | At least one player is seated | Optional (falls back to Idle) |
| `LockClose` | A seat gets locked (one-shot) | Optional |
| `LockOpen` | A seat gets unlocked (one-shot) | Optional |
| `Shake` | Player is struggling (loops during struggle) | Optional |

Example in Blender's Action Editor:
```
Furniture_Armature|Idle       ← chains hanging loose
Furniture_Armature|Occupied   ← chains pulled taut
Furniture_Armature|Shake      ← whole frame vibrates
```

#### Player Seat Animations

Target the `Player_*` armatures. Blender exports them as `Player_main|AnimName`.
The mod resolves them per seat ID (e.g., `Player_main|Idle` → seat `main`, clip `Idle`).

| Animation Name | When Played | Required? |
|---------------|------------|-----------|
| `Idle` | Default seated pose (STATE_IDLE) | **Yes** — canonical fallback |
| `Occupied` | At least one passenger is seated (STATE_OCCUPIED) | Optional (falls back to Idle) |
| `Struggle` | Player struggling to escape (STATE_STRUGGLE) | Optional (falls back to Occupied → Idle) |
| `Enter` | Mount transition (STATE_ENTERING, ~20 ticks) | Optional (falls back to Occupied → Idle) |
| `Exit` | Dismount transition (STATE_EXITING, ~20 ticks) | Optional (falls back to Occupied → Idle) |
| `LockClose` | Seat is being locked (STATE_LOCKING) | Optional (falls back to Occupied → Idle) |
| `LockOpen` | Seat is being unlocked (STATE_UNLOCKING) | Optional (falls back to Occupied → Idle) |

The mod plays the state-specific clip if authored. When a state transitions server-side, the pose updates automatically on all clients — no packet work required.

**Fallback chain:** state-specific clip → `Occupied` → first authored clip. This means: if you only author `Idle`, the player holds it for every state. Adding `Struggle` and `Enter` gets you polish on those states without breaking anything if you skip the rest.

Example in Blender's Action Editor:
```
Player_main|Idle       → seat "main" clip "Idle"      ← arms spread, legs apart
Player_main|Struggle   → seat "main" clip "Struggle"  ← pulling against restraints
Player_main|Enter      → seat "main" clip "Enter"     ← one-shot mount transition
Player_left|Idle       → seat "left" clip "Idle"      ← head and arms through pillory
Player_right|Idle      → seat "right" clip "Idle"     ← same pose, other side
```

**Key difference from body items:** Furniture player animations control **ALL 11 bones**, not just region-owned bones. The furniture overrides the player's entire pose for the blocked regions, and the remaining regions still show body item effects (gag, blindfold, etc.).

### The JSON Definition

Furniture JSON goes in `data/<namespace>/tiedup_furniture/`. Unlike body items, furniture definitions are **server-authoritative** and synced to clients automatically.

#### Full Format

```json
{
  "id": "mynamespace:my_cross",
  "display_name": "Custom Cross",
  "translation_key": "furniture.mynamespace.my_cross",

  "model": "mynamespace:models/gltf/furniture/my_cross.glb",
  "icon": "mynamespace:item/my_cross_icon",

  "tint_channels": {
    "tintable_0": "#8B4513",
    "tintable_1": "#1A1A1A"
  },
  "supports_color": true,

  "hitbox": { "width": 1.2, "height": 2.4 },

  "placement": {
    "snap_to_wall": true,
    "floor_only": true
  },

  "lockable": true,
  "break_resistance": 100,
  "drop_on_break": true,

  "seats": [
    {
      "id": "main",
      "armature": "Player_main",
      "blocked_regions": ["ARMS", "HANDS", "LEGS", "FEET"],
      "lockable": true,
      "locked_difficulty": 150,
      "item_difficulty_bonus": true
    }
  ],

  "category": "restraint"
}
```

#### JSON Field Guide

| Field | Required? | Description |
|-------|-----------|-------------|
| `id` | Yes | Unique ID, e.g., `mynamespace:wooden_cross` |
| `display_name` | Yes | Fallback name if no translation key |
| `translation_key` | No | i18n key for localized name |
| `model` | Yes | Path to GLB file in assets |
| `icon` | No | Inventory sprite model (same system as body items — see [Inventory Icons](#inventory-icons)) |
| `tint_channels` | No | Default tint colors (hex `#RRGGBB`) per channel |
| `supports_color` | No | **Planned** — player recoloring via dye (default: false) |
| `hitbox.width` | No | Entity collision width (0.1–5.0, default: 1.0) |
| `hitbox.height` | No | Entity collision height (0.1–5.0, default: 1.0) |
| `placement.snap_to_wall` | No | Align to nearest wall on placement (default: false) |
| `placement.floor_only` | No | Can only be placed on solid ground (default: true) |
| `lockable` | No | Can seats be locked with a key? (default: false) |
| `break_resistance` | No | Cumulative damage to break (1–10000, default: 100) |
| `drop_on_break` | No | Drop placer item when broken? (default: true) |
| `seats` | Yes | 1–8 seat definitions (see below) |
| `feedback.mount_sound` | No | Sound when a player is force-mounted |
| `feedback.lock_sound` | No | Sound when a seat is locked |
| `feedback.unlock_sound` | No | Sound when a seat is unlocked |
| `feedback.struggle_loop_sound` | No | Sound played when struggle starts |
| `feedback.escape_sound` | No | Sound on successful escape (default: chain break) |
| `feedback.denied_sound` | No | Sound when action is denied (locked dismount, no seat) |
| `category` | No | Creative tab grouping (default: "furniture") |

#### Seat Definition Fields

| Field | Required? | Description |
|-------|-----------|-------------|
| `id` | Yes | Seat identifier (must match `Player_{id}` armature name, no `:` allowed) |
| `armature` | Yes | GLB armature name (e.g., `Player_main`) |
| `blocked_regions` | No | Body regions the furniture controls (default: empty — no blocking) |
| `lockable` | No | Can this seat be locked? (inherits from top-level `lockable`) |
| `locked_difficulty` | Yes if lockable | Struggle difficulty when locked (1–10000) |
| `item_difficulty_bonus` | No | Body items on free regions add to escape difficulty? (default: false) |

#### Blocked Regions Explained

When a player sits in a seat with `blocked_regions: ["ARMS", "HANDS"]`:
- **Animation:** The furniture's player pose controls arm + hand bones. Body items on ARMS/HANDS are ignored.
- **Rendering:** Body items on ARMS/HANDS are hidden (the furniture pose replaces them visually).
- **Gameplay:** The master cannot equip/unequip items on ARMS/HANDS while the player is seated.
- **Other regions** (HEAD, MOUTH, NECK, etc.) work normally — items render, can be changed, and contribute to escape difficulty if `item_difficulty_bonus: true`.

> **Note:** The struggle minigame for escaping locked furniture seats uses the same continuous struggle system as body items. The player holds directional keys to drain resistance. The total difficulty = `locked_difficulty` + bonus from equipped body items on non-blocked regions (if `item_difficulty_bonus: true`), capped at 600.

### Tint Channels (Same as Body Items)

Furniture supports the exact same tint channel system as body items. See [Tint Channels](#tint-channels-dynamic-colors) above — everything applies identically.

Name your Blender materials `tintable_0`, `tintable_1`, etc., use grayscale textures, and define defaults in the JSON `tint_channels` field.

### Exporting Furniture from Blender

Same export settings as body items, with one extra consideration:

1. **Select ALL armatures** — Furniture_Armature AND all Player_* armatures
2. **File → Export → glTF 2.0 (.glb)**
3. Settings:
   - Format: glTF Binary (.glb)
   - Include: Selected Objects
   - Mesh: Apply Modifiers
   - Animation: Export all actions (NLA strips or all actions)
   - **Important:** Do NOT merge armatures — each must remain separate

### Packaging as a Resource/Data Pack

Furniture needs files in **two** locations:

```
my_resource_pack/
├── assets/mynamespace/
│   ├── models/gltf/furniture/
│   │   └── my_cross.glb              ← The GLB model (client resource)
│   ├── models/item/
│   │   └── my_cross_icon.json        ← Icon model (inventory sprite)
│   └── textures/item/
│       └── my_cross_icon.png         ← Icon texture (16x16)
│
└── data/mynamespace/
    └── tiedup_furniture/
        └── my_cross.json             ← The JSON definition (server data)
```

The GLB goes in `assets/` (it's a client resource for rendering). The JSON goes in `data/` (it's server-authoritative gameplay data, synced to clients via packet).

### Common Furniture Mistakes

| Mistake | Symptom | Fix |
|---------|---------|-----|
| Seat armature named `Main` instead of `Player_main` | No seat detected, furniture is decoration only | Prefix MUST be `Player_` |
| Seat ID contains `:` (e.g., `Player_seat:left`) | JSON rejected by parser | Use only alphanumeric + underscore in seat IDs |
| Player skeleton uses wrong bone names | Player not posed correctly on furniture | Use the exact 11 standard bone names (camelCase) |
| Armatures merged into one on export | Parser can't separate furniture from seats | Export with separate armatures, don't merge |
| No `Idle` animation for a seat | Player has no pose on furniture | Every seat MUST have at least `{armature}|Idle` |
| Furniture animation named `Player_main|Idle` | Parsed as a seat animation, not furniture | Furniture anims go on `Furniture_Armature`, not `Player_*` |
| More than 8 seats | JSON rejected by parser | Maximum 8 seats per furniture piece |
| `blocked_regions` references unknown region | JSON rejected by parser | Use exact names from the Region Table above |

### Furniture Examples

#### Example 1: Simple Chair (No Restraint)

A decorative chair anyone can sit on. No locking, no blocked regions.

**Blender:**
- Model a chair mesh, weight paint to a single `frame` bone
- Add `Player_main` skeleton sitting on the chair
- Create `Player_main|Idle` with the player seated (legs bent, arms on armrests)
- Create `Furniture_Armature|Idle` (static, single keyframe)

**JSON:**
```json
{
  "id": "mycreator:wooden_chair",
  "display_name": "Wooden Chair",
  "model": "mycreator:models/gltf/furniture/wooden_chair.glb",
  "hitbox": { "width": 0.8, "height": 1.0 },
  "seats": [
    {
      "id": "main",
      "armature": "Player_main",
      "blocked_regions": []
    }
  ]
}
```

No locking, no blocked regions. The player sits freely and can stand up anytime.

#### Example 2: St. Andrew's Cross (Single Seat)

**Blender:**
- Model the X-frame with chains at wrist and ankle positions
- Add bones for animated parts: `chain_left`, `chain_right`, `lock_mechanism`
- Add `Player_main` skeleton with arms and legs spread in an X pose
- Create animations:
  - `Furniture_Armature|Idle` — chains hanging loose
  - `Furniture_Armature|Occupied` — chains pulled taut
  - `Furniture_Armature|Shake` — frame vibrating (for struggle)
  - `Player_main|Idle` — arms spread, legs apart, flush against the cross
  - `Player_main|Struggle` — pulling against restraints, body twisting

**JSON:**
```json
{
  "id": "mycreator:saint_andrews_cross",
  "display_name": "St. Andrew's Cross",
  "model": "mycreator:models/gltf/furniture/cross.glb",
  "tint_channels": { "tintable_0": "#8B4513" },
  "supports_color": true,
  "hitbox": { "width": 1.2, "height": 2.4 },
  "placement": { "snap_to_wall": true },
  "lockable": true,
  "break_resistance": 150,
  "seats": [
    {
      "id": "main",
      "armature": "Player_main",
      "blocked_regions": ["ARMS", "HANDS", "LEGS", "FEET"],
      "lockable": true,
      "locked_difficulty": 150,
      "item_difficulty_bonus": true
    }
  ],
  "category": "restraint"
}
```

The player is locked with arms and legs controlled by the cross. A master can still equip a gag (MOUTH), blindfold (EYES), or collar (NECK) on the mounted player. The gag adds to escape difficulty because `item_difficulty_bonus: true`.

#### Example 3: Double Pillory (Two Seats)

**Blender:**
- Model a long wooden pillory with two holes
- Add `Player_left` and `Player_right` skeletons, each bent forward with head and arms through the pillory holes
- Position them side by side on the furniture

**JSON:**
```json
{
  "id": "mycreator:double_pillory",
  "display_name": "Double Pillory",
  "model": "mycreator:models/gltf/furniture/pillory.glb",
  "hitbox": { "width": 2.0, "height": 1.5 },
  "lockable": true,
  "seats": [
    {
      "id": "left",
      "armature": "Player_left",
      "blocked_regions": ["ARMS", "HANDS", "HEAD"],
      "lockable": true,
      "locked_difficulty": 120
    },
    {
      "id": "right",
      "armature": "Player_right",
      "blocked_regions": ["ARMS", "HANDS", "HEAD"],
      "lockable": true,
      "locked_difficulty": 120
    }
  ],
  "category": "restraint"
}
```

Two players can be locked side by side. The mod picks the seat nearest to where you're looking when you sit down.

### Monster Seat System (Planned)

The furniture system is built on an `ISeatProvider` interface currently implemented only by `EntityFurniture`. The design intent is that any living entity (monster, NPC) could implement the same interface to hold players in constrained poses using the same mechanics: blocked regions, forced animations, lock/escape — but no second implementation exists yet.

**Example use case:** A tentacle monster that grabs a player on attack — the player "rides" the monster, gets a forced pose (arms restrained), and must struggle to escape. The monster's GLB would contain a `Player_grab` armature with `Player_grab|Idle` and `Player_grab|Struggle` animations, following the exact same convention as furniture seats.

**What this means for artists:** If you create a monster model, you can include `Player_*` armatures in the GLB using the same workflow as furniture. The seat animations, blocked regions, and escape mechanics will work identically.

**Current status:** The `ISeatProvider` interface and all downstream systems (animation, rendering, packets, escape) are implemented and ready. No monster entity or AI has been created yet — this requires its own design phase (AI goals, behaviors, spawn conditions, etc.).

---

## Quick Reference Cards

### Body Items

```
REQUIRED:
  ✓ Skeleton: PlayerArmature with 11 named bones (camelCase, exact)
  ✓ At least one animation: PlayerArmature|Idle
  ✓ Weight painting to standard bones
  ✓ JSON in assets/<namespace>/tiedup_items/ with type, display_name, model, regions

NEVER DO:
  ✗ Animate PlayerArmature (armature root, not a bone)
  ✗ Use wrong case for bone names or animation names
  ✗ Leave vertices unweighted
  ✗ Use pose_priority 0

GOOD TO KNOW:
  → Only Idle is required. Everything else has fallbacks.
  → animation_bones is optional. Omit it and all owned bones work for all animations.
  → Templates let you skip animation entirely.
  → Custom bones are supported — add chain/ribbon/twist bones parented to standard bones.
  → Free bones (body, legs) can be animated using Full-prefixed animations (FullWalk, FullStruggle).
  → Head is protected — use FullHead prefix (FullHeadStruggle) to also animate the head.
  → Bones owned by another equipped item are always ignored.
  → The mod handles sitting, sneaking, walking — you don't have to.
  → Context GLBs in tiedup_contexts/ replace default postures.
  → Name your item mesh "Item" in Blender for explicit selection in multi-mesh files.
  → Run /tiedup validate in-game to check your GLBs for issues.
  → Slim model is optional. Steve mesh works on Alex (minor clipping).
  → Textures bake into the GLB. No separate file needed.
```

### Furniture

```
REQUIRED:
  ✓ Furniture_Armature (any name NOT starting with "Player_")
  ✓ At least one Player_{seatId} armature with 11 standard player bones
  ✓ At least one animation per seat: Player_{seatId}|Idle
  ✓ GLB in assets/<namespace>/models/gltf/furniture/
  ✓ JSON in data/<namespace>/tiedup_furniture/ with id, display_name, model, seats

NEVER DO:
  ✗ Name furniture armature starting with "Player_"
  ✗ Use ":" in seat IDs
  ✗ Merge armatures on export (each must stay separate)
  ✗ Use wrong bone names in seat skeletons
  ✗ More than 8 seats per furniture

GOOD TO KNOW:
  → Furniture bones can be anything (not limited to player skeleton)
  → Seat position = Player_* armature position in Blender (no JSON offset)
  → Player animations on blocked regions override body items
  → Body items on non-blocked regions still render normally
  → Furniture mesh animations (Idle, Occupied, Shake) are optional
  → Tint channels work the same as body items
  → JSON is server-authoritative, synced to clients automatically
```
