The DS3 Codex
Chapter VIII

Enemies & AI

A Cemetery hollow wears another enemy's animations, runs a shared humanoid behaviour graph, and decides what to do by rolling weighted dice in a Lua 5.0 script. Iudex Gundyr adds a second phase, super armour, cloth and an event script.

At fifty-five percent his health, the Iudex is overcome by the pus of man. A status effect, nothing more, but it changes every animation he owns and every thought he has.Codex · SpEffect 5404

In short
MSB Enemy part (model, NpcParam row, NpcThinkParam row)
  NpcParam      → HP, capsule, turn speed, display masks, whose clips it borrows, its attack rows
  NpcThinkParam → which Lua battle goal, eye range and angles, smell, how far it chases
  AI (Lua 5.0)  → weighted roll of an "act" by distance → subgoals (approach, attack, combo, strafe, back off, turn)
                → requests an action id each tick (3000 = W_Attack3000)
  NPC script (HKS common_npc) → behaviour events → shared graph c9997 → clip a00X_NNNNNN
  clip TAE      → AI cancel windows, turn speed, AttackBehavior, Bullet

1Anatomy of a hollow

A Cemetery of Ash hollow
A Cemetery of Ash hollow (c1430) in our engine, from behind. Its lantern and cape run DS3's Havok Cloth.

m40 places 41 c1430, more than any other model. Their NpcParam rows differ in HP (59-183) and display masks; all share behaviorVariationId 11000, turnVellocity 180, hitRadius 0.4, and no poise at all. Four kinds:

NpcParamWeapon (mask)SpEffect → clip prefixBattle goalScript name
143000sword (0)none → a000110005 / 110050MoujaSoldier_Sword
143020sword and shield (1, 5)5404 → a001110010MoujaSoldier_LanceShiled
143030crossbow (2)5405 → a002143030MoujaSoldier_bow
143040broken sword (3)5406 → a003110050MoujaSoldier_BrokenSword

Borrowed clips and a shared graph

2The NPC script

c1100's own HKS handles only event animations. The logic is the shared common_npc.hks data:

Inside an attack clip, the AI may cut in only in its windows: TAE flags 23 "Cancel – AI Combo Attack", 86 "AI Attack", 78 "AI Movement", 79 "AI Dodge". In a000_003000 (3.37 s): 23 from 1.17 s, 79 and 86 from 1.9 s, 78 from 2.07 s. TAE 224 sets the turn speed through the swing (90, 120, 0, 0, 45, 0 °/s), which is how an attack aims itself.

3The battle AI: Lua goals and weighted dice

