This research continues a multi-pass effort to decode TNC's compressed .md6anim animation format, and to explain why the soldier model's skeleton does not pose correctly from the already-proven formula while a pistol's does. This pass found that two "candidate decoder" functions flagged by an earlier reviewer belong to a completely separate, independently-shipped animation system called "Geist" (identified directly from the game's own embedded debug strings, not guessed), not to .md6anim itself. No weapon in the game has a Geist identity file, but the soldier does, which plausibly explains the bind-pose mismatch without yet fixing it. Rotation (quaternion) compression was solved in an earlier pass and remains confirmed; a full byte-for-byte decode of a .md6anim animation is still not achieved. Everything here is static disassembly/format research — no game launch was involved and there is nothing to test in-game yet.
Proven
- Rotation ("smallest three" quaternion) compression: fully solved, round-trips 86/86 samples. Dequantizer at
.text 0x54bfa0, quantizer at0x54bdd0. 0x1169530isidCompressedRigAnimation::Compress, part of C++ namespaceGeist050::Mercury(source file.../geist/lib/core/compiler/animation/riganimationcompressed.cpp) — identified from its own embedded debug strings (source path, log tags, aLinear/Scalar/Quat2/Quat4channel-type enum), not inferred from structure.- Zero code calls in either direction between the
.md6animcluster (0x549e70-0x552210) and the Geist cluster (0x1168000-0x116f000), except two shared low-level quaternion primitives (0x54bdd0/0x54bfa0). The two systems are siblings, not one wrapping the other. 0x116d212, the strongest "separate decoder" candidate an earlier pass found, has zero static callers anywhere in.text, no RTTI vtable slot, and shares its only two internal helper calls exclusively withCompressitself — more likely part of the compiler's own machinery (e.g. a keyframe-fidelity check) than a standalone runtime decoder.- Geist is a large, independently-shipped system in the retail content: 2,818
.geistanimfiles, 66.geistidentityfiles (includinggeist/identity/soldier.identity). No weapon has a.geistidentity(0/66 checked) — weapons stay entirely in the plain.md6skl/.md6animworld. - The 10-float TRS record right after
.md6anim's outer header is now precisely decoded: translation (3 floats) + quaternion xyzw (4) + scale (3). - The
"_FRAMESET_"tag occurs twice per file, not once as earlier passes assumed.
Inferred, not proven
- The likely explanation for the bind-pose puzzle: Geist (not
idMD6Anim) builds the soldier's actual runtime skin pose, while.md6skl/.md6animmay serve the humanoid rig for something else (attachment points, a ragdoll proxy, a distant-LOD fallback) where literal bone-length reproduction isn't expected. Motivated by the code separation above but not proven — Geist's own bind-pose/skin-matrix construction code was not found or disassembled. 0x116d212's 4-mode dispatch likely corresponds toCompress's ownLinear/Scalar/Quat2/Quat4channel types, with mode 3 (Quat4) being the one that touches quaternion data — a clean structural match, not confirmed by a trace.
Open / not decoded
- Where inside the (now precisely located)
"_FRAMESET_"regions the per-joint rotation data actually sits. Two sample files have substantial non-zero payload there (75–92% non-zero bytes) and are better decode targets than the "none_additive" sample earlier passes used, which turned out to be a near-empty additive placeholder. - Which function dequantizes translation.
.md6settings, a resource path pattern (md6/geist/<name>/face.md6settings) referenced byCompress's only known caller, is not among the 174 resource types that actually ship in the retail archive index — possibly unused/cut content, not resolved further.
For modders
Nothing here changes what's achievable in a mod today: reusing an existing weapon's full animation set still works (see the weapon-decl report), but no new .md6anim content can be authored or decoded yet. This is pure format research with no mod-facing recipe of its own.