Captain appearance: bodies, armour, colour and scars

How a captain's look is chosen, stored and drawn: the appearance object, the seed that rolls body, size, head and colours, armour designs and the items that carry them, the army's level-based outfits and title looks, painted skins, scars, limbs and shoulder pads, tint layers, eyes and decals on screen, how grunts are dressed, and when a changed look reaches the world and the Army screen.

A captain's look is a small appearance object built from his seed and his class, then dressed by the items his class kit, army, tribe, title, personality and scars give him. The same object feeds the Army screen's copies and, through the character's visual, the body in the world.

The appearance object

The appearance object is a separate allocation at [FM+0x2D8] (save key SMCStorage; see The Nemesis system). It is built by the SimpleModel builder 0x4C5194, written piece by piece by SetPieceSlot 0x4C62EC, and copied field for field by 0x2EBA60, which fixes its layout:

Offset Type Meaning
+0x00 u32 body type (2 Slim, 3 Buff, 4 Stocky, 5 Olog; Easterlings use 12)
+0x08 / +0x10 vector appearance tags, 16-byte entries {tag record, u32 count}
+0x20 record the base Model (Model/SimpleModel.Base: orc_anim_base, olog_anim_base), which carries the skeleton
+0x28 record[23] piece slots (Model/SimpleModelPieces, 1,727 records)
+0xE0 record[15] design cells (Inventory/ArmorData), indexed by armour slot
+0x158 / +0x160 vector material-override records added by the tattoo and paint rolls
+0x170 record the head record (Model/SimpleModelHeads, 335 records)
+0x178 u32[17] the colour palette, ARGB (0xAARRGGBB); alpha 0xFF = set

A spawned character's visual carries a structure of the same layout at [visual+0xB8] (see Characters).

The object is 0x1C0 bytes. The FactionMember constructor 0x1BB4C1C creates it through 0x2EB490 (malloc(0x1C0), constructor 0x2EB4B8) and moves it into FM+0x2D8 (0x2EB56C); the heap block header in front of it reads 0x1CB on 60 of 60 seated captains. Nothing past +0x1C0 belongs to it: the next heap block starts there (an inventory item instance, vt_0x1FDABF8, in 20 of those 60 cases). The head category Model/SimpleModelHeads is held in the category-cache slot 0x26FFDC8 (see GameDB), which 0x1C05970, 0x1C0546C and 0x1C0606C read.

Piece slots and armour slots

Every design (Inventory/ArmorData, 1,349 records) names an ArmorSlot. 0x36B700 maps it to a piece slot and 0x4C63F4 maps a piece slot back to its design cell, so the design cell index is the armour slot itself:

