- The player's rules are a Havok Script (Lua 5.1) state machine,
c0000.hks: 15,439 decompiled lines. It fires behaviour events that a Havok Behavior graph maps to clips. - Root motion moves the character: walk 1.5 m/s, run ~4, sprint 6.04. TAE flags switch off gravity, collision, turning and pushing per frame, and open cancel windows.
- The body is a Havok character proxy: a kinematic 1.9 × 0.4 m capsule with a 60° slope limit, a 0.3 m step-up and a downward snap.
- The running jump has no take-off speed. A TAE event ramps the gravity multiplier from 0 back to 1; the height you see is in the pose.
1The pipeline
SetVariable("MoveSpeedLevel", …), ExecEvent("W_RollingMedium"), act(…), reads the engine through env(…)2The state machine is a script
action/script/c0000.hks is compiled Havok Script. DSLuaDecompiler turns it into 15,439 lines, 977 functions
of Lua. Every frame, the graph calls <State>_onUpdate() for each active state. The script reads engine state with
env(id, …), writes graph variables, fires transitions with ExecEvent("W_…"), and calls engine actions with act(id, …).
AttackCommonFunction shows the priority of everything: passive actions, evasion, guard, items, magic, weapon arts, the next attack, weapon change, then movement and quick turns.
We don't run the script. Our engine re-implements the states we need by hand, from the decompiled source, with the same thresholds and event names.
From an event to a clip
ExecEvent("W_RollingMedium") names a behaviour event, not a clip. The graph (c0000.behbnd: 7,026 clip generators, 2,589 FromSoft
CustomManualSelectorGenerators, 1,091 states, 1,267 event names) maps it. Our graph reader lists it all:
| Event | Clip(s) |
|---|---|
W_Idle | a000_000000 |
W_RollingLight / Medium / Heavy | A blend on RollingAngleReal of left, right, front and back clips a000_0271w2-7 (w = weight class). The forward roll 027110 everyone uses isn't in the graph. |
W_BackStepLight / Nomal / Heavy | 027000 / 027010 / 027020 |
W_Jump / W_JumpLong | 027410 / 027400 |
W_FallStart → W_FallLoop | 004050 → 004000 |
W_AttackRightLight1/2/3 | 30000 / 30010 / 30020 with the right weapon's motion category as prefix: a023_030000 |
Each event's transition names its crossfade: 0.1 s for rolls, damage and guard reactions; 1/6 s for jumps; FromSoft's own "DefaultTransition" uses the clip's TAE Blend event (attacks 0.2-0.33 s).
3Locomotion
- The engine sets
MoveSpeedLevelfrom the stick and sprint: 0 idle, ~1 walk, ~2 dash. The blend uses a smoothedMoveSpeedLevelReal:ConvergeValue, a linear approach at +3/s (walk), +4.5/s (dash; the file defines 6, then redefines 4.5) and −3/s down. - Above 1.6 you're dashing: R1 and R2 become dash attacks, and only then can you jump (
SPEED_ENABLE_JUMP). - Sprinting drains 1 stamina every 0.065 s. At empty, SpEffect 100020 caps the stick at a run for 2 s.
- Equip load changes which roll plays, not a speed multiplier.
| Clips | Length | Root speed |
|---|---|---|
walk a000_0200x0, 4 directions | 1.33 s | 1.50 m/s |
run 0201x0 | 0.73-0.80 s | 3.7-4.0 m/s |
dash 0202x0 (front only) | 0.57-0.67 s | 6.04 m/s |
quick turns 026001-26031 | 0.67-0.73 s | yaw ±90° / ±180° in place |
ladder 0280xx | 0.4-2.0 s | ±0.5 m vertical per step |
Locked on, the graph picks one directional clip by MoveDirection; there's no 2-D blend. The script sets the "4-direction move
thresholds" (60, 45, 60, 60). A locked-on dash releases the angle lock. Rolls turn to the target at once and pick a direction by RollingAngle.
Lock-on range is 15 m (LockCamParam), 9 or 5 m in the dark.
Stops and turns: letting go at ≤ 0.35 goes straight to idle; ≤ 1 plays a run stop by direction; above that, W_DashStop (0.65 m of slide), from which a
stick pushed past 90° plays a 180° dash turn whose root motion turns you while sliding.
Clips, root motion, smoothing constants, thresholds and directional picks are all DS3's. How the engine maps stick deflection to the level isn't traced.
4Rolls, backsteps and jumps

The medium rolls (1.83 s) share their TAE windows: flag 8 Invincible 0-0.533 s (16 frames of i-frames), flag 7 no turning 0.1-0.7 s, and flag 11 "stick may cancel" from 0.733 s. Distances: light 5.07 m, medium 4.45, heavy 3.31. Costs: roll 16 stamina, backstep 11, jump 16 (26 / 18 / 26 when overweight).

