Skip to content

Spline-compressed animation

hkaSplineCompressedAnimation stores transform tracks in aligned blocks, with static, absent and spline-valued components. Block offsets and frame durations come from the Havok object’s metadata. Sourced · HavokLib

Blocks begin on 16-byte boundaries. Each covers maxFramesPerBlock - 1 frame intervals, sharing its boundary frame with the next block. floatBlockOffsets are relative to the block start. Observed

Over the 49,634 spline-compressed animations the two installs’ animation binders hold (10,865 of Sekiro, 38,769 of Elden Ring; the 11 Sekiro tagfiles that name a compendium held in another binder, or are SDK 2015.1, are not among them), the header fields obey these relations to a relative tolerance of 1e-5, every block read ends exactly at its floatBlockOffsets entry, and padding to 16 reaches the next block or the end of the data except in the case under “Trailing zero bytes”. Proven · Sekiro, Elden Ring · over the 51,478 blocks read

Field Relation
endian 0
maxFramesPerBlock between 1 and 256
frameDuration duration / (numFrames - 1)
blockDuration frameDuration * (maxFramesPerBlock - 1)
blockInverseDuration 1 / blockDuration
numBlocks ceil((numFrames - 1) / (maxFramesPerBlock - 1))
blockOffsets strictly ascending, each a multiple of 16
maskAndQuantizationSize 4 * numberOfTransformTracks, the size of the mask area (4 bytes a track) the walk finds in every block
transformOffsets, floatOffsets empty arrays
numberOfFloatTracks 0 (the cutscene binders’ animations with float tracks have 1, see Float tracks)

The last three rows hold in all 49,634 animations. maskAndQuantizationSize equals 4 * transform tracks + float tracks rounded up to a multiple of 4, which is the transform-track formula with no float tracks; the per-track offset arrays are therefore never populated, and whether their entries would count bytes or words is not observable in these files. Proven · Sekiro, Elden Ring · over the animations read In the animation binders every animation’s numberOfFloatTracks is 0 and every binding’s float-track list is empty, so no clip there drives a float slot; the cutscene binders’ do, below. Proven · Sekiro, Elden Ring · over the animations read

The 11 Sekiro tagfiles left out above read the same way: ten whose compendium another binder holds, and the SDK 2015.1 clip, together 11 spline-compressed animations of 1,286 transform tracks in 12 blocks. In them maskAndQuantizationSize is 4 * numberOfTransformTracks, every spline degree is 1 or 3, no track’s rotation selector is 0, 3 or 4, every block ends where the next begins and no bytes follow the last. Proven · Sekiro · over the 11 tagfiles

All track masks precede track data. Each track then stores position, rotation and scale data, with padding to four bytes after each; the block ends on 16-byte alignment. Sourced · Smithbox

Transform track mask4 bytes0% known
ProvenObservedSourcedInferredUnknownNot described
OffsetSizeTypeFieldConfidenceNotes
0x01u8quantization selectorsSourcedBits 0–1 position; 2–5 rotation; 6–7 scale
0x11u8position component flagsSourcedLow nibble static; high nibble spline
0x21u8rotation component flagsSourcedLow nibble static; high nibble spline
0x31u8scale component flagsSourcedLow nibble static; high nibble spline

A vector spline stores a control-point count and degree, byte knot values, component bounds and static values, then quantized control points in XYZ component order. Static-only vectors store floats. Rotation tracks store aligned quantized quaternions, with knots and degree when spline-valued. Sourced · Smithbox

Selector Scalar quantization Confidence
0 8-bit values between the component bounds Sourced · Smithbox
1 16-bit values between the component bounds Sourced · Smithbox
Selector Quaternion encoding Alignment Confidence
0 Polar32 4 bytes Sourced · Smithbox
1 ThreeComp40 1 byte Sourced · Smithbox
2 ThreeComp48 2 bytes Sourced · Smithbox
5 Uncompressed four-float quaternion 4 bytes Sourced · Smithbox

The external decoder’s format model also names ThreeComp24 and Straight16 as selector values 3 and 4. Sourced · Smithbox Over the 5,240,225 transform tracks of the 49,634 animations (1,348,357 in Sekiro, 3,891,868 in Elden Ring), counting the selector of every track’s mask whether its rotation is stored or absent, selector 1 occurs 5,199,442 times (Sekiro 1,317,639; Elden Ring 3,881,803), selector 2 39,263 times (30,718; 8,545), selector 5 1,520 times (Elden Ring only), and selectors 0, 3 and 4 never. Proven · Sekiro, Elden Ring · over the tracks read

