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
What the animation header states
Section titled “What the animation header states”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
Track masks
Section titled “Track masks”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
| Offset | Size | Type | Field | Confidence | Notes |
|---|---|---|---|---|---|
| 0x0 | 1 | u8 | quantization selectors | Sourced | Bits 0–1 position; 2–5 rotation; 6–7 scale |
| 0x1 | 1 | u8 | position component flags | Sourced | Low nibble static; high nibble spline |
| 0x2 | 1 | u8 | rotation component flags | Sourced | Low nibble static; high nibble spline |
| 0x3 | 1 | u8 | scale component flags | Sourced | Low 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
Float tracks
Section titled “Float tracks”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
Little-endian. The count and degree are the head of a transform spline track.
| Offset | Size | Type | Field | Confidence | Notes |
|---|---|---|---|---|---|
| 0x0 | 2 | i16 | count | Proven | control points - 1 |
| 0x2 | 1 | u8 | degree | Proven | 1 (7 blocks) or 3 (157 blocks) |
| 0x3 | points + degree + 1 | u8[] | knots | Proven | frames within the block, not decreasing |
| padded to 4 | 4 | f32 | lowest | Proven | |
| lowest + 4 | 4 | f32 | highest | Proven | |
| highest + 4 | 2 * points | u16[] | control points | Observed | lowest + (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
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.
Non-finite stored floats
Section titled “Non-finite stored floats”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
Evaluated as a B-spline, either span next to the point is NaN in every component.
Trailing zero bytes after the last block
Section titled “Trailing zero bytes after the last block”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
How the stored values are made
Section titled “How the stored values are made”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
Absent components
Section titled “Absent components”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
Independent decoder scope
Section titled “Independent decoder scope”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
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.