Skip to content

Asset Packs

Moshu edited this page Oct 5, 2026 · 1 revision

Asset Packs

Content Studio and Fit Lab can fit parts of third-party modular 3D packs (outfits, helmets, weapons) onto the UO body and export them as UO equipment. Each pack is described by one asset-pack mapping: a JSON document (schema: spritemotion.asset-pack) that says how the pack's skeleton fits the UO rig and which UO layer each part becomes.

Source: docs/asset-packs.md.

One pack, one mapping

Packs are handled 1:1. Nothing in the studio is specific to a pack: tools/uo-content/pack_fit.py reads the mapping, and a job selects it with pack_mapping. A mapping holds two tables:

  1. Bones → target rig (uo-model3d-v13): how each pack bone is fitted onto the canonical UO rig.
  2. Parts → equipment slots: which UO layer each part type becomes, from games/ultima-online/equipment/layers.json.

A fictional, validated example: examples/asset-pack/example-modular.json. The CC0 starter has a real one: examples/cc0-starter/outfit-mapping.json.

Licensing: the sidecar

Most commercial packs forbid redistributing their meshes, textures and data. Keep a commercial pack's mapping, its batch scripts and every output outside the repository, in a local sidecar folder (SPRITEMOTION_SIDECAR, default ../SpriteMotion-Sidecar) that is never pushed. The pack's own files are located through the environment variable named in the mapping's source.env; they are never copied.

A mapping may go in games/<game>/asset-packs/ only if its pack's license allows it.

Bones

bones lists every bone of the pack's skeleton.

Field Meaning
role root, deform, twist, finger, attach, face, dynamic, ik, prop
target Target-rig bone that receives this bone's weights
end On aligned bones: the source bone at the far end of the chain
keep_orientation Aligned bone that is moved only, not rotated or scaled (spine, head)
target_end Optional target bone ending the aligned chain (e.g. a knuckle for wrist-to-knuckle chains)
end_from_parent With end: null, extrapolate the previous segment for a terminal finger
follows For a non-aligned bone: the aligned ancestor it moves with
proposed_target A better rig target not yet used by the fitter (twist bones, fingers, shield bone)

Fit per source mesh: each aligned chain is rotated onto its target bone and scaled to its length (clamped). Every vertex follows the weighted blend of its bones; non-aligned bones use their nearest aligned ancestor. dynamic_chains covers cloth/hair bones that ship inside part files; attach_points lists sockets.

Parts

One entry per part type.

Field Meaning
status uo (becomes an item on uo_layer), merge, extra, body, skip
uo_layer Layer id from layers.json
alternatives Other layers that fit some pieces
pair The mirrored part; left and right become one item (one UO layer = one item)
studio_part Fit template: helm, chest, arms, gloves, legs, boots, robe, cloak, skirt, weapon, shield, bow, quiver
bind skinned or rigid
offset, rotate, scale, depth_scale Rest-pose fit
surface_clearance Push close-fitting shells out of the body surface before binding
hide_body {enabled, outward, inward}: hide body faces under the part

uncovered_layers lists animated UO layers the pack has nothing for. Tune offset, rotate, scale, bind and hide_body per slot in Fit Lab.

UO layers at a glance

Id Layer Animated Id Layer Animated
1 OneHanded yes 14 Bracelet paperdoll only
2 TwoHanded (shields, bows, 2H weapons) yes 15 Face yes
3 Shoes yes 16 FacialHair yes
4 Pants yes 17 MiddleTorso yes
5 Shirt yes 18 Earrings paperdoll only
6 Helm yes 19 Arms yes
7 Gloves yes 20 Cloak (also quivers) yes
8 Ring paperdoll only 21 Backpack yes
9 Talisman paperdoll only 22 OuterTorso yes
10 Neck yes 23 OuterLegs yes
11 Hair yes 24 InnerLegs yes
12 Waist yes 25 Mount internal
13 InnerTorso yes

Adding a pack

  1. Dump the pack's skeleton and the bones each part type is weighted to (a Blender script importing the base model and a few part files per type).
  2. Write the mapping: align the main chains (pelvis, spine, neck, head, clavicles, arms, hands, legs, feet, toes), let everything else follow, map part types to layers, mark pairs, use rigid binding for hard pieces.
  3. Validate:
    python -c "from spritemotion.schemas import validate, read_json; print(validate(read_json('<file>'), 'spritemotion.asset-pack', required=True))"
    An empty list means valid.
  4. Build a preview job with source_files, palette and pack_mapping, and review the contact sheet before full builds.
  5. Export it to Fit Lab (python tools/fit-lab/run.py export --pack <pack>) and tune slots there.

After a mapping change, regenerate the GLBs and create new render jobs; existing jobs keep their frozen mapping.

Clone this wiki locally