Author: HobbitDur

Model animation

More info on OpenVIII

Header (data sub table)

Offset Length Description
0 4 bytes Number of animations
4 nbAnimations * 4 bytes Animations Pointers
AnimPointer Varies Animation

Animation

Offset Length Description
0 1 byte Number of frames
1 Varies Animation frame

Animation frame

Each frame is one vector location and BonesCount*RotationVectorData. Vector location is used to manipulate the model in world space, where all the animation is rotating each bone. The frames work accumulatively, so in order to get the final rotation at in example frame 5, you have to add all rotations from frames 0,1,2,3 and 4. (The location vector deltas accumulate the same way, into the root-translation fields of the skeleton header. Per-bone scale values, on the other hand, are absolute — see below.)

Offset Length Description
0 LocationVectorData LocationVectorData
Varies 1 BIT Mode bit: 1 = every RotationVectorData of this frame carries the per-bone scale channels
Varies RotationVectorData * bonesCount Rotation Vector data per bone

The mode bit is stored into the skeleton’s per-bone-scale flag each frame (the byte at offset 1 of the skeleton header, historically called “Extra data”). It selects, for that frame only, which of the two bone-transform paths the engine uses: rotation-only, or rotation + per-bone scale.

Location vector data

Offset Length Description
0 PositionType Location X
Varies PositionType Location Y
Varies PositionType Location Z

Rotation vector data

Offset Length Description
0 RotationType Rotation X
Varies RotationType Rotation Y
Varies RotationType Rotation Z
Varies ScaleChannel (see below) Bone scale X — only present when the frame’s mode bit is 1
Varies ScaleChannel Bone scale Y — idem
Varies ScaleChannel Bone scale Z — idem

Scale channel (formerly Unk1/Unk2/Unk3)

The three optional 16-bit payloads are per-bone scale factors — FF8’s squash-and-stretch animation system. Each channel is:

Offset Length Description
0 1 BIT Payload present
Varies 0 if not present, else 16 BITs Scale value − 1024

Semantics (all verified in the PC engine):

  • The final scale is payload + 1024 when the payload bit is set, and exactly 1024 when it is not — and 1024 means 1.0 (neutral). So an absent payload is not “no data”, it is “reset this bone’s scale to normal”.
  • Unlike rotations, scale values are absolute per frame, not accumulated.
  • Scale is hierarchical: at transform time a child bone’s effective scale is parent_scale × value / 1024, so scaling a parent squashes its whole subtree.
  • The values land in the bone’s scaleX/scaleY/scaleZ fields (offsets 10/12/14 of the skeleton bone struct); when the frame’s mode bit is 0 the engine takes the rotation-only path and those fields are ignored.

Vanilla usage (from an exact re-implementation of the engine’s bit reader, run over all 143 monster files — 3103 animations, 0 decode mismatches): 22 monsters use the scale channels in 259 animations, values typically 0.7×–1.2×. The users are exactly what squash-and-stretch suggests — gelatinous monsters (Blobra, Gerogero, Abyss Worm, Funguar, Tonberry King, Creeps, Forbidden, Imp, Torama, Gargantua, Abadon, Elvoret, Elnoyle) and machines with pistons/telescoping parts (X-ATM092, BGH251F2 both forms, Mobile Type 8, Droma), plus Odin and Fujin.

So enemies indeed “work without them” — any monster whose animations never set the mode bit — but for the 22 listed monsters these channels are real animation data that a viewer/exporter must apply to reproduce the original motion.

Position type

Position is BIT length. First we read the count of bits to read by reading first 2 bits.

Offset Length Description
0 2 BITs PositionTypeBits
0+0b11 PositionTypeBits Vector axis

PositionTypeBits

PositionTypeBits value Bits to read for one vector axis
0b00 3
0b01 6
0b10 9
0b11 16

Rotation type

Rotation is also BIT length. First read first bit to see, if there’s rotation data. If no, continue. If yes, then read the rotation data similar to Position Type

Offset Length Description
0 1 BIT IsRotationTypeAvailable
0+0b1 2 BIT RotationTypeBits
0+0b100 RotationTypeBits Rotation vector axis (pitch/yaw/roll)

RotationTypeBits

RotationTypeBits value Bits to read for one vector axis
0b00 3
0b01 6
0b10 8
0b11 12

Bitstream details

  • Bits are read LSB-first within each byte.
  • Multi-bit values (position, rotation, scale payloads) are sign-extended from their bit width.
  • The stream is continuous across frames — frames are not byte-aligned; the engine saves its byte+bit position between rendered frames and resumes there.
  • When the entity is Slowed, the engine halves the decoded position and rotation deltas as it applies them (the stream itself is unchanged).

Reference (names & addresses)

FF8_EN.exe:

Name Address Role
Battle_ReadAnimation 0x508F90 Per-frame decoder: location vector, mode bit, per-bone rotations/scales
bitReader 0x5092A0 Reads n bits LSB-first, sign-extends
Battle__ReadPositionType 0x509320 2-bit type + {3,6,9,16}-bit signed value
Battle__ReadRotationType 0x509370 1 presence bit, then 2-bit type + {3,6,8,12}-bit signed value
Battle__ReadBoneScale 0x5093F0 1 presence bit; absent → 1024 (neutral), present → 16-bit payload + 1024
ProcessFieldEntitiesTransformation 0x508C90 Builds bone world matrices; separate rotation-only / rotation+scale paths
positionReadHelper 0xB8B9F0 {3, 6, 9, 16} bit-count table
rotationReadHelper 0xB8B9F4 {3, 6, 8, 12} bit-count table