Animation: clips, behaviour graphs and the runtime
The engine's own animation system (Havok Animation and Behavior are not used): the .anix clip format and its codecs, where a loaded clip lives and how events are pre-parsed, which characters can play a clip, the .bvr behaviour graphs, and the per-frame runtime from the behaviour state through clip trackers to the pose job on the animation worker threads.
The engine's own system
Animation is written by Monolith, not Havok. The executable carries run-time type information for
exactly one Havok animation class, hkaSkeleton (kept for cloth deformation), and for no Havok
Behavior class at all; the many hka* and hkb* names elsewhere in the file are Havok's
serialization name table, which survives without the code. The engine's own animation sources are
named in its strings: AnimNodeLink.cpp, ThreadAnimNodeLink.cpp, AnimationMgr.cpp,
BehaviorData.cpp, CharacterBehaviorInterface.cpp, SimpleModel.cpp and
FacialAnimationMgr.cpp.
Clips: the .anix format
header 'ANIX' | 0x2C | 0x30 | file size | track count | track table offset | block chunk offset |
block chunk size | anim chunk offset | anim chunk size | 1 | 1
tracks run-length runs {channel hash, run length, node hashes}: rotation 0x27436396,
position 0x00CE66FC, events 0x210C8F5A
anim +0x00 directory offset (0x10), +0x04 clip count, +0x0C the EVENT DICTIONARY offset;
chunk directory {u32 name hash, u32 relative offset}; records are not in directory order
record name[0x30] | +0x30 (flag << 16) | (0x7C + 16 * blocksets + 8 * named) | +0x34 blocksets |
+0x36 named | +0x38 groups | +0x3A tracks | +0x3C root summary: start position, start
rotation (x, y, z, w), end position, end rotation | +0x74 u32 | +0x78 duration (ms) |
+0x7C codecs (4 characters per group), group of each track (u8), remap (u16 per track),
blockset table {offset, size, t0, t1}, named table {hash, offset}, the KeyString blob
blockset u32 named count, u16 group offsets, u16 named offsets, align 4, u32 LITH 0x2B99DBC1
LITH {keys, n2, n3, times[keys], 0}
| Codec | Content |
|---|---|
| SQ03 | constant rotations: 42-bit smallest-three, three per 16 bytes |
| AQ03 | animated rotations with their own key times (keys may skip frames where motion is smooth) |
| SV01 / AV01 | constant / animated vectors |
| UATR | position polylines: a u8 segment and a u8 weight per key, then a 4-aligned float3 palette |
| UAQT / UAVT | keyed rotations / vectors |
| STRI | event strings (offsets into the KeyString blob) |
- A record ends at the next record by position, the event dictionary or the end of the chunk, not at the directory's next entry.
- Group and LITH offsets inside a blockset are u16, so a blockset holds at most 64 KB; long clips use several blocksets that share their boundary key, with absolute LITH times, an AQ03 block header of (start time, end time, keys) and one STRI group per blockset.
- Unused SQ03 slots must hold a valid quaternion: zero bits decode to a negative value under the square root, and the vectorised SQ03 evaluator asserts. Shipped files never leave them zero.
- The clip root is the character's position, about 1 m above the feet (the same reference as the
live position at
CCharacter+0xAC). - One path can be several different files:
pl_sync_chordkill_front.anixships as a 466,997-byte loose entry and as a 464,227-byte bundle section; the game uses whichever its region loaded.
A loaded clip
resource table [base+0x2A86758] + (hash(path) & 0x7FF) * 0x18; resource +0x08 = a 0x28-byte entry
{+0x00 handle vt_0x1DB4508, +0x08 the anim object, +0x18 3, +0x24 index}
anim object vt_0x1E6F528 (constructor 0x57E4F4, 0x88 bytes); +0x30 the data object,
cached +0x38 anim chunk and +0x68 block chunk (0x57E5A8)
data object 0x28 bytes (loader 0x480AEC): +0x00 block chunk (16-aligned) +0x18 size,
+0x08 anim chunk +0x1C size, +0x10 node / track pairs +0x20 count
anim registry [base+0x26FEFF8] + 8 + (hash(path) & 0x3FF) * 0x18, node = object + 0x10
The samplers (0x260740, 0x261360) re-read these pointers and the clip record on every sample, so a change to a clip's values in memory plays on the next frame. The track-to-bone binding is a vector per skeleton indexed by the file's track index and built at load, so a clip with a different track layout cannot replace a loaded one in place. A region change reloads the region's animation files.
Events are pre-parsed. When an anim object attaches, 0x4B9E58 parses the file's event
dictionary (the anim chunk's +0x0C: a path, clip names, every event string, then each clip's
events) into per-clip objects in an arena: anim object +0x40 arena / +0x48 size, +0x50 .. +0x58
a vector of {u32 hash(clip), object*}; a clip object holds +0x10 frames, +0x14 count, +0x18
duration (ms), +0x20 a u32 frame per event (+0x28 count), +0x30 the events as (string index
<< 24) | ms with 0xFF meaning no text (+0x38 count), and +0x40 the strings (+0x48 count).
Playback reads these tables, never the STRI tracks.
Which characters can play a clip. A character plays a clip by name from its skeleton family's
clip table (0x302540): [[[[char+0x110]+0x1C0]+0x50]+0xB0] (vt_0x1E6F3F8). A table takes clips only
from files its member list names (+0x78 / +0x80, 0x38-byte entries: +0x20 the path, +0x28
the anim object while loaded); the orc list names 3,363 paths and the olog's 2,268. A file at a path
the list does not name registers nowhere, even in a listed folder; a listed path that ships in no
archive (2,107 of the orc list's) is an unused slot. Loading a bundle registers its clips, unloading
unregisters them. The orc, Gondorian and Easterling families share many entries (one anim object
for orc_deathtaunts.anix in all three); the olog's list is separate. Six tables served 47 spawned
characters in one scene.
Armour shoulder-pad helper bones (L/R_SPadX) are keyed at half of the arm's swing (correlation 0.99
- 1.00 on five sprint clips), so they follow the arm by design data, not by a constraint.
Behaviour graphs (.bvr)
A .bvr is a GameDB database whose root type is BlendDB. Its categories are node types
(BlendState, StateTransition, Animation.SimpleAnimation, Predicate.And, Modifier.* ...);
its records are the graph's nodes, mostly named secondary records, with inline structures
(PotentialFrame, AnimPoint, ValueModifier, AnchorData, TrajectoryPoint) as primaries. The
player graph has 139,198 records in 270 categories: 46,400 structures, 22,229 state modifiers,
21,128 predicates, 15,704 transitions, 7,561 logic nodes, 6,454 camera modifiers, 3,750 states,
3,069 animations and more.
| Record | Holds |
|---|---|
BehaviorSource |
the root: StartState, DefaultWeightset, ModifierTypes, PreviewModel, TrackingStates, reset-to-default states |
BlendState |
one animation state: AnimationLink (the animation tree), Links (outgoing transitions) with a normalised-time window each and a minimum character LOD; Modifiers; Rate, DefaultBlendTime, BlendInterpolationMethod, Looping, Mirrored, gravity and foot / hand IK, movement / rotation / collision scale, NativeState, AIPosture, Priority |
CameraBlendState |
a camera state: CameraModifiers and a predicate |
StateTransition |
an edge: target node, predicate, blend time, window, sync, mirroring, interpolation |
The 29 graphs hold 10,702 blend states and 73,448 transition links. The four player graphs
(player_elf, el2, elt, bar) have about 2,050 states and 1,700 camera states each; the warg 431, the
troll 218, the five orc class graphs 132-136, and smaller graphs serve the Easterlings, ologs,
humans, the AI Talion, the fellbeast, Sauron's war form, the Nazgul, the Balrog, Gollum, goblins and
spiders. 73,017 links resolve to a state, 431 have no target and none names a missing state. 5,300
states are reachable by transitions from the start state; the rest are entered by code (grabs,
riding, ladders, mount deaths: the states that synchronised attacks and presentations force).
All 17,606 clip references of the states resolve to a clip in some .anix (the .skel
animation-set paths the nodes name ship nowhere, so the join goes through the clips' own names).
A BlendDB record is a native structure. Its header byte +0x01 is 0x1A (flag 0x10) on all
318,372 live records, so its fields sit at instance + SlotOffset(type, field) (SlotOffset 0x251C24,
size step 0x251CAC) instead of the GameDB slot rule, and compiled code reads them as plain members;
by-name getters divert such records to the reflected path (GetFloatByName 0x115B43C -> 0x5DA0FC).
BlendState live layout: Mirrored +0x68, Priority +0x80, DefaultBlendTime +0x88, StartPct
+0x98, Rate +0xA8, Gravity +0x110, Looping +0x118, Links +0x138 ({pointer, count});
editor-only fields take no bytes. Six fields read live equal the disk decode for 10,697 of 10,697
records.
Only two "play as" tiers are authored into the player graph: the tags pool (371 records) holds
PlayAs_Spider and PlayAs_Gollum, and Client/PlayerMovement has exactly five records
(PlayerMovement, PlayerMovement_Alt01, SpiderMovement, GollumMovement, DwarfMovement).
The runtime
The behaviour state of a character
| CCharacter offset | Meaning |
|---|---|
| +0x120 | the behaviour-interface base subobject (constructor 0x45520C; vt_0x1F96C68 on the player, vt_0x1FB3AA8 on NPCs) |
| +0x140 | char* the .bvr path (behaviors\player\player_elf.bvr) |
| +0x578 | the AnimationMgr (= [GM+0x6CB0]) |
| +0x1198 / +0x11A0 / +0x11A8 / +0x11B0 | four BlendState record pointers; +0x11B0 is the current state |
| +0x1258 | the graph's BehaviorSource record |
| +0x1970 | the AnimNodeLink (vt_0x1DCC0C0, constructor 0x456F14, destructor 0x4555B8) |
| +0x1980 / +0x1988 | a shared pointer to one manager-wide object (AnimationMgr+0x65A0) |
The state slots point into the graph's records, not into per-character copies: every value is a
record of one of the loaded BlendState categories (28 loaded graphs, 21,394 records), so all
characters on one graph share them, and a change to a BlendState record reaches every character
using that graph. In play the current state changes as characters move (60 kinds of transition in
20 s on 80 characters: Idle -> IdleToRun, IdleToRun -> RunToIdle, Run -> Stumble -> Run, Idle ->
Wpn_Sheathe, Idle <-> AutoPicker_Fidget ...). +0x11A8 is either null or equal to the current
state (29,520 of 29,520 samples, and 35,882 of 35,882 in a second run); +0x11A0 is set only on the
player (its second machine: button and stick states). +0x1198 is not a previous state: right after
402 state changes it held the old state 0 times, the new state 334 times and was null 61 times. It
is null on exactly the samples where +0x11A8 is null, and otherwise equals the current state, except
on the player, where it can hold the second machine's state: in 164 of 164 samples where it differed
from the current state, the character was the player and the value equalled +0x11A0 (for example
A_Button_JustPressed). The null is passing: 41 of 55 characters showed both null and set within 40 s.
The AnimNodeLink: +0x98 the AnimationMgr, +0xC8 the parsed name of a forced animation, +0xF0
/ +0xF8 a BlendState pair (+0xF0 equals char+0x11A8), +0x230 a serial from the counter
0x2702008, +0x270 a shared pointer to the manager object. The AnimationMgr [GM+0x6CB0] has no
vtable: +0x4158 / +0x4160 count and 0x28-byte handles of the registered characters (+0x20 the
character; every spawned character is registered), and +0x6720 / +0x6728 a pool of 256 objects
(vt_0x1FD99F8, about 0xE0B8 bytes each).
Clip trackers
M = [[char+0x110]+0x1C0] is the character's model animation instance (vt_0x1DA9700; the model
object is vt_0x1F6F950). M+0x08 is a vector of 0xA0-byte trackers, one per active clip;
M+0x50 the skeleton resource; M+0xC8 the pose-job descriptor and M+0xD8 its 20-byte blend
entries (one per tracker); M+0x118 the time source; M+0x128 job submitted, +0x12A needs
evaluation.
| Tracker offset | Meaning |
|---|---|
| +0x02 | flags: bit 0 advanced by the engine clock, bit 1 loop, bit 3 cleared on wrap |
| +0x04 | u16 delay counter (0xFFFF = none) |
| +0x08 | the clip name's hash |
| +0x0C | current key index |
| +0x10 | rate (time += (dt + remainder) x rate) |
| +0x14 | weight |
| +0x18 | sub-millisecond remainder |
| +0x1C | playback position in ms |
| +0x24 | weight-set index (-1 = whole body) |
| +0x44 | key count |
| +0x4C | duration in ms (equal to the clip record's +0x78 in 100 of 100) |
Tracker 0 is always the clip base (the model's base pose). Clips layer per body part rather than
cross-fade: a player at a fidget state carried base plus PL_Base_Idle_B (pl_fidgets.anix) and
two pairs of fist idle clips, all at weight 1.0 with different weight sets. Most trackers have rate 0
and are not engine-advanced: the graph writes their time. In play, tracker time advanced at the world
clock's rate (median 1.0 ms per world ms over 42,123 consecutive same-clip pairs; 731 loops or
restarts).
Per frame
main thread CGameClientShell vf1 0x1A864C -> model manager vt_0x2301178 vf1 0x1A83AC -> vf14 0x1A8490
-> per model and phase 0x187A38 -> 0x1760A8:
1. 0x1760F0 advance: per tracker 0x17620C (skip unless flag bit 0 and rate != 0);
0x17674C wraps a looping clip or holds the last key; dt from M+0x118 or the
world clock
2. 0x176348 submit: 0x175F04 copies weight, weight set, time and clip of each tracker
into M+0xD8, then queues the job on ThreadSystem_A [base+0x26FF000] (vf2)
workers EngineAnimationJobThread1-3 (routine 0x114CBA8) take jobs from
AnimJobThreadSystem [base+0x26FF018]+8 -> 0x1B0E60 -> the pose job 0x1B0EE4;
the main thread drains the same queue too (0x7042FC -> 0x1AF430)
pose job 0x1B0EE4 walks the blend entries: the first non-zero weight -> op 0 SAMPLE
0x264E8C -> 0x260740 -> 0x261360; each later one -> op 1 BLEND 0x182980 by its weight;
no clip -> op 2 REFERENCE POSE 0x11B517C; end -> op 3 OUTPUT 0x1B12A4
The operations live in a 16 x 0x20 table at 0x2A86C60 filled by 0x1188844; the dispatcher 0x1B1230 asserts "Animation Operation not registered" for a missing one. In play the pose job also shows up on the main thread (2.7% of its samples).
Console entry points
AnimLink.PlayAnim (handler 0x1BCF20C, CharacterBehaviorInterface.cpp) targets the character named
in its first argument or the player and calls 0x1BDEC34(link = [char+0x1970], name), which parses
the name (0x41758C, 0x41A1E0), stores it at link+0xC8 / +0xD0 and calls 0x41A148. AnimLink.Dump.History
(0x1BDEE0C) finds a registered character by name through 0x1B32410.
Ragdolls
Ragdolls are a death effect. Every character model carries a physics ragdoll, but the powered ragdoll, its brain and the joint tuning from the database are started only by the dead-body step 0x2F8460: the slot claim (0x65C214), the brain start (0x65C274) and the joint setup (0x64D4A4) each have that one caller. Knockdowns and other reactions of living characters are animations.
The dead-body step
The NPC Update runs its dead-body branch, slot 121 (base implementation 0x175020), once the combat
object's dead byte [char+0x26A8]+0x4A0 and the byte char+0x225E are set (see Threads and the frame);
on the frame of death the AI update itself calls the step once. 0x2F8460(state = char+0x1AF0,
character):
- Reads
Combat/Category.RagdollReactionof the killing attack (0x65E990; 11 of 2,559 categories set one) and, when a ragdoll is pending, itsInitalVelocityScaleandForceValue. - Claims a ring slot, then, when the character's model (
[char+0x2B30]) names aCharacter/Models.RagdollConfig, applies that config's joints, enables the ragdoll and starts its brain; otherwise it starts the controller with no config and no brain. - Fires
Combat/Buffs.DeathProjectiles, switches the character toRigidBody, plays the clip named byCharacter/Models/Global.RagdollDeathAnimation(base) on the model and posts the audio hookCharacterSwitchesToRagdoll. - On every later frame: runs the brain (0x30F6D4), then tests for rest. Counting from the state's
time stamp and leaving out the brain's running time, a body is at rest after 15 s, or after 3 s
once its speed falls below
Character/Models/BodyCleanup.AtRestSpeed(15 cm/s without a record). A resting body that is not the player goes to the body cleanup 0x6C00A0 (Lifetime,SinkDistance); otherwise 0x145218 relaxes the hit reaction (RelaxTime, start and end friction and tau factor).
The thresholds are initialised data ([base+0x26736A4] 15.0, [base+0x26736AC] 15.0,
[base+0x26736B0] 3.0, enable bytes [base+0x26736A8] and [base+0x26736C0]), each read only by
this step. The two BodyCleanup records, Troll (15 models) and Fellbeast (2), set
AtRestSpeed to 1,000,000, so those bodies rest at once; the other 264 models use the default.
The per-character body settings are copied from Character/Models/Global when the character is
created (0xFCFB4, from its slot 16), into char+0x1E50: +0x239 BodyRagdoll, +0x218
BodyDeathDirMinY, +0x238 BodyDeathForce, +0x21C BodyDeathForceScale, +0x234
BodyDirDeathForce, +0x220 BodyDirDeathForceScale, +0x228 BodyStickDist, +0x22C
BodyStickTime, +0x230 BodyDeathScaleVelocity and +0x224 BodyDeathClampVelocityMax (shipped:
true, 0.4, true, 0.5, true, 1.5, 400, 0.3, 1.0, 800). A later edit of the record reaches only
characters created afterwards.
| Offset from the character | Content |
|---|---|
+0x110 -> +0x1C8 |
the physics ragdoll (below) |
+0x1AF0 |
dead-body state: +0x00 phase (0 while alive), +0x308 the killing attack, +0x310 the BodyCleanup record, +0x320 a time stamp (f64), +0x350-+0x35D flags |
+0x1AF8 |
the ragdoll controller |
+0x1E50 |
body settings (above) |
+0x2C17 |
u8, set on a corpse |
The physics ragdoll
[model+0x1C8] (26 of 26 characters read):
| Offset | Content |
|---|---|
+0x00 |
pointer to the shared definition of the skeleton's ragdoll (one per body family) |
+0x30 |
the character |
+0x38 |
the model |
+0x78, +0x88 |
per-body arrays (count at +0x80, +0x90) |
+0x98 |
joint objects (count at +0xA0) |
Every ragdoll has one body more than joints: orcs, Olog-hai and cave trolls 20 bodies and 19 joints,
humans and wargs 21 and 20. A joint object (vt_0x1DB4980) keeps the powered-angle limits of its
pitch, yaw and roll axes in radians at +0x0C-+0x20 (slots 10-12 convert from degrees), mirror
bytes from +0x24 (slots 13-15) and a motor-disabled byte at +0x28 (slot 8).
The joint setup (entered at 0x64D4A4, which reaches its body 0x64D4B8(ragdoll, config) by a conditional
tail jump) walks the config's Ragdoll/Constraints: joint
[+0x98][Index], motor disabled unless Powered, then each axis's ConstraintAxisData
MinPoweredAngle / MaxPoweredAngle (default -180 / 180) and Mirror. On the 11 corpses read whose
powered ragdoll started, 19 of 19 joints hold exactly the database limits; living characters hold
unconfigured joints (±565.49, 180 × π). The database tuning therefore reaches the physics only at
death.
Ragdoll has two configs, Orc_Base and OlogHai_Base, each a brain and 19 joints (indexes 0-18,
torso to feet). Character/Models.RagdollConfig names Orc_Base on 113 models (humans included,
whose 20th joint is never tuned), OlogHai_Base on one and nothing on 167; models without a config,
such as wargs, still take a ring slot but get no brain.
The ring of eight
The engine runs at most eight powered ragdolls. [base+0x26FFB90] holds eight controller pointers
and [base+0x26FFB88] a cursor. The claim releases the controller in the cursor's slot if there is
one (0x65C528), stores the new controller, writes the slot index into controller+0x2EC and advances
the cursor modulo 8. A released controller keeps +0x2EC at -1 and its joints keep their tuning. In
one read 15 corpses lay in the world and two held slots.
The brain
The controller at char+0x1AF8:
| Offset | Content |
|---|---|
+0x08 |
the character |
+0x10 |
the brain (Ragdoll/Brains) |
+0x18 |
the config |
+0x20, +0x28 |
the current and the prior behaviour controller |
+0x48 |
eight 0x50-byte players |
+0x2C8, +0x2CC, +0x2D0 |
height, speed, average body speed |
+0x2D4-+0x2E8 |
time stationary, moving, on ground, in air, in the current behaviour, in total |
+0x2EC |
ring slot, -1 for none |
The brain step 0x30F6D4 runs only while the controller holds a slot. It updates the samples and the
timers (the brain's OnGround and Stationary float ranges decide which timers run), then walks
Brain.BehaviorControllers in order and takes the first controller whose conditions all pass:
float ranges on height, velocity, average body velocity, time on ground, in air, stationary, moving,
in the current behaviour and in total, spine upright, head severed, include and exclude hints, the
current and prior controller, and the combat state. On a change it records the prior controller,
resets the behaviour timer, picks a BehaviorSet whose RequiredRagdollConfig is the controller's
config, picks one of its behaviours and starts the behaviour's Sequences.
Orc_Base lists ten controllers (DieHeadSeveredController, DieController,
FallingBraceImpactController, TuckAndRollController, FallForwardBraceController,
WritheOnGroundController, PanicLostGroundController, the two leg-kicking controllers and
FallbackController); OlogHai_Base three. A behaviour (Ragdoll/Behaviors, 18) sets strength and
stiffness ranges, joint masks and scalar mappings; a sequence (Ragdoll/Sequences, 16) plays timed
target poses (Clamp, PingPong), and a target pose sets per-joint pitch, yaw and roll. In one
read of eleven corpses with a brain, three ran DieHeadSeveredController, four DieController and
four WritheOnGroundController (one entered from FallForwardBraceController).
Clip keywords
Two keyword tables name the ragdoll controls a clip can carry: at 0x1E658B0 (RAGDOLL, DESTROY,
RESUMERAGDOLL, DISALLOWRAGDOLL, ALLOWRAGDOLL, DELAYRAGDOLL, COLLISION_CHECK_NONE,
COLLISION_CHECK_WORLD_ONLY, COLLISION_CHECK_ALL, GROUNDCHECK, RAGDOLL_STIFFNESS,
NEVER_RAGDOLL, FREEZERAGDOLL, BEHAVIOR_HINT) and at 0x1F9FC40 for sync actions
(SYNCACTION_ALLOWDEATH, SYNCACTION_DELAYDEATH, RESUMERAGDOLL, ALLOWRAGDOLL, RAGDOLL,
DELAYRAGDOLL, STOP_ALIGN), each entry a 32-bit hash and the name (the second table has no
terminator; its seven entries run into a string). The hash is the multiply-by-31 string hash over the
upper-cased, sign-extended bytes: 0x569FF8(rest of the string, seed) computes seed * 31 + c per
character, recursively, and its callers seed it with the upper-cased first character, so a keyword
matches in any case (17 of 17 table entries reproduce). The same hash has 18 callers, among them the
slot-19 method of four classes.
The keywords are not turned into flags when a clip loads: the dead-body step asks the clip at run time.
0x75BB34(character, attack) copies the RAGDOLL and RESUMERAGDOLL entries to the stack and
0x2C5DD8(character, attack) uses the DESTROY entry; both pass the key set to 0x4CAB0C, which walks
the events of the clip the character is playing (when [[char+0x26A8]+0x114] is not -1) and compares
each event name's hash with the keys, or else to 0x2C5E10, which checks the attack's own animation:
the clip named by AnimationData.Name (looked up in the character's set by 0x4C8A0C) or, without one,
the AnimationData.BehaviorNode, where each key is tested by name (0x1BDEA50). The dead-body step
0x2F8460 is the only caller of 0x75BB34 and one of two callers of 0x2C5DD8 (the other is 0x19B6D1C).
It calls 0x75BB34 only while the state byte +0x35A is set, then clears that byte without testing the
result. The DESTROY query gates the step's flags: while +0x353 is set and the killing attack shows
no DESTROY, the step can enter phase 5 (after further checks, among them the attack's
AttackPostData.SuppressDeathAnimGroundCheck) and sets +0x356 when a second query, on the record
0x190E80 returns (a wrapper of the Combat/Attack.MultiHitRecords accessor), finds DESTROY; in
another branch, no DESTROY sets +0x35C and +0x35D. The other entries of both tables have no
direct reader in the code (checked for four of them).
In a sample of 40 clip files
(302 clips, 4,192 events) the ragdoll events are DELAYRAGDOLL 23, RAGDOLL 22 (one with argument
0.25), ALLOWRAGDOLL 15 and RESUMERAGDOLL 14, spelled in any case. Combat/Sync records can start
the source or the target with a delayed ragdoll (StartWithDelayRagdollSource 1 of 1,507,
StartWithDelayRagdollTarget 98).
Open questions
- Which code reads the clip keywords that have no direct reader (
DISALLOWRAGDOLL,DELAYRAGDOLL,FREEZERAGDOLL,BEHAVIOR_HINTand the sync-action table), and why the dead-body step calls theRAGDOLLquery without testing its result. - Which Havok objects stand behind a joint object (it points to objects of vt_0x1DABE88 and
vt_0x22C2EE0), and whether they own the live
hkpRagdollConstraintDatainstances; thehknpRagdollandhknpRagdollKeyFrameHierarchyUtilityclasses ship, with no use traced. - How the animated pose hands over to the ragdoll at the switch (only the
baseclip is replayed), and what the byte+0x46of the physics ragdoll means (set on every corpse and on 7 of 11 living characters). - Which graph node writes the trackers' time, and where the transition in progress and its blend time are held (not in the character's state slots or the link).
- What clears the state slots
+0x1198and+0x11A8for a moment (43% of samples read null). - The role of the AnimationMgr's 256-object pool.
- The hand-off from the pose output to skinning.
- Which names
AnimLink.PlayAnimaccepts (state names, clip names or both). The request builder 0x41A1E0 takes two names: PlayAnim fills the first, which keeps onlyStringHash(name)atlink+0x23Cand setslink+0x262; the second form instead looks the name up as a record of the character's graph category[char+0x11C0](0x7C6F20) and keeps the record atlink+0x248. The request is then posted to the animation side (0x1159E8C), whose use of the hash is not traced.
Shadow of War (Steam), game version 1.21. An unofficial fan research project, not affiliated with or endorsed by Warner Bros. Games, Monolith Productions or Middle-earth Enterprises. Middle-earth: Shadow of War belongs to its owners.