Every spline track of every part (position, rotation, scale) has degree 1 or 3: Sekiro 578,820 tracks of degree 1 and 445,248 of degree 3, Elden Ring 1,562,834 and 1,436,343 in the animation binders; the cutscene binders add 2,750 and 5,430 (Sekiro) and 153,042 and 82,981 (Elden Ring). Degrees 2 and 4 never occur, so the stored degree byte spans 1 to 3 in these files. Proven · Sekiro, Elden Ring · over the tracks read

The cutscene binders (cutscene/*.cutscenebnd.dcx, 705 Sekiro and 9,856 Elden Ring spline animations, 17,014 and 267,433 transform tracks) use one selector each: every Sekiro track selects 1 (6,613 store a rotation spline, 8,157 a static rotation, the rest none) and every Elden Ring track selects 5 (95,255 splines, 132,031 static, the rest none), so Elden Ring’s cutscene rotations are stored as four floats. Over both kinds of binder, the 60,209 spline animations (5,526,279 transform tracks, absent rotations included) of every tagfile of both installs, selectors 0, 3 and 4 never occur, and every block of selector 1, 2 and 5 reads to exactly the place the header says. Proven · Sekiro, Elden Ring · over the tracks read

Selector 0 (Polar32) therefore never appears in the examined clips of either game, and nothing here exercises its layout or the scale of its angle. Proven · Sekiro, Elden Ring · over the tracks read

Spline animations with float tracks occur in the cutscene binders only: 66 of Sekiro’s 705 and 542 of Elden Ring’s 9,856 cutscene spline animations, in 1,110 blocks. None of the 60,209 spline animations of every tagfile in both installs has a float track anywhere else. Proven · Sekiro, Elden Ring · over every tagfile of both installs

Each has exactly one: numberOfFloatTracks is 1, the binding’s float-track list is [0], and the skeleton the binding names has one float slot, camNNNN_FOV (cam0010_FOV, cam0120_FOV, …), whose reference value is 0. It is the field of view of a cutscene camera; the animation’s one transform track is the camera’s. Proven · Sekiro, Elden Ring · over the 608 animations The values lie between 0.12 and 1.75 in the 1,110 blocks, which reads as radians. Inferred

In a block the mask area holds one mask byte for each float track straight after the transform masks, then padding to maskAndQuantizationSize, which is 4 * transform tracks + float tracks rounded up to 4 in all 60,209 animations; the padding bytes are zero in all 1,110 blocks. The float data begins at floatBlockOffsets, where the transform data ends (this holds for all 67,673 blocks), and ends where the padding to 16 reaches the next block or the end of the data: no block with float tracks is followed by more bytes, so the 16 zero bytes of the rule below do not occur there. Proven · Sekiro, Elden Ring · over all blocks

Mask byte Float track Blocks (Sekiro, Elden Ring) Confidence
0x03 one f32 for the whole block 946 (87, 859) Proven · Sekiro, Elden Ring · layout read to the exact block end in all 946
0x12 a spline of 16-bit control points 164 (12, 152) Proven · Sekiro, Elden Ring · layout read to the exact block end in all 164

No other mask byte occurs. Read as bits, 0x01 is static, 0x10 spline and 0x02 a 16-bit quantization (the transform masks put the static flags in the low nibble and the spline flags in the high one), but with two values in the data the split is not tested; whether a mask without 0x02 stores 8-bit control points is not shown. Inferred

Float track, mask 0x12 (a spline)variable100% known

Little-endian. The count and degree are the head of a transform spline track.

ProvenObservedSourcedInferredUnknownNot described
OffsetSizeTypeFieldConfidenceNotes
0x02i16countProvencontrol points - 1
0x21u8degreeProven1 (7 blocks) or 3 (157 blocks)
0x3points + degree + 1u8[]knotsProvenframes within the block, not decreasing
padded to 44f32lowestProven
lowest + 44f32highestProven
highest + 42 * pointsu16[]control pointsObservedlowest + (highest - lowest) * value / 65535

The static track (0x03) is a single f32 and nothing else. A spline’s control points number from 4 to 255 in a block, and its largest value is 65534 or 65535 in each of the 164 blocks, never more, which is what a scale of 65535 gives. Observed · Sekiro, Elden Ring · over the 164 spline blocks Blocks share their boundary frame, so the value at the last frame of a block is the first of the next: the two agree to within 8e-6 over all 502 seams between consecutive blocks of float tracks (33 in Sekiro, 469 in Elden Ring). Over the 99 seams where a spline meets a spline or a static value, the scale 1/65535 gives a smaller mean seam error (9.7e-7) than 1/65534 (1.3e-6) or 1/65536 (2.5e-6). Observed · Sekiro, Elden Ring · over the 502 and 99 seams Eight-bit control points do not fit: read that way, 118 of the 164 blocks would not end where the next begins. Proven · Sekiro, Elden Ring · over the 164 blocks

What pads between two float tracks?

Every animation with float tracks has exactly one, so the alignment of a second float track’s data after the first is not shown. The same holds for the meaning of a mask byte without the 0x02 bit.

A stored float can be NaN or infinite where the value is a quantization bound or a static component, and, in an uncompressed rotation (selector 5), in a control point. Over the 60,209 spline animations, Elden Ring’s animation binders hold 3 position vectors and 7 scale vectors with a non-finite value or bound, its cutscene binders 250 scale vectors, and Sekiro’s binders none. Two Elden Ring cutscene animations store a rotation control point of four quiet NaNs (0x7FC00000 each): s12_05_0020 cut0040 (control point 180 of 249, block 0) and cut0060 (control point 36 of 256, block 1), both in track 190 of 225, the rotation of the R_Wing_12 support bone of the skeleton c8905_0000. The other control points of those splines are finite unit quaternions, and the block parses to the exact place the next begins, so the point is stored data, not a misread. Proven · Elden Ring · over the 60,209 animations

What does the game do when a rotation spline blends a NaN control point?

Evaluated as a B-spline, either span next to the point is NaN in every component.

only in Elden Ring The data of 4,975 of the game’s 38,769 spline animations ends 16 zero bytes after the aligned end of its final block. The extra bytes occur after the final block only: none of the 1,337 blocks that are followed by another block has them, including 181 whose end has the same geometry. They are present exactly when the last transform track’s rotation mask selects ThreeComp40 (selector 1) and the end of that track’s rotation, taken where the rotation is stored or would be stored when it is absent, lies less than 3 bytes before the 16-byte-aligned end of the block. That holds for all 4,975 and for none of the other 33,794 final blocks. Proven · over the animations read

The rule reads as a guard so that an 8-byte read starting at a 40-bit rotation stays inside the data: a five-byte quaternion ending within 2 bytes of the aligned end would otherwise let the read run 3 bytes past it. Inferred

only in Sekiro The same geometry occurs in 1,243 of the 10,865 final blocks, and none is followed by extra bytes; the 2016.2 data ends at the aligned end. Proven · over the animations read

What the stored values show of the encoder that made them, over the 60,209 spline animations of every binder of both installs (Sekiro 11,581, Elden Ring 48,628).

Every byte is accounted for. Laying out the values each block stores (masks, knot bytes, bounds, quantized control points, packed quaternions, float tracks) by the rules on this page, with zero for every padding byte and the 16 guard bytes where the rule above puts them, gives back the data, blockOffsets and floatBlockOffsets of all 60,209 animations byte for byte; so every padding byte, the tail of the mask area included, is zero. Proven · Sekiro, Elden Ring · over the 60,209 animations

The header is f32 arithmetic. In all 60,209, maxFramesPerBlock is 256, and the three durations are these expressions bit for bit, each operation rounded to f32: frameDuration = duration / (numFrames - 1), blockDuration = frameDuration * (maxFramesPerBlock - 1), blockInverseDuration = 1 / blockDuration. Proven · Sekiro, Elden Ring · over the 60,209 animations

Knots are clamped frame numbers. Every spline’s knot bytes begin with degree + 1 zeros and end with degree + 1 equal bytes, which in every transform track’s spline are the block’s last frame; no interior knot repeats more than three times, and only degree 3 splines repeat one at all. Proven · Sekiro, Elden Ring · over the 60,209 animations That is the shape an error-bounded global approximation leaves (The NURBS Book, algorithm A9.10: knots removed from the polyline through every frame while each frame stays within a tolerance, the degree raised one step at a time, every knot gaining a copy at each step), with degree 1 where degree 3 would need more control points than the block has frames. Inferred A fit of that kind, given each animation’s decoded frames with a tolerance of 0.001 on every part, writes the stored header of all 60,193 animations that store no NaN, and the stored data of 892 of Sekiro’s and 4,554 of Elden Ring’s byte for byte. Observed · Sekiro, Elden Ring · decoded frames re-encoded

Flag bytes. A spline rotation’s flag byte keeps static bits beside its spline bits: 45,839 of Sekiro’s 852,415 spline rotations and 126,953 of Elden Ring’s 2,382,563 have a non-zero low nibble (0x5a, x and z spline with y and w static, 11,076 and 17,158 times), though the whole quaternion is stored as a spline. No position or scale flags the w lane, and no component is flagged both static and spline. Proven · Sekiro, Elden Ring · over the 60,209 animations

Positions and scales are 16-bit. Every track of both games selects 16-bit control points for its position and its scale (code 1 in bits 0-1 and 6-7 of the quantization byte). Proven · Sekiro, Elden Ring · over the 60,209 animations A spline lane’s integers (a position, scale or float track spline component) run from 0 to 65535 or 65534: the lowest control point is stored as 0 in every lane, and the highest as 65534 in 147,207 of Sekiro’s 391,016 lanes and 678,375 of Elden Ring’s 1,944,817 (as 65535 in the others; lanes whose two bounds are equal, or not finite, are left out). Proven · Sekiro, Elden Ring · over every spline lane Rounding could not give 65534 for the highest point, so the integers are truncated; and the quotient that reaches 65534.99… instead of 65535 points to a division by an approximate reciprocal. Computing trunc((v - min) * r * 65535) with r one Newton step from an x86 processor’s rcpss estimate of 1 / (max - min) gives the stored top integer of 99.84% of Elden Ring’s lanes and 76.9% of Sekiro’s. Observed[over every spline lane]{games=“sekiro elden-ring”} Processors differ in that estimate, which would account for Sekiro’s lower share. Inferred

A packed NaN. 1,006 ThreeComp40 control points (108 in Sekiro’s animation binders, 898 in Elden Ring’s) store the five bytes 00 80 00 00 80: bit 39, outside the 39 bits the format uses, is set, and the three 12-bit components are 0, 8 and 0. Proven · Sekiro, Elden Ring · over every 40-bit quaternion That is what a quaternion of NaNs gives when each component is truncated to a 32-bit integer (NaN becoming the most negative one), offset by 2047, saturated to 16 bits and packed; normalising a control point of length zero gives such NaNs. Inferred Decoded, the point is three components of about −0.707 and a fourth of 0, not a unit quaternion. Proven

An absent component has neither static nor spline flags. The neutral interpretation is zero translation, identity rotation and unit scale, rather than the bone’s reference transform. Inferred

For Sekiro’s player idle, read-only live-memory comparisons distinguish these possibilities on 12 bones. All 12 are closer to neutral decoding, with residuals about 1e-6..1e-5, versus 5e-5..3e-4 for reference-pose fallback. Other bones retain unexplained runtime pose changes. Observed · Sekiro

Across the decoded Elden Ring sample, absent components also occur frequently on bones with non-neutral reference values, supporting the same fallback without a direct runtime pose comparison. Inferred · Elden Ring

Sampled spline values agree with a second decoder derived from HavokLib, including fractional frames. This checks implementation agreement; shared format assumptions remain shared assumptions. Observed

Smithbox’s decoder agrees on 609 Elden Ring animations: over 1,979,936 values no component differs in whether it is present, and the largest differences are 5e-6 in position, 1.5e-7 in rotation and 1.4e-6 in scale. Observed · Elden Ring The two decoders share one lineage (HavokLib, then Smithbox’s copy, then ours), so the agreement checks the implementation, not the format; and since no track of either game selects Polar32, it cannot have exercised that selector. Inferred

The all-character Sekiro scan explicitly identifies c1090/hkx/a000_205000.hkx as SDK 2015.1, outside the 2016.2 layout, rather than treating it as a malformed 2016.2 clip. Observed · Sekiro Its own type table describes the 2015.1 layouts, whose hkReferencedObject-derived classes are 8 bytes smaller than 2016.2’s; read with that table it decodes as an ordinary clip (Havok tagfiles). Observed · Sekiro

What happens outside a clip’s stated time range?

Independent decoders differ on clamping versus extrapolation beyond the final frame. Agreement inside the clip does not establish engine behavior outside that range, or the complete animation blending system.