Skip to content

Attachment Files

The definition file is the heart of an attachment: it names the assets, picks the anchor, places the model, and holds every optional behaviour block.

Path: data/<namespace>/attachment/<id>.json

Schema: townstead:attachment/v1

ProblemRuntime result
Missing definition referenced by a geneNothing renders for that grant; the doctor reports it.
Missing base geometry or texture, including an omitted referenceThe whole definition is rejected and absent from the synced manifest.
Invalid whenThe definition loads, but the condition cannot match, so the attachment stays hidden. Validation and the doctor report it.
Missing morph, visibility, pose, physics, or animation boneThat operation has no effect on the missing bone; valid bones continue. Checks use the union of base and stage geometry.
Unknown target point or tagThe definition loads but creates no rendered instance.
Invalid life-stage nameThe entry is never selected; base geometry, offset, and scale remain.
Missing stage geometryThat stage keeps the base model while retaining its stage offset and scale.
Out-of-range physics valueThe authored value still runs; the doctor warns rather than clamping it.
Invalid segments count, bone list, or axisThe doctor reports it. Segmentation requires 2..8 and exactly one source bone; a failed derived bake falls back to unsliced geometry.
Missing animation fileThat animation entry is skipped during load.
Missing animation clipThe entry remains but produces no sample; other poses, clips, and physics continue.

Treat validation errors as authoring failures even where rendering can continue; fallbacks keep other content visible but are not a stable substitute for valid data.

FieldTypeRequiredNotes
geometryrefyesA .geo.json under attachment/geo/, or a Blockbench project under attachment/bbmodel/ (the .geo.json wins when both exist). Write "fox_tail" or "other_ns:fox_tail"; the folder and extension are implied. A bbmodel reference may bake one of its embedded animations as a static pose: "wings#spread". Bbmodels convert at load with Blockbench’s own Bedrock transform, keep the project’s coordinate origin (use the definition offset to place it), and ship over the same blob sync. Their embedded animations also serve animations references ("wings#flap").
texturerefyesA .png under attachment/textures/. Same reference form.
targetobjectno{ "tag": "..." } anchors to every matching point; { "point": "..." } anchors to one named point. See Attachment Points.
bonestringnoDirect model-bone anchor when there is no target. Defaults to body.
offset[x, y, z]noPlacement offset in model pixels, applied after the anchor.
rotation[x, y, z]noBase orientation in degrees, applied ZYX.
scalenumbernoUniform render scale. Defaults to 1.0.
tint, tint_blend, tint_strengthmixednoColouring. See Surfaces & Colour.
emissiverefnoA second texture drawn full-bright over the same geometry.
renderstringnocutout (default) or translucent.
hides_underlistnoArmour slots that hide the whole attachment while worn. See below.
whenobjectnoA Pheno condition gating the whole attachment: it renders only while the condition holds for the bearer. See below.
morphobjectnoMaps heritable channel values onto bone scale and rotation. See Genetics & Morphs.
visibilitylistnoThreshold-gated bones. See Genetics & Morphs.
stagesobjectnoPer-life-stage overrides. See below.
posesobjectnoState-driven rotations. See Poses & Equipment.
physicsobjectnoSecondary-motion chains. See Bone Physics.
animationsobjectnoKeyframe clip triggers. See Keyframe Animations.

Targeting precedence is target.tag, then target.point, then bone. A tag can resolve to several points at once, so one definition can fill both ears.