ArmorSlot Designs Piece slot (offset) Content
0 99 7 (+0x60) helmet
1 454 8 (+0x68) shoulders (and the player's cloaks)
2 479 9 (+0x70) chest (and the player's armours)
3 54 10 (+0x78) arms
4 69 11 (+0x80) pants
5 27 12 (+0x88) legs
6 38 3 (+0x40) quiver (and scabbards)
7 50 4 (+0x48) accessory
8 7 13 (+0x90) body LOD
9 10 15 (+0xA0) head extra: metal jaw, gold tooth, masks, hoods, hair
10 9 0 (+0x28) body (no design cell)
11 18 1 (+0x30) head (no design cell)
12 35 none rings

The enum EInventoryArmorSlot (0x7D6E9C, 15 values) names them in order: Helmet, Shoulder, Chest, Arms, Pants, Legs, Accessory1, Accessory2, LowLOD, HeadAccessory1, BodyReplacement, HeadReplacement, Ring, Accessory3, Accessory4. ArmorSlots 13 and 14 map to piece slots 16 and 17; no shipped design uses them (the 1,349 designs stop at ArmorSlot 12). The build itself fills slot 2 (hair) and slot 5 (body flair), which no design reaches.

The 23 piece slots

The names are the engine's own: the stored forms of an appearance for slots 0-15, the slot enum 0x836D18 for slots 16 and 17, and the character node table 0x2A40950 for the five FX slots. "Built on" is the object the model load puts the piece on: 0x4C71D0 sends slots 1, 14 and 15 to the head object when the character has one, and every other slot to the body model.

Slot Offset Name ArmorSlot Design cell Built on In the save
0 +0x28 BodyModel 10 none body yes
1 +0x30 HeadModel 11 none head yes
2 +0x38 HairModel none none body yes
3 +0x40 AccessoryModel1 6 6 body yes
4 +0x48 AccessoryModel2 7 7 body yes
5 +0x50 BodyFlairModel none none body yes
6 +0x58 BodyScarModel none none body yes
7 +0x60 HelmetModel 0 0 body yes
8 +0x68 ArmorShoulder 1 1 body yes
9 +0x70 ArmorChest 2 2 body yes
10 +0x78 ArmorArms 3 3 body yes
11 +0x80 ArmorPants 4 4 body yes
12 +0x88 ArmorLegs 5 5 body yes
13 +0x90 LowLOD 8 8 body yes
14 +0x98 HeadScarModel none none head yes
15 +0xA0 HeadAccessory1 9 9 head yes
16 +0xA8 Accessory3 13 13 body no
17 +0xB0 Accessory4 14 14 body no
18-22 +0xB8 to +0xD8 FX1 to FX5 none none not built no

The model load 0x4C67B0 builds a fixed list of slots, not a range: 0, 5, 6, 8-13, 3, 4, 16 and 17, and through 0x103978 slots 1, 14, 7, 2 and 15. 0x36B248 walks all 23 but only refreshes pieces that already exist (0x4C6718 returns on an empty model slot), and 0x2EBA60 (copy), 0x369F60 (compare) and 0x36B430 (tints) walk all 23 as well, so a value in any slot is carried along whether or not it is ever built.

What writes the array

Eight routines write it. SetPieceSlot, the copy 0x2EBA60 and the three load routines below carry records; the constructor 0x2EB4B8, the reset 0x2EA718 and 0xD587B0 only zero it. 0xD587B0 does so with inlined stores rather than the accessor, then calls the build, which is how a rebuild empties every slot the build does not fill. The executable backs that list up three ways. Only 6 functions bounds-check a 23-element array: 0x36A018, 0x4C62EC, 0xFC93A0 and 0xFC93C0 on this one, 0x1A9A068 and 0x1AE9F18 on others. No block fill or block copy has a count of 23 cells or of the whole object, which holds two vectors and so cannot be copied raw. And following appearance pointers across 2,984 functions finds no other store to a record slot; that walk reproduces all 48 calls to the write accessor 0xFC93A0, but it does not follow virtual calls.

SetPieceSlot is always called with a known slot: the build 0x4C5194 (0-5 and 7), the item application through the ArmorSlot map (0x61BBC4, 0x4C61BC; never 2, 5, 6, 14 or 18-22), 0x619C1C for Inventory/Flair/BodyModelReplace (3 records; BodySlot 0 is slot 1, 1 is slot 0), 0x1C049D4, which empties slots 7-13, the Firebird import 0x1C04F20 (0, 1, 2, 7), 0x79B700 (0, 1, 2) and 0x103CB8 (1).

The slots the game barely uses

Hiding pieces

Inventory/ModelHide (7 records) hides slots and weapon slots by count. 0x1C68F08 keeps one counter per slot, 18 of them, and calls 0x1BEA4AC when a slot's visibility changes. Its ModelSlots values use the slot enum 0x836D18, which has 17 names: Body 0, Head 1, Hair 2, Accessory1 3, Accessory2 4, BodyFlair 5, BodyScar 6, Helmet 7, ArmorShoulder 8, ArmorChest 9, ArmorArms 10, ArmorPants 11, ArmorLegs 12, LowLOD 13, HeadAccessory1 15, Accessory3 16, Accessory4 17. Meatshield, Bound_Prisoner and FailSpy_Balcony hide Accessory1. Blinded_Dissolve lists 15 of the names in alphabetical order, all but Accessory3 and Accessory4, so those two stay visible through it.

The character node table

0x47888D0 fills a static table at 0x2A40950: 23 rows of 0x30 bytes, each with a name, readers for the <name>_Visible and <name>_AnimatedShape properties, two accessors and the Visible key. The rows run Body, Head, Hair, Accessory1, Accessory2, BodyFlair, BodyScar, Helmet, Shoulder, Chest, Arms, Pants, Legs, LowLOD, Accessory3, Accessory4, HeadScar, HeadAccessory1, FX1 to FX5. 0x1D641C4 and 0x1D67D20 index a model directly by row, so rows 14-17 do not line up with slots 14-17: Accessory3_Visible acts on slot 14 and HeadScar_Visible on slot 16. Rows 18-22 are what ties FX1 to FX5 to slots 18-22.

Live inspection of a running game: slots 6, 14 and 16-22 were empty on all 527 captains (slots 8 and 9 held a piece on 514) and on 65 spawned characters, and no model of 66 spawned characters had a piece in slots 18-22. So none of these slots holds anything in normal play. They are not inert: a record written to slot 5, 6, 16 or 17 of a captain's appearance is built on his spawned character alongside slot 4.

Stored forms

An appearance is stored in three forms, and all three stop at slot 15, so slots 16-22 do not survive a save and reload.

SetPieceSlot(appearance, slot, piece, design, visual) returns early when the slot already holds that piece; otherwise it removes the old piece's appearance tags (0x4C64EC), adds the new piece's (0x4C647C), writes the design into its cell, and, when a visual is passed, swaps the piece on that visual as well.

Building a look

The build sequence

Every member build runs the same sequence:

  1. Parameters. 0x577190 clears a parameter block and 0x1BEA190 fills it from the member: +0x00 the seed FM+0x168, +0x250 a size fraction (below), +0x210 the class model FM+0x278, +0x228 personality, +0x238 role, +0x240 row (a story character's DisplayFactionRow when he has none), the active traits, and the class kit's appearance-tag setup from the combat tree FM+0x188 (0x576FFC).
  2. The SimpleModel. 0x4C5194(appearance, params) takes Character/Models.SimpleModel of the class, adds the class kit's appearance tags (0x6FEA04, 0x1C06064), then rolls body, head, helmet, hair, colours, accessories and flair (below).
  3. Items. 0x1C10F00 empties the inventory (0x1C7BF4: every slot, the colour list and the look-record list), then 0x1C10AB0 gives, in order, the personality's Items, the role's Item list, the role's ScarType items, the tribe's Item list and the class kit's ItemsFactionMember, then the kit's named list for a follower (Named_PlayerFollowerItems) or an enemy (Named_SauronFollowerItems), chosen by flag bit 27.
  4. The army outfit. 0x61BA74 adds pieces of the army's outfit by level and rank (below).
  5. Apply. 0x61BAD4 writes every item's look into the appearance object.

The builder build 0x1BBF928, the preset build 0x1BC06C0 and 0x1BC00C8 run these steps inline (the builder build reaches step 4 when it sets the member's level). RebuildDirect 0x1BC3AA8(fm) is the whole sequence on an existing member; its only caller is 0x1C15B0C, reached only from the console command Faction.RebuildSMC 0x1C5BDA8, which rebuilds every member of the current region's army ([FS+0x1B8]) and then refreshes that army's Army-screen data.

The seed

Every roll of the look derives from the 64-bit seed FM+0x168:

Lcg(s)  = s * 0xBB38435 + 0x3619636B
Unit(s) = (u32)(s >> 32) / 4294967295.0

The size fraction is Unit(Lcg(seed)) (plus 0.0). 0x4C5194 copies the seed once and passes that copy to the body roll 0x6A9F38, the head pick 0x2EDBB4, the helmet pick 0x701A98, the hair pick 0x6C8BB4, the colour roll 0x2E9A2C and the accessory picks 0x6F4CA4 / 0x6F5614, in that order; every draw advances it. A class whose body table draws a different number of times therefore also changes the face, hair and colours that follow.

Item builds start again from the seed: the give path passes FM+0x168 to the item build, which draws from a local copy, so a member draws the same design from the same builder at every build. Colour items (FlairColors) are rolled from the seed the same way. The army outfit's choice of slots is the exception: it draws from the engine's global generator (0x1B3C6C).

The weighted pick used throughout is the reservoir 0x1B3B90: a weight of zero or less never draws; otherwise s = Lcg(s), r = Unit(s) * (acc + w), the entry wins when w > r, and acc += w.

Body type

0x6A9F38 walks Model/SimpleModel.Models (_StructuresModelSimpleModelEntry). For each entry, the weight is the BodyWeights[j] of the last RequiredScaleRange[j] (a float pair) that contains the size fraction; an entry with no matching range or no Body.Model is skipped. The chosen entry's BodyType goes to +0x00 and its Body to piece slot 0. The orc classes' tables:

Class Buff (3) Slim (2) Stocky (4)
Orc_Infantry 1 over 0.3 - 0.7 1 over 0 - 0.33 1 over 0.67 - 1
Orc_Defender 0.3 over 0 - 0.6 0 1 over 0 - 1
Orc_Berserker 1 over 0 - 1 0 0.15 over 0.6 - 1
Orc_Spearman 0.15 over 0.3 - 0.7 1 over 0 - 1 0
Orc_Archer 0.15 over 0.3 - 0.7 1 over 0 - 1 0.01 over 0.67 - 1

OlogHai_Infantry has one entry (body type 5). A Defender is Buff for about 14% of seeds (0.6 x 0.3 / 1.3); a Spearman or an Archer is mostly Slim. The bodies are Orc_Buff_Body001, Orc_Slim_Body001 and Orc_Stk_Body001V2. Changing a member's class therefore re-rolls his body at the next build, and a non-orc class gives a non-orc body and head.

Size

The in-world size uses the same fraction. 0x7F8488(fm) takes the army he was born into (FM+0x180, InitialFaction, else FM+0x178), the seed and the class, and 0x4E541C returns

size = min + (max - min) * Unit(Lcg(seed)) + army.ScaleAdditiveModifier

with (min, max) the class's Character/Models.AIScaleRange (Faction_MtnPass adds 0.1). 0x4E3CF0 stores it into the character's scale component (char+0x2738, at +0x10 and +0x6C) only when it differs by more than 0.01. Because body and size share the fraction, an Infantry captain is Slim at the small end of his class's size range, Buff in the middle and Stocky at the large end.

Head, hair and helmet

0x2EDBB4 picks a Model/SimpleModelHeads record from the chosen entry's DynamicHead list (the entry's Head list when that is empty), weighted per head. When the parameter byte +0x254 is set it considers only heads whose DynamicBundle is available (0x2EDDFC); member builds leave it clear. The record goes to +0x170 and its Head piece to slot 1; the helmet comes from the record's Helmets (slot 7) and the hair from its Hair (slot 2, only when SimpleModel.CanApplyHair). Every orc class lists the same heads per body: 24 Buff, 24 Slim and 23 Stocky, in two head families each.

Unique heads are the 18 designs of armour slot 11. Given by a title or a scar, such a design replaces the head piece; the head record (the face) and the hair stay. Smp_Empty_Set_NoHair differs from Smp_Empty_Set only by the tag Tag_I_ARE_BALD, which hides the hair.

The colour roll

0x2E9A2C rolls palette entries from the SimpleModel's colour setups (Model/SimpleModelColorSetup, 465 records, entries _StructuresSimpleModelColorEntry {SelectionWeight, Hue in degrees +- HueDeltaRange, Saturation / Value minimum and maximum 0-255, AppearanceTag}). The chosen entry's tag is added to the appearance:

Palette slot Rolled from Slot Rolled from
0 skin SkinTint 8 bone ClothingTint2
1 eye EyeTint 9 trim Trim
2 hair HairTint 10 faction metal FactionMetal
3 tattoo TattooTint, if a roll passes TattooChance 11 faction leather FactionLeather
4 war paint PaintTint, if a roll passes PaintChance 12 faction cloth FactionCloth
5 metal ArmorTint1 13 faction bone FactionBone
6 leather ArmorTint2 14 faction trim FactionTrim
7 cloth ClothingTint1 15 faction war paint items only

A passed tattoo or paint roll also picks one record of TattooMaterialOverrides / PaintMaterialOverrides into the list at +0x158. Slot 16 exists in the array but no tint layer reads it. Shipped orc tables: skin Orc_Infantry_Skin_Color (every orc class) is greenish (hue 63), fleshtone (32) or bluish (187) at weight 1, with charcoal (Tag_DarkSkin), albino (Tag_WhiteSkin) and three more at 0.01; eyes Eye_Color red 3 (Tag_RedEyes), yellow 2 (Tag_YellowEyes), blue 1, green 1; hair brown, grey, black 1, white 0.01. TattooChance is 0 on every class and PaintChance 0 on every class but the Berserker (0.5). Infantry, Defender and Archer share every setup; the Berserker has its own paint and tattoo, and the Spearman no clothing tint.

Armour

Designs and pieces

A design's ModelPicker is a list of _StructuresInventoryArmorModelPicker {SimpleModelPiece, RequiredBodyType}. 0x61BC24(design, body type) returns the first entry whose RequiredBodyType is the member's body type or 0 (any body). A design with no piece for that body type empties the slot.

Sets are named design families AD_<Set>_<Slot>, many with Bling variants (21 *Bling* designs). A Bling piece is the same model as its plain twin with a different mesh-part table (see Shoulder pads). The nine tribe sets cover all four bodies, except that Regal has no Olog legs.

A piece record carries Model, Tint (its tint layers), MeshIndexPairs, AppearanceTag, BaseWeight and ArmorDataDynamicBundle (its geometry bundle). A design also names its material bundle (DynamicBundle, a link to a DynamicResource/BundleDefinition, for example Black_Base), an OutOfMemoryBackups design, used when the piece does not fit the gear budget (AD_Black_Shoulders names AD_Sauron_Shoulders; see Resource streaming, "The gear budget") and follower and enemy colour overrides.

Items carry the armour

Beyond the SimpleModel's own pieces, a captain wears armour only as items. The member's inventory inv = FM+0x50 is laid out:

Offset Meaning
+0x00 + 8 x slot item instances of the 10 weapon slots (by the look's ItemSlot; see Items)
+0x50 + 8 x slot item instances of the 15 armour slots (by ArmorSlot)
+0xC8 / +0xD0 the colour list: entries {u8 source, u32 palette slot, u32 ARGB}
+0xE0 / +0xE8 the look-record list: Inventory/Items records with a body change or material overrides

The give path 0x355474(inv, fm, item, source, appearance):

  1. refuses the item when the member's combat tree's CharacterType fails the item's RequiredCharacterType / ExcludedCharacterType;
  2. for each FlairColors entry (Inventory/Flair/Color {ColorType < 17, ColorRecord}) gets or creates the colour-list entry of that slot (0x1C10E54) and, if the source may replace the entry's (rule below), rolls it from the seed (0x1C11A74);
  3. an item with an ItemBuilder becomes an instance: the build 0x649790 draws one WeaponData look and then one ArmorData design uniformly, sets +0x48 the design and +0x30 its ArmorSlot (15 when none), and the source goes to +0x100; 0x61B868 then places it in its slot and adds its item's AppearanceTags;
  4. an item without a builder is filed in the look-record list (once) when it has a BodyModelChange or PassiveMaterialOverrides;
  5. every PairedItems entry is given the same way.

PassiveFXSequencePicker is not read on this path.

Which item wins a slot. 0x61C238(new source, old source, new item, old item) decides whether an item may replace the one in a slot (and a colour entry another):

Old source Replaced by
0 (empty) anything
1 / 2 a source of at least 1 / 2
3 a source above 3
4 - 10 a source of 4 or more whose item's FactionMemberItemReplaceValue is at least the old item's
11 and above nothing

A new source of 11 never replaces, nor does the same item. The sources the build uses are 2 class kit, 3 army outfit, 4 personality, 5 role, 6 tribe and 10 scar. FactionMemberItemReplaceValue is 0 on 5,003 of 5,618 items; the rest use 1 - 100 (10 on 247, 5 on 137, 2 on 87, 20 on 44) and one 9001.

Applying. 0x61BAD4(inv, appearance) writes each armour-slot instance through 0x61BBC4 (piece for the body type, then SetPieceSlot with no visual), applies each BodyModelChange (Inventory/Flair/BodyModelReplace, 3 records: a model picker by body type and a BodySlot, 0x619C1C) from the slot items and the look-record list, and copies each colour-list entry into the palette.

The faction base layer

Each orc class kit's named lists give a base suit at source 2, the lowest priority: an enemy gets Sauron_Chest, Sauron_Pants, Sauron_Shoulders, Sauron_Arms and IT_Faction_Enemy_Tint; a follower Talion_Chest, Talion_Pants, Talion_Shoulders, Talion_Arms, FactionPlayerFollower and IT_Faction_Friendly_Tint (an Olog Sauron_Pants and Sauron_Helmet, or Talion_Pants; Archers and Spearmen add their quiver). The kit's ItemsFactionMember gives the body LOD (IT_Orc_Body_LOD -> AD_Orc_Body_LOD).

The army outfit by level

0x61BA74(fm) decides how many armour pieces a member wears and fills them from an outfit. The count comes from the army's Faction/Definition.ArmorSetup (Faction/ArmorSetup/Default): 0x61C940 walks its Entries in order and returns the ArmorPieceCount of the first whose MinLevel is at most the shown level (FM+0x4B8 + 1) and whose RequiredRowRank list is empty or holds the row's rank:

Entry MinLevel Rank Pieces
0 1 Overlord 6
1 30 any 6
2 22 any 5
3 16 any 4
4 10 any 3
5 5 any 2
6 2 any 1

A level-1 member gets none. The outfit is the tribe's ArmorSetOverride, else the role's ArmorSetOverride (2 of 914 roles), else the class kit's DefaultArmorSet (1 of 93 kits); each is a Model/SimpleModelArmorSet (17 records) with an item and a weight per slot:

Tribe Outfit
Dark Black_Base_Outfit
Feral Beast_Base_Outfit (no arms item)
Machine Industrial_Base_Outfit
Marauder Regal_Base_Outfit
Outlaw Scavenger_Base_Outfit
Slaughter Slaughterhouse_Base_Outfit
Sorcerous (and its DLC poison variant) Mystic_Base_Outfit
Terror Torture_Base_Outfit
Warmonger, Bruz Warmonger_Base_Outfit (no arms item)
Militant Fanatic_Base_Outfit (every item empty)

Easterling, Gondor, Sauron, ShadowWar, Tribeless, Undead and NazgulTwins have none. Every tribe outfit weighs chest 5, pants 4, shoulders 3, arms 2, legs 2, helmet 1. The Feral and Warmonger tribes' own Item lists give Beast_Arms / Warmonger_Arms directly.

0x61BE74 lists the outfit's six slot items as candidates where the slot is empty or the outfit may replace what is there, judged at the outfit's origin (6 for a tribe outfit, 5 for a role's, 3 for a kit's); slots held by an item it may not replace count toward the total. It then draws candidates by weight, one per missing piece, and gives each through the give path at source 3, so a drawn piece lands only on an empty slot or over the base layer. Besides the build, 0x61BA74 runs on a level change (SetLevel 0x35188C) and from 0x61B614, which places a member in a row (it reads the row's Rank and EnableHenchmen), so captains gain armour as they level and rise.

Title looks

A title (Faction/Roles/Definitions, 914) carries no look of its own: its Item list does. 725 roles list items; 685 list at least one item with an ItemBuilder (armour or weapon), 374 an item with a look payload, and 6 carry a ScarType. An item builder's ArmorData list holds 1 - 40 designs.

Of 5,618 items, 317 carry a look payload: PassiveMaterialOverrides 210 (decals, glow, hides), FlairColors 123 (palettes, including about 55 full colourways IT_*_Tint), MaterialSetVariants 71, PassiveFXSequencePicker 21 (auras) and BodyModelChange 3. 199 of them have no builder and live in the look-record list.

A title change through the role setter 0x1BC6698 dresses at once: 0x1BC63F8 writes FM+0x318 (the old role to +0x320), forces the role's personality when it has one, applies its ScarType through AddScar and fires its role event; 0x1BC6698 then gives each of the role's items at source 5 and re-applies the look (0x61BAD4). A plain write of FM+0x318 gives no items until the next build.

Body model, painted skin and body look

Three different things change "the body":

Scars, limbs and shoulder pads

Scars

AddScar 0x7BB8C4(fm, Combat/Scar/Type):

  1. returns when FM+0x370 already holds the type;
  2. refuses when the member is in the world and holds any of the type's ExcludedItems, or when 0xFCB040 finds one in his inventory;
  3. takes the setup from his combat tree's ScarSetup (Combat/Scar/Setup: Orc 22 types, Olog 18; 24 types in all) and gives one random entry of the type's scar items (0x7BBABC) and its helmet item (0x7BBAF4), both at source 10;
  4. if either was given, writes FM+0x370, re-applies the look (0x61BAD4) and counts the use on the faction system (0x7BBA44).

RemoveScar 0x1BC409C removes one random item of the type's list, so it can miss on a type with several. The build gives only the role's ScarType; nothing in it reads FM+0x370, and its item clear removes the earlier scar items, so a rebuild drops a scar given by AddScar. The scars a death or a return brings are in The Nemesis system.

By their items: Poison, Branded_Slave and Mauled (and the Olog Poison) bare the head (an AD_empty_head* item in armour slot 0); Metal_Head and Sack_Head replace the head (armour slot 11) and MetalJaw_Helmet uses slot 9; ExposedBrain, DaggerHead and AxeHead are helmets; burns, cuts, eye wounds, Facepeele and Shamed are decals only; claws use the accessory slot and the curse the body.

Limbs

Six designs replace a limb: AD_L_Arm_Claw, AD_BigClawL_Accessory, AD_R_Leg_Claw, AD_NazgulArm, AD_Blight_Arms (accessory slot) and AD_Zog_Legs (legs). The orc's own limb is hidden by the item's PassiveMaterialOverrides, not by the piece: an Inventory/MaterialOverride names a model piece (0 body, 8 shoulders, 10 arms, 12 legs, 13 body LOD) and a SeverMask (1 left lower leg, 2 right lower leg, 4 lower body, 8 left forearm, 16 left upper arm, 32 right forearm, 64 right upper arm). The claw and peg-leg items hide only the body skin; the game ships armour hides that no item uses (orc_Hide_Armor_Arml_L, piece 10 mask 8; orc_Hide_Armor_Legl_R, piece 12 mask 2). Two overrides on the same piece and key do not combine (the last bound wins).

NazgulArm (orcs only) puts Orc_<body>_NazgulArm in the accessory slot: a pale veined arm with its own 512 x 512 atlas and armour tint layers, and three hides (orc_Hide_Body_Armu_R mask 96, orc_Hide_Armor_Arml_R 32, orc_Hide_Armor_Armu_R) that cut the orc's own right arm away. It comes from Role_Accursed, Role_Hand and Role_StormBringer.

Shoulder pads and appearance tags

A shoulder piece's MeshIndexPairs (_StructuresSimpleModelMeshIndexPair {MinIndex, MaxIndex, RequiredAppearanceTag[], ExcludedAppearanceTag[]}, 4,447 in the database) are ranges of the model's mesh parts. 0x1B37F0 keeps the pairs whose required tags are all present in the appearance and whose excluded tags are absent, and picks one uniformly from a copy of the visual's seed. The tags are Tag_ShoulderBoth, Left_Shoulder and Right_Shoulder, given by items (Both_Shoulders, the Only_* items, some chests and scars). A Bling piece maps 2-2 to 1-2, 3-3 to 3-4 and 2-3 to 1-4 (parts 1 and 4 are the plates). 80 of 132 orc and Olog shoulder pieces have no both-sides layout.

Tags are added by SetPieceSlot, by item equip (0x61B868, an item's AppearanceTags), by the class kit at the start of a build and by colour entries, and removed per piece on a swap; 0x2EA18C adds a count, 0x6E90F4 subtracts and removes an entry at zero. A build adds the kit's and the colours' tags again without clearing, so counts grow with each build. The vector is saved (smc_storage_appearancetags). Tags also select weapon models: _StructuresInventoryItemModel.OverrideModels (0x3FF344, picked by 0x1BFB1FC when the wearer holds every required tag).

Colour on screen

Tint layers

A piece's Tint is a Model/SimpleModelTint (76 records) that maps the tint mask's R, G, B and A channels to palette slots; index 16 means unused. The records are named after the slots they read (74 of 75 named records agree; for example Skin_Eye_Warpaint_Hair = 0, 1, 4, 2 and Metal_Leather_FactionCloth_Trim = 5, 6, 12, 9).

The writer 0x36B430 walks the visual's 23 pieces: for each piece with a Tint it finds or creates the tint entry (0x4C3634) and, per channel below 16, 0x2EAF10 -> 0x2EAFF4 writes the layer: when the palette entry's alpha is 0xFF, amount 1.0 and colour (R, G, B) / 255 (SetParamVector, handle slot 7); otherwise amount 0.0 and no colour record. On the head (slot 1) the tattoo layer is gated by SimpleModel.TattooApplyToHead and war paint by PaintApplyToHead (true on 182 of 185 SimpleModels). A second writer (0x36B5D8 -> 0x4C3F58 -> 0x78CD3C) applies a design's follower or enemy colour overrides, rolled in HSV, in an entry keyed by that design.

The tint owner [visual+0xC0] holds the character's SimpleModel at +0x08 and a vector of 0x18-byte entries at +0x18 / +0x20: {SimpleModelTint, design (0 = base), handle}; an entry whose design is the slot's design wins over the base entry. Pieces that share a SimpleModelTint share one store. The handle class and its setters are in Rendering; the store (0x28 bytes) holds its records {u32 type << 28 | hash, u32 size, data} at +0x00 / +0x08 (type 2 float3 k_c?TintColor, type 5 float k_f?TintAmount), a reference count at +0x1C and a change stamp at +0x20.

The render cache. A piece's render node rebuilds its parameters only when one of its stores' stamps is newer than its last build. Values written into a store in place change nothing on screen until the stamp moves ahead of the counter the render thread keeps in its thread-local block (about 7,900 increments per second).

The shader blends c = lerp(c, diffuse x tint, mask x amount) per channel in linear space, always from the original diffuse. Pieces with a blank tint mask (flatblack_alpha.tex, for example the metal-bandage helmet) cannot be tinted. The gear shader also lerps each mask layer's specular sample toward k_v?SpecularTint (reflectance, specular occlusion, roughness) by mask x k_f?TintAmount x k_fSpecularTintToggle x k_f?SpecularTintAmount; gear materials declare the vectors (0.6667, 1, 0.5) with amounts 0, and no captain's tint store carries them.

Eyes and glow

A captain's eyes are solid material slots 5 and 6 of his head piece's material object, one material per eye (see Characters for the slots): eye_orc, _black, _bright, _serpent, _undead, _cataracts, _cut, _metal and _crossed, eye_cursed and olog_eyes, all resident in a loaded world; eye_orc_spider ships only in a DLC bundle. The eye shader tints the iris by the G layer (the eye palette entry) and writes an emissive value saturate((mask . k_vEmissiveTintChannelMask - range.x) / (range.y - range.x)) x k_fEmissiveScale: orc heads ship range (0, 2) and scale 0.05 (0.35 bright, 0 cataracts), so a fully masked pixel glows at half the scale; Olog heads have no eye glow. The head writes an emissive lookup row (k_nEmissiveLUTIndex, orc heads 7; the brand texture writes k_nBrandLUTIndex); the EyeGlowLUT_* records name rows Fire 0, Wraith 1, Poison 2, Nazgul 4, Cursed 5.

A title's eye look is an item (58 items, given by 64 titles): MaterialSetVariants swap only the eyeball (19 variants, all on the head), PassiveMaterialOverrides paint the rest, and FlairColors set skin and eye colours. An undead captain's glowing eyes are not the eye material but the I_Am_Undead item's PassiveFXSequencePicker (UndeadFX -> necro_undead_ambient effects with "Left Eye Star" / "Right Eye Star" particles on the eye bones), started by the item handler 0x1C0E0C -> 0x79BFF4; see Visual effects.

Material overrides and decals

AddMaterialOverride 0x57AF70(visual, record, n) and RemoveMaterialOverride 0x57D530(visual, record, n) put an Inventory/MaterialOverride (378 records) on a drawn character. Add does nothing unless the visual's byte +0xB2 is set and it has a skeleton (+0x90). The list visual+0xC8 holds 0x38-byte entries {+0x00 decal slot, +0x08 record, +0x10 count, +0x18 handle, +0x20 vector}; the same record again only raises the count, and a remove at zero unbinds and frees the entry. A new entry's parameters come from the record (_StructuresInventoryMaterialParam: a parameter picker such as ScarLayer, ScarPosition, ScarRotation, ScarScale with 16 names each, or a plain name), placed in the lowest decal slot that no entry of the same piece group sharing that picker uses, and bound at index 0 of the piece's material after the tint store, so the last store carrying a key wins. The head and body shaders read decal slots 0-7.

Body decals need the scar material. Normal bodies draw orc_body001(v2).mat / olog_body001.mat (body_noscar.fxo, no decal slots). The scar twins (orc_body_scar, orc_body_stk_scar, olog_body_scar and their LODs, body_lod0/1.fxo) use the same textures and meshes plus eight decal slots. A decal store holds k_fScarLayerN, k_fScarRotationN, k_fScarTintN (floats) and k_vScarPositionN, k_vScarScaleN, k_fScarRotationNCosSin (type 11, float2); a decal of scale s covers a UV square of side 1 / (4s).

The orc body atlas (shared by Slim, Buff and Stocky; engine texture coordinates):

Region u v
right hand / right arm 0.00 - 0.15 / 0.17 - 0.49 0.10 - 0.44
torso (back / front) 0.35 - 0.65 0.02 - 0.25 / 0.30 - 0.54
left arm / left hand 0.51 - 0.83 / 0.85 - 1.00 0.10 - 0.44
thighs right 0.03 - 0.33, left 0.67 - 0.97 0.41 - 0.66
shins right 0.03 - 0.33, left 0.67 - 0.97 0.71 - 0.97

Left and right islands do not overlap. orc_Cursed (the curse) pairs Body_Scar_BodyReplacement, sets skin Orc_Skin_White, the curse face and black eyes, and four olog_curse_body vein decals on the torso. Orc_Skin_White and Orc_Skin_Undead are HSV saturation 0, value 255: a white tint that shows the pale diffuse itself.

Grunts

Grunts are not faction members and get no member build; the dresser 0x4F12E0(visual component, is follower) dresses them from the class kit ([Combat/Tree].InventorySetup, read at every call). It skips the player (the combat object's +0x50); for an unnamed character an enemy gets Grunt_SauronFollowerItems and loses Grunt_PlayerFollowerItems (0x4F163C), a follower the reverse, so domination re-dresses a grunt. A named character gets the Named_* lists minus every item with a builder. Its callers include inventory initialisation 0x4F0938 and the follower setter 0x102F0C.

The shipped grunt armour is one item: Sauron_Armor (Talion_Armor for followers) -> AD_Sauron_Armor (armour slot 4) -> Orc_<body>_Sauron_faction, the strap-and-loincloth outfit, or Olog_Buff_Skirt_Grunt. The give path 0x1C01E0 -> 0x1C1224 -> 0x11565A4 -> 0x649790 draws one design per item build exactly as for a captain, and the equip requests the bundles of what it draws.

When a changed look shows

Residency. A piece draws only when its geometry and material bundles are resident; both are requested by the gear load request of an equipped item (see Resource streaming). A piece no design lists has no material bundle to request: AD_Machine_Body lists only Orc_Buff_Machine_Body (body type 0), so the Slim and Stocky Machine bodies, which have their own geometry bundles, have materials (machine_body_m.bndl) that nothing requests. Easterling pieces' designs list body type 12 only and name no material bundle; their shared easterling_outfit.mat rides in easterlings_base, easterling_buff_heavy_arms_g and flair\grunt\desert_grunt_m.

The appearance object and the body. 0x61BAD4 and the build write only the appearance object (SetPieceSlot with no visual). A drawn body changes through SetPieceSlot with its visual and through the equip path's load request.

0x62EB14 (often labelled UpdateVisualModel) is an army refresh, not a character refresh: it takes an army context and that army's summary container ([ctx+0x30]), rebuilds the container's summaries (0x62BC44, refilling each from its member through 0x62BF54) and asks the world UI to refresh the army's stage (0x62E30C). The 7 of its 12 call sites examined all pass an army context: the walk over every army 0x1B5D66C, fort-upgrade removal 0x1B5E39C, 0x1C1B22C, 0x1C1DB10, the promotion events 0x1C1D828 (with a temporary container), the console rebuild, and 0x7A97DC, which first replaces the army's summary container (0x62AFD4) and is called after the faction system initialises and from sim-event generation. A member is not a valid first argument: the rebuild's first instructions (mov rax,[rdx+0xD8] / mov [rcx+0x9F50],rax) store the context's +0xD8 in the container's +0x9F50 before anything is tested, so a member and his 0x1C0-byte appearance object in place of a context and a container put a pointer 0x9F50 bytes past the appearance object.

The Army screen

The Army screen draws each captain from his army's summary (0x1A8 bytes, one per member, in the summary container), not from the member (see User interface for the stage objects). The refill 0x169168(summary, fm) copies the key {FM+0x178, FM+0x174} (+0x88), class +0x138, combat tree +0x130, role +0x140, personality +0x148, tribe +0x150, rarity +0x178, level, flags, rebuilds the trait and marker lists, copies the appearance into [summary+0x40] with 0x2EBA60 and the inventory into [summary+0x30]. Both copies come from the mock member when FM+0x2E0 is set (0x75D2FC / 0x75D314), so such a captain shows the older look (see The Nemesis system).

The per-captain redraw is 0x19C395C(world UI [base+0x26FFB60], &key): the army's stage holder (0x455EDC), the summary (0x1B5C0F8), the stage character (0x1A28FAC) and its rebuild UI_CWorldUICharacter 0x627F40(object, summary, 0, 0). It is what the game calls after a Training Order (0x1C51630). It draws from the summary as it is, so a summary that was not refilled redraws the old look; the open screen also keeps its own summary copies per army (0x16FC1C).

Pieces across species and shipped quirks

Open questions

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.