Bone Data Section

This section describes the actor's bones, including the hierarchy and pose position.

Header

Offset Type Description
0x0 Magic (0x007B7960 for Sluggers)
0x4 uint16 Actor ID
0x6 uint16 Bone count
0x8 uint32 First word of an embedded tree structure (unknown purpose)
0xc uint32 Root node pointer (relative to header). This is the second word of the tree structure and is used to traverse the bone hierarchy.
0x10 uint32 GEO name pointer (relative to header). This is the name of the geometry vertex file the bones are for, <stem>.gpl (or .tpl). Not always NUL-terminated: in 34 vanilla models (10 distinct names, all exactly 12 or 16 characters long) one leftover byte follows the extension before the zero padding, e.g. nokonoko.gpl + B0, L_teresa.gpl + 80, and mii_male.gpl / L_mii_female.gpl + 70 (p, which read as .gplp). 123 other 12/16-character names are terminated normally (survey of 1_Input/dt_na.dat, 2026-10-04). The exporter cuts names after the extension (binfmt.clean_geo_name) for folder names and the .sluggie GeoName; the bytes in the block are never rewritten.
0x14 uint16 Skin file id
0x16 byte[2] Padding
0x18 uint32 User data size
0x1c uint32 User data pointer (relative to header). Points at a chain of descriptors — see User data below.

Bone

Offset Type Description
0x0 uint32 Orientation control pointer (relative to header). This is an SRT that holds the bone's pose position, can be null. A null pointer means an identity local transform. It is mostly found on the extra root bones, but non-root bones have it too (chunk 18 bone 2, chunk 95 bone 1).
0x4 uint32 Previous sibling bone pointer (relative to header)
0x8 uint32 Next sibling bone pointer (relative to header)
0xc uint32 Parent bone pointer (relative to header)
0x10 uint32 First child bone pointer (relative to header)
0x14 uint16 GEO file ID: the index of the GPL submesh this bone draws rigidly. FFFF means the bone owns no mesh (it may still drive skinning of submesh 0 through SKN). See GEO ID ownership rules.
0x16 uint16 Bone ID
0x18 bool Is this bone positioned relative to its parent? (Usually true)
0x19 uint8 Something about draw priority, usually 0
0x1a uint16 Flags. Bit 0x8000 gives the bone two extra matrices multiplied into its draw matrix (set on 15 vanilla bones, never in a stadium); otherwise 0. See How the game builds and draws the bones.

Section layout and tree links

Surveyed over all 203 models with an ACT section (2026-09-18). The layout is fully deterministic and has no slack:

0x00                  header (0x20 bytes)
0x20                  bone table: 0x1C x boneCount, in bone id order
0x20 + 0x1C*N         SRT array: 0x34 each, one per bone with a non-null SRT, in bone order
                      geo name, user data, padding (see Section tail)

GEO ID ownership rules

Surveyed across 721 exported models and confirmed in Dolphin (2026-09-16):

The bone table is reached through the tree: the tree header sits at header+0x08 and its root pointer at header+0x0C. Each node address is the bone's start, relative to the ACT header. BlockValidator walks it to check the rules above.

How the game builds and draws the bones