Geometry is standard Bedrock .geo.json, which Blockbench can export:

  • Named bones with pivots, parents, and per-bone rotation.
  • Box UV or per-face UV cubes, including mirror and per-cube rotation. A per-face UV entry that omits a face skips that face entirely, matching Blockbench’s transparent-face export.
  • texture_width and texture_height should match the PNG. When they disagree, UVs are scaled and /townstead attachment doctor warns.
  • Save a normal cube-and-group .bbmodel; no manual Bedrock export is required. Current Blockbench 4.x and 5.x metadata are recognized for animation rotation conventions.
  • Author in model pixels: 16 units are one Minecraft block. The project origin is preserved; use definition offset for final placement.
  • Put cubes in named groups. Groups become bones and nested groups form the hierarchy. Give every bone a unique stable name because all behavioral features resolve it by name.
  • Supported cube data includes origin and size, inflation, cube rotation and pivot, box UVs, mirrored UVs, and per-face UV rectangles. Meshes, locators, texture meshes, and other non-cube elements are not converted as attachment geometry.
  • Group origins become bone pivots; group and cube rotations are converted to Townstead’s geometry convention. Keep the intended project origin rather than recentering to correct placement.
  • Embedded .bbmodel animations support rotation channels. A named clip can also provide its first rotation keyframe as a static baked pose.
  • Embedded position and scale channels, plus Molang or other non-numeric expressions, are skipped with warnings. Export .animation.json when position or scale is required; Molang remains unsupported there.
  • If matching .geo.json and .bbmodel files both exist, .geo.json wins.
  • Use /townstead attachment dump-geo <id> to inspect the exact converted geometry sent to clients when hierarchy, pivot, cube, coordinate, or UV conversion looks wrong.

Bone names matter: morphs, poses, physics chains, animations, and visibility rules all address bones by name, and the doctor validates every reference against the geometry (including stage-swapped geometry).

Author the model around its natural mount point, then place it with offset, rotation, and scale. Rather than guessing values and reloading, use the live commands:

/townstead attachment adjust my_pack:fox_tail rotation -30 0 0
/townstead attachment adjust my_pack:fox_tail offset 0 0 1
/townstead attachment dump my_pack:fox_tail

adjust updates every rendered instance immediately and lasts until the next reload; dump prints file-ready JSON with the live values folded in. See Tooling & Commands.

stages overrides render placement per canonical life stage, keyed baby, toddler, child, teen, adult, and senior. Players read as adults.

"stages": {
"baby": { "scale": 0.5, "offset": [0, 1, 0] },
"child": { "scale": 0.75 },
"adult": { "geometry": "my_pack:fox_tail_three" },
"senior": { "geometry": "my_pack:fox_tail_three" }
}
FieldTypeNotes
scalenumberMultiplies with the definition scale and any genetic size roll.
offset[x, y, z]Extra offset in model pixels, added to the definition offset.
geometryrefA whole different model for this stage. Stage geometry ships over the same blob sync.

A growth arc such as fawn nubs, antlers, and an elder rack is just three geometry swaps, or a fox line that grows one tail as a child and three from adulthood.

By default an attachment punches through worn armour. hides_under suppresses the whole attachment (emissive layer included) while the bearer wears anything in a listed slot:

"hides_under": ["helmet"]

Valid slots are helmet, chestplate, leggings, and boots. For a softer reaction, fold the attachment with a wearing_* pose instead of hiding it; see Poses & Equipment.

when gates the whole attachment on a Pheno condition, evaluated client-side per bearer exactly like pose conditions. The classic use is a beard dwarven women carry in their blood but never wear:

"when": { "type": "pheno:not", "condition": { "type": "pheno:gender", "is": "feminine" } }

Phrase gates so unknown bearers land on the right default: pheno:gender reads MCA gender from villagers and players alike but never matches entities without one, so not feminine keeps the beard on unknown bearers while hiding it on feminine villagers and players. The mirror gate ("is": "feminine" with no not) grants an attachment to women only, like braids. No when, or one that fails to parse (the doctor flags it), renders unconditionally.

A tail definition using most of the optional blocks:

{
"schema": "townstead:attachment/v1",
"geometry": "my_pack:fox_tail",
"texture": "my_pack:fox_tail",
"target": { "tag": "tail_root" },
"offset": [0, 0, 1],
"rotation": [-120, 0, 0],
"scale": 0.85,
"tint": "gene",
"tint_blend": "color",
"emissive": "fox_tail_glow",
"stages": {
"child": { "scale": 0.75 },
"adult": { "geometry": "my_pack:fox_tail_three" }
},
"poses": {
"sleeping": { "bones": { "tail_1": { "rotation": [0, 40, 0] } }, "transition": 20 }
},
"physics": {
"chains": [ { "bones": ["tail_1", "tail_2", "tail_3"], "stiffness": 0.35, "damping": 0.82 } ]
}
}