The AI scripts are Lua 5.0 bytecode (not HKS): script/aicommon.luabnd (209 shared goals and helpers) and script/m40_00_00_00.luabnd (the area's battle goals). NpcThinkParam.battleGoalId names the file. Goals form a stack: a goal's Activate pushes subgoals, its Update reports success when they're done, and the battle goal replans every time its subgoals run out. Interrupt handlers can clear them early.

Battle goal 110005, the sword hollow

If the target is more than 60° off its front, it turns. Otherwise it weighs its acts by distance:

DistanceAct 1Act 2Act 4Act 7Act 8Act 14
≥ 10 m15405101515
≥ 5.5 m203010151015
≥ 3 m151510202020
≥ 1 m20—10302020
< 1 m20—15252020
ActReachAttacks (roll %)Then
13.2 m35: 3000 · 40: 3000 → 3001 · 25: 3000 → 3001 → 3002after-act
27 m90: 3003 (the dash thrust) · 10: 3003 → 3001—
43.5 m3005after-act
73.5 m30: 3008 · 70: 3008 → 3005after-act
82.2 m30: 3009 · 70: 3009 → 3005after-act
143.2 m20: 3000 · 80: 3000 → 3009after-act

Each act first approaches to its reach less its own radius: it runs when at least reach + 10 m away (or half the time at reach + 1), giving up after 4 s walking or 8 s running. The after-attack act then rolls again by distance: strafe 2.5-4.5 s at 30-45°, back off 2.5-4.5 m, or run back in.

110010 · sword and shield

Its first act always keeps its distance behind the shield. Every approach, strafe and retreat raises the guard (action 9910). Its weights grow 1.5-5× when it's behind you. On a blocked hit within 3 m: 80 % counter.

110050 · broken sword

Swings from whichever side you stand on within 2 m, turns if you're behind it, runs in, and backs off less.

143030 · crossbow

Shoots from up to 44 m (75 % the long shot beyond 10 m), turning up to 3 s to face within 45°. The bolt is Bullet 1100801: 20 m/s accelerating to 60, light gravity, homing 5°/s.

Attack goals are C++ (GOAL_COMMON_CommonAttack). The Lua wrappers show only their parameters: success distance, turn time, front angle.

Close

Our engine ports all four battle goals act by act, with their lives, weights and after-acts, and lets AI requests in only where the HKS would. Our readings: a combo continues only if the target is within its success distance; an approach that times out fails the plan. No navmesh (they walk straight at you), no team roles or platoons, no unique idles (sitting hollows getting up).

4Noticing you

The Lua only reads states that C++ sets (IsBattleState, IsFindState, IsCautionState). The inputs are in the data:

Tuned

Our engine uses these with a line-of-sight ray, eyes 1.5 m up. Played with DS3's numbers, the hollows were too easy to walk up to, so we added three adjustments (noticed within 3 m all round, eyes at least 50° each way, hearing 1.5× as far). Turned off, they leave DS3's own numbers.

5How DS3 makes enemies customisable

Almost nothing about an enemy is hard-coded. A designer composes it from data:

LeverWhereChanges
Which body and statsMSB part → NpcParam rowHP, defence, capsule, poise, weight, regen, outfit and weapon (display masks)
Which movesnormalChangeAnimChrId + a SpEffect's animIdOffsetBorrow another character's clips; switch the whole clip set (the crossbow hollow, Gundyr's phase 2)
Which attacks hit howbehaviorVariationId → BehaviorParam → AtkParam_NpcDamage, shapes, damage level, knockback, bullets
How it thinksMSB part → NpcThinkParam → battle goal LuaThe same model can be a swordsman, an ambusher or a sniper
Per-row special casesInside the Lua: if thinkId == 110054 …Firebomb throwers, spectators, surrounders; Gundyr's file also holds Champion Gundyr's branches
What it does in the storyEvent scripts (.emevd) by entity idAI on and off, invincibility, forced animations, boss bars

6Iudex Gundyr

KneelForceAnimationPlayback(30002, loop), AI off, invincible, lock-on point off
the action button at his dummy 100 (ActionButtonParam 8900 "Pull out a sword")
The pullthe player is warped onto dummy 100 and plays a000_060750; he plays 20003. The sword is his mesh on bone SealWeapon01, and the two clips are made to fit: the knight's hand stays 9 cm from it
TAE 231 message 40 at 3.67 s of 20002
Phase 1GOAL_Gunda_511000_Battle; he always walks; HP 1037, super armour 185
HP ≤ 55 %: act 50, attack 1500
Transformation (7.3 s)TAE 66 sets SpEffect 5404 (permanent): every clip becomes a001_* and the switch goal picks …_HU_Battle. TAE 233 shows the pus at 0.4 s and swaps his arm meshes at 2.13 s; message 10 moves the lock-on point to the pus
HP 0
Defeatevent 14005800: 1 s, a sound, 4 s, HandleBossDefeat, flag 14000800 → his bonfire appears and the door can open

His AI: phase 1 acts by side and distance (within 3 m on his right: 55 % act 4, 45 % act 5), with cool-downs (3000 not again for 10 s). Phase 2 adds rolls back and to the side after attacks. Small and middle hits don't flinch him: his only damage clips are Large. After a death, he rises again when you come back through the courtyard's gateway (region 4002800, a 6 × 3.8 × 6 m box 36 m from him), not when you walk up to him.

Close

His clips, both battle goals, super armour, cloth and the event scripts are ported. Not done: the fog walls, camera 5110, his weak point, the grab and ripostes, and the pus's hurt capsules being there in phase 1 too.