The jump has no take-off speed
The running jumps have 3.0 / 2.06 m/s of horizontal root motion and zero vertical. Their TAE has flag 27 Ignore Gravity for 0-0.1 s,
and event 300 ActivateJumpTableEarly ramps jump table 27 from 0 to 1 over 0.1-0.3 s. The physics module copies that value into its
gravity multiplier (+0x1D4, 0x1409dc600) exe. So the capsule carries on level for a moment, then gravity comes back.
The height you see is in the pose. (A popular cheat table's "jump height" patch makes the start value negative: a hack, not a mechanic.)
R1 in the air comes out on landing as the rolling R1, and R2 as the second heavy: the RH-attack window opens as the jump lands. The plunging attack starts once a fall is 2 m deep and R1 is pressed. Its clips carry flag 50, so the plunger falls through the enemy's capsule instead of standing on its head.
5TAE flags: the physics switchboard
| Flag | Name | Player events | Example |
|---|---|---|---|
| 27 | Ignore Gravity | 477 | ladders, ledge drops, the first 0.1 s of a jump |
| 19 | Disable Map/Object Collision | 364 | ladder mount, bonfire warp, pushing Gundyr's door |
| 51 | Disable Falling Animation | 528 | landings |
| 74 | Ladder Collision Start | 49 | every ladder clip |
| 50 | Disable NPCParam HitRadius Push | 174 | grabs, plunging |
| 7 | Disable Turning | 5,559 | most attacks |
| 11 | Cancel – LS Move | 3,626 | the stick may end the clip |
| 89 / 90 / 91 | No movement / no dash or run / no dash | 339 / 303 / 117 | heavy actions, drinking |
Plus event types 224 SetTurnSpeed, 236 RootMotionReduction (0.8 → 0.7 → 0.5 through a jump) and 300 the jump tables. These carry most of DS3's "feel".
6The body: Havok's character proxy
Characters aren't rigid bodies. Each is a Havok hknpCharacterProxy, a kinematic capsule moved by a shape-cast-and-slide solver,
wrapped as NS_SPRJ::CsHkCharacterProxy. The parameters were read off the stack frame of the creation function 0x140dda1c0, with
Havok's own descriptions, which are embedded in the exe exe:
| Field | DS3 | Our Jolt CharacterVirtual |
|---|---|---|
maxSlope | π/3 = 60° (first set to π/2, then overwritten) | mMaxSlopeAngle 60° (Jolt's default is 50°) |
| capsule | 1.9 m × 0.4 m, feet at the origin (NpcParam) | The same, as a shape offset |
keepDistance / keepContactTolerance | 0.05 / 0.1 | mCharacterPadding / mPredictiveContactDistance |
characterStrength | FLT_MAX | mMaxStrength large (Jolt's default is 100 N) |
penetrationRecoverySpeed | 1.0 | 1.0 |
maxCastIterations | 10 (2 with a setup flag) | Jolt's iterations |
| step-up lift | 0.3 m (0.8 in another mode) | mWalkStairsStepUp 0.3 |
| ground probe and snap | a downward cast to step + 0.8 m below | mStickToFloorStepDown |
| gravity | (0, −9.8, 0) × the multiplier, zeroed by flags | Applied by hand (Jolt doesn't, for virtual characters) |
A character can have two proxies: one against characters and a second, with a different capsule, against the map. The second exists only when
NpcParam's subHit size differs. After integrating, the closest upward-facing contact becomes the ground: its normal, and its hit material from body + shape key.
The module keeps the last grounded position (+0x170, the fall-height reference) and air time.
Who pushes whom isn't traced. Ours lets a character be stopped only by characters at least as heavy, so the player can't shove hollows about but hollows push the player guess. Corpses block nobody.
DS3's numbers on a different solver from the same family (shape-cast and slide, contact manifold). Unsolved: when DS3 runs "mode A" vs "mode B", the slide speed on slippery material, and FrpgCollisionFilter's layer matrix.
7Falling and fall damage
| Fall | Result (c0000.hks) |
|---|---|
| ≤ 0.3 m | no landing animation |
| 0.3-1.3 m | W_LandLow |
| 1.3-4 m / 4-8 m / > 8 m | W_Land default / middle / heavy |
| > 20 m | death on landing, unless SpEffects 4050-4080 |
| ≥ 60 m in the air | the fall-death animation mid-air |
Fall damage is engine code, on landing, before the script looks exe. For players (0x140634bc0): 0 under 5 m, all of max HP from 20 m,
else max HP × CalcCorrectGraph 210 (height → %: 5/8/12/16/20 m → 8/15/50/90/100 %) × 211 (equip load) × 212 (VIT) × 213 (DEX) × SpEffects.
The Knight takes ×1.32: 53 HP at 5.4 m, 219 at 10.4 m, all of it from about 14.6 m. Enemies: (h/15)^1.8 × (100 − fallDamageDamp) %.
The thresholds, landings, landing deaths and the damage curves, from the script and the exe.
8Bonfires, items, ladders, camera, keys
Interaction
Action buttons are ActionButtonParam cylinders on dummy polys: radius, height, offset, and the facing allowed (AllowAngle 180 = any). Bonfires, Gundyr's sword and the door all work this way (Levels).
Ladders
Objects with attach points. The script asks GetLadderEventCommand; gravity is off and ladder collision on (flags 27 + 74), 0.5 m per rung clip. not done: m40 has few.
Camera
LockCamParam: 4 m target distance, 43° vertical field of view, pivot height 1.42 m guess; a ray pulls it in against the collision. DS3's rotation speeds aren't found.
Keys on PC
The Key Config screen's actions come from the menu text. The defaults (WASD, Space, Q, left/right click, Shift + click for strong attacks, R item, E interact) come from published guides; where DS3 stores them is an open lead.