From main.dol (US; function names from the external tool's symbol list, 2026-10-14). Stadiums are built exactly like characters: nothing outside the model block lists their bones. No DOL table (stadium archive, object lists, options, time of day) names bones, and the GPL mesh names do not occur in the DOL.

High- and low-poly pairs share one skeleton

Each player character has a high-poly model and a low-poly L_* model, stored as file indices n and n + 1 of the same chunk. Their ACT geo names match once the L_ prefix and the extension are removed; names are compared after cleaning (see the GEO name pointer above; the Mii pairs read as .gpl/.gplp without it). All 66 vanilla pairs have the same bone count, and 56 have identical parent, track and mirror tables.

The game poses the low-poly model with the high-poly model's bones. Confirmed in Dolphin with Mario (2026-09-27), after appending a bone 91 to one or both models:

The hammerspace patcher checks the pair against the partner's currently patched state. It refuses a patch or unpatch that would leave the low-poly model drawing on a bone the high-poly model lacks. It warns when an appended bone's parent or SRT differs between the two models.

User data

Found while investigating (2026-09-18) what silently counts bones outside the header's own boneCount. The header's user data pointer (0x1c) is null when userDataSize (0x18) is 0 — dozens of models ship no user data at all with a perfectly working bone tree. When present, it points at a chain of descriptors, each read until the header's total userDataSize is consumed:

Offset Type Description
0x0 uint32 Descriptor size in bytes, including this 0x0C-byte header
0x4 uint16 Kind (see below)
0x6 uint16 Count (meaning depends on kind)
0x8 uint32 Data pointer (relative to header)

Three kinds are seen across the corpus:

The kinds are independent: some models carry only a kind-3 table and no mirror table (chunks 130/18, 137/74, 137/139, 142/129). Models without any user data include chunks 5, 7, 9-12, 15, 16, 126-129, 137, 142 and 159.

Every player model's bone table is exactly boneCount entries, ids 0..boneCount-1 in table order with no gaps — the mirror and track tables are the only place a bone count appears outside the header, and this is why extending the bone table (e.g. appending new bones) requires rebuilding kinds 2 and 3 alongside it, while any kind-4 entry can simply be carried through unchanged.

SRT

SRT stands for scale, rotation, transform.

Offset Type Description
0x0 uint8 Type: a component-presence mask, see SRT type byte
0x1 byte[3] Padding
0x4 float[3] Scale (x, y, z)
0x10 float[4] Quaternion stored as (x, y, z, w). Note: when applying this quaternion, negate w and reorder to (-w, x, y, z) to match the game's convention.
0x20 float[3] Translation (x, y, z)
0x2c byte[8] Padding? Don't know why it's here.

SRT type byte

The type byte says which components the game reads. Bit 0x4 means "rotation is live", bit 0x8 means "translation is live". The game ignores any component whose bit is not set. Surveyed over the player corpus (about 4,000 bones with an SRT, 2026-09-19):

Type Bones Non-identity rotation Non-zero translation
04880%0%
481100%0%
814690%62%
121903100%94%

There are no exceptions in either direction. Types with bit 0x1 set (1, 5, 9, 13) have identity scale, rotation and translation everywhere and mean something else. No vanilla bone has a non-unit scale, so no scale bit is known.

So a new bone's type byte must be derived from the values written, never copied from its parent. Found in game: a new bone that copied its translation-only parent's 8 lost its own rotation, and the mesh on it rendered at Mario's chest instead of above his cap, rotated 180 degrees about the bone. The Blender exporter now computes the mask from the bone's rotation and translation, and warns about a non-unit scale.

Appending bones

New bones can be appended to a skeleton as long as nothing about the existing bones changes. Confirmed in Dolphin (2026-09-18 to 2026-09-20):

Why appending is safe: nothing outside the ACT counts bones. Player models have ptr6 = ptr8 = 0, and ptr7 (facial poses) indexes submeshes, not bones. GPL, TEX and SKN have no bone counter. SKN refers to bones by id (and a new bone may be skinned to, 2026-10-12), ANM is looked up by track id (a new bone has track 0xFFFF and is skipped unless it is given one), and the bat/glove attach points in main.dol use bone ids too. Keeping every existing id keeps all of these valid. The one exception is the high-/low-poly pair, see above.

Changing existing topology is not safe, and the result is silently wrong rather than a crash:

Appending a new bone at the end of its parent's child chain adds a link without disturbing any existing one, so it is the only topology change allowed.

New root bones, for stadiums. A new root bone is written with a null parent pointer and linked after the last root in the root sibling chain, which starts at the header's root pointer (+0x0C). That adds a link at the end of an existing chain, like a leaf append. The header word at +0x08 is left alone: across all exported models it is 0xC or a stale-looking address, and the game overwrites it with 4 at load (see How the game builds and draws the bones). act_rebuild.append_leaf_bone(parent_id=None) builds this layout. Meshes on new root bones draw correctly in Yoshi Park and Daisy Cruiser (Dolphin, 2026-10-18; see GEO ID ownership rules). The patcher takes new root bones only in stadium models (dirs 7-16), whose bones are all roots, and refuses them elsewhere.

The hammerspace patcher enforces this before writing:

Section tail and alignment

The ACT section starts on a 32-byte boundary, like every section (see overview). Its inside has no single alignment formula. The geo-name strings follow the SRT blob directly, with no reserved null-terminator byte in the layout. The padding after the names and user data, up to the section end, varies per donor. It depends on the section that follows ACT in the model block, not on anything inside ACT (185 player models rebuilt byte-identical this way).

act_rebuild therefore recomputes the bone table and SRT blob, which shift when the bone count changes. It carries the name/tail region through verbatim and leaves section alignment to BuildHEADERModelBlock.