5 Oct 2026. tools/typical.js, tests/unit/typical.test.js. A preview: no ledger file, benchmark or page changes. Not measured: the exam comes after T-673, one change at a time.
In plain English
The ledger ranks every unit at its single best version: the best squad size, loadout and detachment. Reviewers grade the plain unit as people usually field it. Carnifexes show the gap: under Track B they rank 20th of 52 because the ranked version is a pair in Subterranean Assault (Value 921), while one Carnifex with its datasheet wargear in a middle-of-the-road detachment is worth 351. This tool works out each unit's typical version and blends it with the best one through game-wide dials, so "rank what's usually fielded" can be tried without touching the ledger. At 0 the ledger is exactly today's (tested to the bit).
Definitions
A unit's forms here are its own solo forms (one member, the unit itself), valued by the ledger page's compute() at the setting. A form can stand for several detachments (the engine groups detachments that give the same numbers); every detachment counts once, 14 for each Tyranid unit.
- Typical squad: the smallest size the unit has, with its datasheet wargear (with or without choices); Auto-equip only where that size has no datasheet form (Hive Tyrant, Winged Hive Tyrant, Harpy, Tyrannofex). No tournament popularity.
- Per detachment d: best(d), the unit's best solo form in d (any size or loadout); typ(d), the typical squad in d.
- Four corners: B today's ranking Value (best squad, best detachment; compute()'s own row); S = max over d of typ(d) (typical squad, best detachment); D = median over d of best(d) (best squad, typical detachment); T = median over d of typ(d) (the typical version).
- Dials
typicalSize(ws) andtypicalDet(wd), 0 to 1;typicalWsets both when they aren't given. Each part blends (1 − w) × best + w × typical on its own axis, so together: Value = B + ws(1 − wd)(S − B) + (1 − ws)wd(D − B) + ws·wd(T − B). typicalSize 1 alone gives S ("only the size goes typical"), typicalDet 1 alone gives D ("only the detachment goes typical"), both give T; at typicalW 0.5 a unit's Value is the mean of its four corners. Written as B plus differences, so 0 is B to the bit and a one-form unit never moves. A unit with no solo form of its own keeps B.
Sanity print: Tyranids at typicalW 0.5
Ranks of 52; "size", "det" are the ranks with only that dial at 0.5. Values per 100 points.
Track B (2026-10-05-trackB.json; mean |Δ rank| 1.96, 38 units move)
| Unit | today | at 0.5 | Δ | size | det | B | S | D | T | Value | reviewers |
|---|---|---|---|---|---|---|---|---|---|---|---|
| Carnifexes | 20 | 25 | −5 | 24 | 20 | 921 | 505 | 768 | 351 | 636 | C C C C C |
| Winged Hive Tyrant | 8 | 9 | −1 | 8 | 9 | 1199 | 1199 | 999 | 999 | 1099 | A B B B B |
| Hive Tyrant | 5 | 5 | 0 | 5 | 5 | 1341 | 1341 | 1205 | 1205 | 1273 | S A S A A |
| Old One Eye | 21 | 19 | +2 | 18 | 22 | 917 | 917 | 744 | 744 | 830 | B B A B C |
| Neurotyrant | 39 | 32 | +7 | 36 | 34 | 472 | 472 | 470 | 470 | 471 | A – – A B |
| Hierophant | 32 | 27 | +5 | 29 | 31 | 601 | 601 | 549 | 549 | 575 | D C C D – |
| The Red Terror | 22 | 18 | +4 | 19 | 21 | 898 | 898 | 778 | 778 | 838 | A A S – – |
| Neurolictor | 11 | 8 | +3 | 10 | 8 | 1121 | 1121 | 1079 | 1079 | 1100 | S S S – S |
| Hormagaunts (×10) | 10 | 23 | −13 | 20 | 14 | 1141 | 599 | 726 | 453 | 730 | S A B A S |
| Pyrovores (×1) | 30 | 37 | −7 | 38 | 27 | 659 | 193 | 642 | 155 | 412 | A – – B B |
| Hive Guard (×3) | 28 | 34 | −6 | 33 | 29 | 692 | 342 | 536 | 277 | 462 | A B A C C |
| Zoanthropes (×3) | 15 | 20 | −5 | 21 | 15 | 1010 | 650 | 849 | 471 | 745 | S A B B B |
At typicalW 1: Carnifexes 20 → 35 (size alone 31, detachment alone 20), Winged Hive Tyrant holds 8, Hive Tyrant 5 → 4.
Track A (2026-10-05-step34b-baseline.json; mean |Δ rank| 1.00, 26 units move)
| Unit | today | at 0.5 | Δ | size | det | B | S | D | T | Value |
|---|---|---|---|---|---|---|---|---|---|---|
| Carnifexes | 32 | 32 | 0 | 32 | 32 | 840 | 840 | 757 | 691 | 782 |
| Winged Hive Tyrant | 3 | 4 | −1 | 3 | 4 | 3273 | 3273 | 2783 | 2783 | 3028 |
| Hive Tyrant | 2 | 2 | 0 | 2 | 2 | 3536 | 3536 | 3219 | 3219 | 3378 |
| Old One Eye | 13 | 13 | 0 | 13 | 13 | 1823 | 1823 | 1668 | 1668 | 1746 |
| Neurotyrant | 30 | 25 | +5 | 30 | 26 | 948 | 948 | 942 | 942 | 945 |
| Hyperadapted Raveners | 9 | 7 | +2 | 9 | 7 | 2240 | 2240 | 1937 | 1937 | 2089 |
| Hormagaunts | 18 | 30 | −12 | 22 | 22 | 1274 | 893 | 782 | 619 | 892 |
| Termagants | 38 | 43 | −5 | 43 | 38 | 591 | 420 | 514 | 363 | 472 |
| Tyranid Prime with Lash Whip | 7 | 9 | −2 | 7 | 9 | 2269 | 2269 | 1871 | 1871 | 2070 |
Under Track A the ranked Carnifex is already a single one (B = S); the detachment half takes 10% off them as off most units.
Reading it. The size half does the work on units with two sizes (Hormagaunts, Pyrovores, Hive Guard, Zoanthropes, Carnifexes): their best version is the bigger squad. The detachment half takes 10 to 25% off nearly every unit, so it moves ranks only where a unit's best detachment stands far above its median. The Winged Hive Tyrant (1199 → 999 on B) is less of an outlier than expected: every unit has one good detachment. Units the same everywhere (Neurotyrant) rise.
Predictions (written before any measurement)
- Tyranids, Track B, typicalW 0.5: toward the reviewers: Carnifexes down, Neurotyrant, Red Terror, Neurolictor up; away: Hormagaunts (S/A) 10 → 23, Pyrovores, Hive Guard, Zoanthropes down, Hierophant up. Five lists −1.0 to +0.5, most likely −0.3. Track A: −0.5 to +0.3.
- The exam, 14 armies, Track B: single-detachment stars and big-squad picks fall, units good everywhere hold; −0.5 to +1.0, most likely +0.2. The detachment dial alone likely does better than the size dial alone.
- Keep: not kept about 65%; kept as a small-strength dial about 30%; confirmed about 5%.
Doubts
- The smallest size isn't typical for every squad (Hormagaunts and Termagants are often fielded at 20), and the datasheet alone gives no better rule without popularity. The size dial may push hordes away from the reviewers.
- The median counts every detachment the same, including ones nobody plays the unit in.
- B can be a non-solo row when leadersLeading, joinedMarginal or leaderCredit are on; S, D, T stay solo. On Tracks A and B the row is the best solo form, so S, D ≤ B and T ≤ both (the test checks it). Bands aren't recut (the exam reads values).
Scoring it, and the exam hook
score(bench, dials, { w, armies, withApart }) runs exam.js's armies through compute() and the dials and scores with exam.js's own pairTable and weighted (checked against ledgerstep.js shape() to 1e-12). At 0 it reproduces exam.js to the bit: Track A 59.43, Track B 59.43 on the 14 armies (59.41 and 60.08 with Votann pooled); Tyranids 79.26 and 77.68. It lacks the paired bootstrap, because exam.js's army() computes the values inside it. The hook: export army and boot from exam.js, and let army(A, settings, w, post) take an optional post(run, D, S) applied to each compute() result before values are read; here post = (run, D, S) => typical.adjust(run, D, S.typical), a setting carrying its typical dials beside sliders. Existing calls pass no post and are unchanged.
Run: node tools/typical.js [--bench <setting>] [--w 0.5] [--size x] [--det x] [--units a,b] [--md].