Source: src/public/studio.h in ValveSoftware/source-sdk-2013, version 48, read on
13 November 2026. Sizes are the sums of the declared field types for the 32-bit layout. Every
…index is a byte offset from the start of the record that holds it. A sequence is a named,
blendable group of animations; an animation is the movement of the bones over a number of frames.
Animation descriptions
mstudioanimdesc_t, 100 bytes, at the header’s localanimindex.
| Offset | Size | Type | Name | Meaning |
|---|---|---|---|---|
| 0 | 4 | int32 | baseptr | Offset back to the header, from this record |
| 4 | 4 | int32 | sznameindex | Offset of the name |
| 8 | 4 | float | fps | Frames per second |
| 12 | 4 | int32 | flags | Looping and other flags; see the sequence flags below |
| 16 | 4 | int32 | numframes | Number of frames |
| 20, 24 | 4, 4 | int32 | nummovements, movementindex | Piecewise movement: mstudiomovement_t, 44 bytes |
| 28 | 24 | int32[6] | unused1 | Unused (to be zero when loading older versions) |
| 52 | 4 | int32 | animblock | Animation block that holds the data |
| 56 | 4 | int32 | animindex | Offset of the animation data; non-zero when the data is not in sections |
| 60, 64 | 4, 4 | int32 | numikrules, ikruleindex | IK rules stored in the .mdl (ikruleindex non-zero then) |
| 68 | 4 | int32 | animblockikruleindex | Non-zero when the IK data is in the animation block file |
| 72, 76 | 4, 4 | int32 | numlocalhierarchy, localhierarchyindex | mstudiolocalhierarchy_t, 48 bytes |
| 80 | 4 | int32 | sectionindex | Offset of the mstudioanimsections_t records |
| 84 | 4 | int32 | sectionframes | Frames in each fast-lookup section; 0 if sections are not used |
| 88 | 2 | int16 | zeroframespan | Frames per span (not valid before version 47) |
| 90 | 2 | int16 | zeroframecount | Number of spans |
| 92 | 4 | int32 | zeroframeindex | Offset of the zero-frame data, 0 for none (cleared when converting version 47) |
| 96 | 4 | float | zeroframestalltime | Saved during read stalls (mutable) |
Sections
An mstudioanimsections_t is 8 bytes: animblock and animindex (ints).
With sections the animation is cut into runs of sectionframes frames, each with its own
data, so one frame can be found without reading the whole animation.
Movements
mstudiomovement_t, 44 bytes: endframe (int), motionflags (int, the
motion types), v0 (velocity at the start
of the block), v1 (at the end), angle (yaw rotation at the end of the block’s
movement), vector (movement relative to the block’s initial angle), and position
(relative to the start of the animation; the header marks it with question marks).
Local hierarchy
mstudiolocalhierarchy_t, 48 bytes: iBone (the bone being adjusted),
iNewParent, start, peak, tail, end (floats:
beginning of influence, start of full influence, end of full influence, end of all influence),
iStart (first frame), localanimindex (offset of an
mstudiocompressedikerror_t, see Skeleton) and
four unused ints.
Per-bone animation data
An animation’s data is a chain of mstudioanim_t records, one per animated bone. Each starts
with 4 bytes and is followed by data chosen by the flags.
| Offset | Size | Type | Name | Meaning |
|---|---|---|---|---|
| 0 | 1 | byte | bone | Bone index |
| 1 | 1 | byte | flags | Below |
| 2 | 2 | int16 | nextoffset | Offset from this record to the next; 0 ends the chain |
| 4 | … | data | In this order, as the flags select: the rotation (a raw quaternion, or a value pointer), then the position |
| Flag | Name | Data that follows |
|---|---|---|
| 0x01 | STUDIO_ANIM_RAWPOS | A Vector48, constant over the animation |
| 0x02 | STUDIO_ANIM_RAWROT | A Quaternion48, constant |
| 0x04 | STUDIO_ANIM_ANIMPOS | An mstudioanim_valueptr_t for position |
| 0x08 | STUDIO_ANIM_ANIMROT | An mstudioanim_valueptr_t for rotation |
| 0x10 | STUDIO_ANIM_DELTA | Delta animation |
| 0x20 | STUDIO_ANIM_RAWROT2 | A Quaternion64, constant |
The header’s accessors place the data as follows. The rotation data starts at offset 4. A raw position
follows the raw rotation: after a Quaternion48 if RAWROT is set, after a
Quaternion64 if RAWROT2 is set. With animated data the position value pointer follows the
rotation value pointer if ANIMROT is set, and starts at offset 4 if it is not.
Value streams
mstudioanim_valueptr_t is 6 bytes: offset[3], three shorts, one each for x, y and
z. An offset is from the start of this value pointer to a value stream; 0 or negative means there is none
for that axis. A stream is made of mstudioanimvalue_t entries, a union of two bytes
(valid, total) or one short value. The header declares that union and
nothing more, so the run-length coding that decides which entry is a count and which a value, and how the
bone’s posscale and rotscale are applied, is not described here (see
Not described). The bit layouts of
Vector48, Quaternion48 and Quaternion64 are likewise defined outside
this header.
Sequence descriptions
mstudioseqdesc_t, 212 bytes, at the header’s localseqindex.
| Offset | Size | Type | Name | Meaning |
|---|---|---|---|---|
| 0 | 4 | int32 | baseptr | Offset back to the header, from this record |
| 4 | 4 | int32 | szlabelindex | Offset of the label |
| 8 | 4 | int32 | szactivitynameindex | Offset of the activity name |
| 12 | 4 | int32 | flags | Looping and other flags, below |
| 16 | 4 | int32 | activity | Initialised at load time to the game’s values |
| 20 | 4 | int32 | actweight | Activity weight |
| 24, 28 | 4, 4 | int32 | numevents, eventindex | mstudioevent_t, 80 bytes |
| 32 | 12 | Vector | bbmin | Per-sequence bounding box, lower corner |
| 44 | 12 | Vector | bbmax | Upper corner |
| 56 | 4 | int32 | numblends | Number of blended animations |
| 60 | 4 | int32 | animindexindex | Offset of an array of shorts, groupsize[0] × groupsize[1] long, each an animation index; the entry for (x, y) is y × groupsize[0] + x, and x and y are clamped to the group size |
| 64 | 4 | int32 | movementindex | Offset of a float array, one per blend, for blended movement |
| 68 | 8 | int32[2] | groupsize | Size of the blend grid |
| 76 | 8 | int32[2] | paramindex | Pose parameters that drive the blend (X, Y, Z, XR, YR, ZR) |
| 84 | 8 | float[2] | paramstart | Local (0 to 1) starting value |
| 92 | 8 | float[2] | paramend | Local ending value |
| 100 | 4 | int32 | paramparent | Parent parameter |
| 104, 108 | 4, 4 | float | fadeintime, fadeouttime | Ideal cross-fade times (0.2 by default) |
| 112, 116, 120 | 4 each | int32 | localentrynode, localexitnode, nodeflags | Transition nodes at entry and exit, and the transition rules |
| 124, 128 | 4, 4 | float | entryphase, exitphase | Used to match the entry and exit gait |
| 132 | 4 | float | lastframe | Frame that should generate the end-of-sequence event |
| 136 | 4 | int32 | nextseq | Auto-advancing sequence |
| 140 | 4 | int32 | pose | Index of the delta animation between the end and nextseq |
| 144 | 4 | int32 | numikrules | Number of IK rules |
| 148, 152 | 4, 4 | int32 | numautolayers, autolayerindex | mstudioautolayer_t, 24 bytes |
| 156 | 4 | int32 | weightlistindex | Offset of one float per bone |
| 160 | 4 | int32 | posekeyindex | Offset of floats read as iParam × groupsize[0] + iAnim |
| 164, 168 | 4, 4 | int32 | numiklocks, iklockindex | mstudioiklock_t, 32 bytes |
| 172, 176 | 4, 4 | int32 | keyvalueindex, keyvaluesize | Key-values text; a size of 0 means none |
| 180 | 4 | int32 | cycleposeindex | Pose parameter used as the cycle index |
| 184, 188 | 4, 4 | int32 | activitymodifierindex, numactivitymodifiers | mstudioactivitymodifier_t, 4 bytes: sznameindex |
| 192 | 20 | int32[5] | unused | Unused; the header comment says they grow back to 8 ints on a version change |
Events
mstudioevent_t, 80 bytes.
| Offset | Size | Type | Name | Meaning |
|---|---|---|---|---|
| 0 | 4 | float | cycle | Point in the sequence |
| 4 | 4 | int32 | event | Event number |
| 8 | 4 | int32 | type | Event type; the header defines NEW_EVENT_STYLE as 1 << 10 and calls the old event system one to phase out |
| 12 | 64 | char[64] | options | Option string |
| 76 | 4 | int32 | szeventindex | Offset of the event name |
Auto layers
mstudioautolayer_t, 24 bytes: iSequence (int16), iPose (int16),
flags (int32), and the floats start, peak, tail,
end (beginning of influence, start of full influence, end of full influence, end of all
influence).
Flags
| Where | Values |
|---|---|
Sequence and animation flags | 0x0001 STUDIO_LOOPING, ending frame the same as the start; 0x0002 STUDIO_SNAP, no interpolation from the previous animation; 0x0004 STUDIO_DELTA, adds to the base sequences instead of blending; 0x0008 STUDIO_AUTOPLAY, always plays; 0x0010 STUDIO_POST; 0x0020 STUDIO_ALLZEROS, no real animation data; 0x0040 unused; 0x0080 STUDIO_CYCLEPOSE, cycle from a pose parameter; 0x0100 STUDIO_REALTIME, cycle from a real-time clock; 0x0200 STUDIO_LOCAL, has a local context sequence; 0x0400 STUDIO_HIDDEN, hidden in default views; 0x0800 STUDIO_OVERRIDE, a forward-declared empty sequence; 0x1000 STUDIO_ACTIVITY, activity index updated at run time; 0x2000 STUDIO_EVENT, event index updated at run time; 0x4000 STUDIO_WORLD, blends in world space |
Auto layer flags | 0x0010 STUDIO_AL_POST; 0x0040 STUDIO_AL_SPLINE, ramp in and out is a spline instead of linear; 0x0080 STUDIO_AL_XFADE, pre-bias the ramp for a weight other than 1; 0x0200 STUDIO_AL_NOBLEND, always blends at 1.0, ignoring weight; 0x1000 STUDIO_AL_LOCAL, a local context sequence; 0x4000 STUDIO_AL_POSE, blends using a pose parameter instead of the parent cycle |
Pose parameters, transitions and included models
A pose parameter, mstudioposeparamdesc_t at localposeparamindex, is 20 bytes:
sznameindex (int), flags (int; the header’s comment is “????”), start,
end and loop (floats; loop is the looping range, 0 for no looping, 360
for rotations).
The transition graph is numlocalnodes × numlocalnodes bytes at
localnodeindex, read as from × numlocalnodes + to by the header’s
pLocalTransition accessor, and an array of name offsets (from the file start) at
localnodenameindex.
Included models, mstudiomodelgroup_t at includemodelindex, are 8 bytes:
szlabelindex (a text name) and sznameindex (the file name), both from the start of
the record. They name external animation and model files (the header’s comment). The
animation block table, mstudioanimblock_t (8 bytes: datastart,
dataend), at animblockindex, divides the .ani file named by
szanimblocknameindex. Block 0 is not used by the accessor, which asserts an index above 0.