Tactical Reroll
⋯

Roles found in the data, and each role scored by its own weights (T-630, Tyranids, 3 Oct)

From reports/tiers/2026-10-03-roles.md , rendered when the site is built.

Jordan's question: "score units based on roles where certain stats are ignored. Is there a statistical method for this? And can we just look at the aggregate data and see what roles fall out naturally? Or are we back to the 4 we had tried ages ago?" Scope (Jordan, later on 3 Oct): Tyranids only for now; an all-armies run made before that call is a side note at the end. Command: node tools/roles.js (16 s; --all for the 16 armies, 5 min). Numbers in reports/tiers/2026-10-03-roles.json (each Tyranid unit's memberships) and 2026-10-03-roles-all-armies.json. A report: nothing is adopted and no setting, engine or data file changed. Our own numbers; unit names only.

The short answer

The roles, unit by unit (Tyranids)

Memberships sum to 1; the main role and its share in %. Archetypal analysis, k = 3 (the knee of the fit curve, also the most stable k; Jordan's budget allows 2 to 3).

RoleUnits (main role, share)
Big and tough (27)The Swarmlord 100, Hive Tyrant 100, Norn Assimilator 100, Hierophant 100, Harridan 100, Old One Eye 95, Winged Hive Tyrant 94, Maleceptor 93, Haruspex 92, Hive Crone 91, Exocrine 90, Tyrannofex 88, Tervigon 85, Norn Emissary 82, Trygon 82, The Red Terror 82, Tyrannocyte 81, Harpy 77, Screamer-killer 76, Toxicrene 67, Sporocyst 66, Psychophage 66, Carnifexes 63, Deathleaper 55, Mawloc 55, Broodlord 55, Neurotyrant 53
Bodies on objectives (22)Von Ryan's Leapers 100, Hormagaunts 100, Termagants 100, Ranged Warriors 100, Neurogaunts 96, Genestealers 93, Gargoyles 91, Barbgaunts 90, Venomthropes 87, Melee Warriors 87, Pyrovores 77, Tyrant Guard 71, Neurolictor 68, Raveners 63, Parasite of Mortrex 63, Biovores 62, Hive Guard 59, Hyperadapted Raveners 54, Winged Tyranid Prime 51, Zoanthropes 51, Tyranid Prime with Lash Whip 50, Lictor 48
Cheap action pieces (3)Mucolid Spores 100, Spore Mines 100, Ripper Swarms 51

Mixed units (no role above 60%): the Lictor, Zoanthropes, Deathleaper, Hyperadapted Raveners, both Primes, the Broodlord, Hive Guard, Ripper Swarms, the Neurotyrant and the Mawloc. These are mostly the characters and mid-sized specialists the reviewers argue about.

Each role's profile (z-scores within Tyranids):

RoleHighLowOld jobs' proxies (correlation with membership)
Big and toughmonster +1.03, wounds per model +0.99, Kill into light vehicle +0.75, heavy vehicle +0.63, heavy infantry +0.63infantry −0.92, Presence −0.83, Actions −0.81, Kill into chaff −0.72Hammer +0.21, Anvil +0.12, Banner +0.25, Runner −0.56
Bodies on objectivesKill into elite infantry +1.24, OC +1.22, Kill into chaff +1.21, model count +1.14, Hold +0.95wounds per model −1.13, monster −0.95, Kill into light vehicle −0.64Hammer +0.30, Anvil +0.33, Banner −0.21, Runner +0.36
Cheap action piecesmounted or beasts +3.31, Actions +2.16, Presence +2.16, fly +1.41Kill into character −3.65, Soak −2.36, Kill into elite infantry −2.04Hammer −0.86, Anvil −0.75, Banner −0.08, Runner +0.37

The proxies: Hammer is the log of total Kill per point, Anvil is Soak with Hold, Runner is Actions, Presence and Move, and Banner is the character flag (no solo line measures support).

How many dimensions matter: five, by parallel analysis (eigenvalues 6.1, 5.2, 3.0, 2.5, 2.2 against shuffled-data thresholds 2.9, 2.6, 2.3, 2.1, 1.9; Kaiser's rule says 8). After rotation:

  1. small and cheap (Actions, Presence, beasts) against big and dangerous (Kill into characters, cavalry and heavy infantry, Soak, wounds);
  2. objective bodies (OC, model count, Hold, Kill into chaff);
  3. speed (charge lands, Move, melee share, arrival);
  4. anti-armour Kill (into monsters and vehicles);
  5. transport (the Carry line).

The three roles span the first two dimensions well. Speed, anti-armour and transport cut across them: inside each role they separate the better units from the worse. The three roles explain 38.5% of the table's variance (two roles 21.5%, four 48.1%, five 55.7%). Bootstrap stability is 0.874 (mean matched-profile correlation over 20 resamples), and 89% of units keep their main role. A Gaussian mixture on the five components prefers 5 clusters by BIC and agrees only weakly with the roles (adjusted Rand 0.27; 0.37 at 3 clusters). The units sit on a continuum rather than in natural clumps.

Each role scored by its own weights: leave one list out (Tyranids)

Fit on four lists by the pairwise likelihood (tools/ledgerfit.js's, reliability-weighted). Score the list left out. The single-weight ledger is fitted the same way on the same folds, with the same ridge (toward the step-6 weights). The role ledger's ridge pulls toward that fold's single fit. Each role frees the 3 lines its profile is furthest from 0 on (from Part 1, never from letters): big and tough Kill, Actions and Presence; bodies on objectives Kill, Score and Hold; cheap action pieces Kill, Soak and Actions.

The budget: 5 lists, 3,946 cross-tier pairs over 226 graded entries of 52 units. The single ledger fits 7 weights and 5 list scales. The role ledger adds 9 role weights, 21 parameters in all, about 188 pairs per parameter. The pairs share their units, so the honest count is nearer 2.5 units per parameter.

Ridge λSingle ρRoles ρΔρ (SE over 5 folds)Lists betterRoles, all 7 lines freeSingle pairwiseRoles pairwiseScrambled letters: Δρ mean, SD (3 shuffles)
0.010.5460.546−0.000 (0.006)3 of 50.54774.3%74.1%+0.025, 0.023
0.0010.5390.560+0.021 (0.008)4 of 50.56374.2%75.0%+0.011, 0.032
0.00010.5400.563+0.023 (0.009)4 of 50.55774.2%75.2%+0.011, 0.042

References: the step-6 weights, unfitted, give ρ 0.565 (pairwise 75.1%), but they were tuned on these five lists. Every weight at 1 gives ρ 0.347.

List held outGradedλ 0.01: single → rolesλ 0.001: single → rolesλ 0.0001: single → rolesStep-6
Auspex440.640 → 0.6210.645 → 0.6690.652 → 0.6740.662
Hivemind450.604 → 0.5950.604 → 0.5970.605 → 0.5930.622
Second410.556 → 0.5600.529 → 0.5510.525 → 0.5570.587
Maelstrom480.552 → 0.5690.544 → 0.5850.541 → 0.5850.571
Astrategas480.378 → 0.3850.374 → 0.4000.378 → 0.4040.381

Reading it.

The weights fitted on all five lists (bold: freed for that role; the rest held at the single fit):

λLedgerKillSoakScoreActionsHoldPresenceSpawn
0.01Single36816.210.11.3019.22.7414.8
Big and tough36516.210.11.3019.21.9514.8
Bodies on objectives31216.210.41.3018.42.7414.8
Cheap action pieces22616.210.11.2919.22.7414.8
0.001Single75.922.57.230.7800.700
Big and tough61.822.57.230.87000
Bodies on objectives57.522.55.180.7800.700
Cheap action pieces016.57.230.4300.700

With a weak ridge the single ledger itself drops Hold and Spawn and halves Kill against Soak (the weights' overall size is set only by the ridge; read them against each other). With every line free, the roles' weights wander further (in the JSON) for no held-out gain.

Side note: all 16 armies (done before the scope change)

Before Jordan narrowed the scope, the full brief had been run on all 16 armies (733 units, 26 lists). It had a smoke test and its own results; node tools/roles.js --all reproduces it (312 s). In short:

Characters are a role across armies but not within Tyranids, whose characters are mostly monsters. Damage is again not a role.

Method

Rates and counts take log(1 + x). Every feature is then standardised within the army.