Written overnight on 25 Sep for Jordan to read with coffee. Nothing here is built yet: the engine work goes in its own PR that waits for your ok.
Clickable mockup (live numbers from a small copy of the planned engine): https://claude.ai/artifact/QSyqparVA3nHMxGM6gEtGM (private to Jordan; kept out of the repo because it carries statlines).
What we're building
A character can join a squad, on either side of the calculator, and the maths plays it out the way a game would. When your Captain is with the Intercessors, his weapons fire with theirs and his buffs apply to the whole unit. When the enemy's Captain is with their Intercessors, your shots hit the squad first, the Captain goes last, and Precision weapons can pick him out.
The defending side is the part that sets us apart. The calculators we've seen treat the target as one statline, so a Warlord in a bodyguard unit either doesn't exist or soaks shots like a trooper.
How joined units work in 11th edition (our summary)
Checked against the 11e core rules on Wahapedia; the wording below is ours.
- Who can join whom. A datasheet with the Leader or Support ability lists the squads it can join. Before the game, each leader or support unit may join one eligible squad. A squad takes at most one leader and one support unit. The joined unit then counts as one unit for every rule.
- Toughness. Attacks use the best Toughness among the squad's own models. Only once the squad's models are all dead does the leader's Toughness count. This matters: a tough leader in a softer squad is wounded at the squad's Toughness while the squad is alive.
- Keywords. The joined unit has every keyword of every model still alive in it. Anti-Psyker triggers against the whole unit while a Psyker leader lives, even on shots that land on the squad.
- Abilities. A unit-wide ability, like a squad's Feel No Pain or a leader's "while leading, the unit gets +1 to hit", covers every model in the joined unit until the last model of the unit it came from dies. Things tied to one model (an enhancement, a piece of wargear) stay with that model.
- Who takes the hits. This is the heart of it, and 11e changed it:
- The defender splits the unit into groups: each character is its own group, and the other models group by matching Wounds, Save and invulnerable save.
- Groups are ordered: a squad group with a wounded model goes first; no character goes before the squad groups; wounded characters go before unwounded ones.
- All the save dice are rolled at once, then resolved from the lowest roll up, each against the group currently in front. When that group is wiped out, the next one steps up. A 1 always fails. Damage goes on an already-wounded model where possible.
- Feel No Pain is rolled as each hit's damage lands. When the whole unit is dead, any leftover hits are lost.
- Precision. A Precision weapon can pick a visible character's group as the one in front, until those attacks are done or the character dies. Devastating Wounds from Precision attacks go to that character first too.
- Devastating Wounds and mortal wounds. A critical wound from a Devastating Wounds weapon skips the save. Its damage lands as mortal wounds after the normal damage, and each crit can kill at most one model. Mortal wounds go to squad models before characters.
What the data gives us
- BSData knows who can join whom. Every leader and support unit has a "Leading" or "Supporting" link naming the squads it can join, by keyword (e.g. Battle Sisters Squad). There are about 2,100 of these links across all factions.
bsdata.jscan read them in the browser into aleadslist on each unit record, so we host nothing new. A few leaders can only join certain squads depending on their wargear (a Captain with a jump pack, say); we'll take the union and let the player choose, which is fine for a calculator. - Most tournament lists say who's attached. Of the 80 winning lists we show, 58 name their joined units in a form we can read:
- the GW app's "Attached Unit 1…" blocks: 27 in English, 19 in French;
- New Recruit's "Leading …" / "Attached to …" lines: 12.
Another 4 describe it loosely; 18 don't say. So the importer can join units automatically most of the time, and the player can always fix or add a join by hand.
- New Recruit's JSON export probably records the joins too, since the "Leading" links are its feature. We need one real export with a leader in it to confirm (see questions below).
- Our unit records already have per-model statlines (
models), Feel No Pain, keywords, and each unit's default wargear, which is everything the defending groups need.
Where it shows up
It's not only for uploaded lists. The calculator works from BSData, so it knows every datasheet's eligible leaders.
- Calculator, attacking side: under the unit picker, a small + Leader chip (and + Support where the faction has them). It opens a list of the characters that can join this squad. Pick one and the card reads "Captain + Intercessor Squad", the leader's weapons join the weapon list (each can be switched off as now), and the leader's buffs appear as modifier chips you can see and untick.
- Calculator, defending side: the same chip. The answer card then splits kills into "squad" and "leader", e.g. "4.1 Intercessors, Captain dies 12% of the time". A Precision chip appears in the attack modifiers whenever the target has a character.
- Army vs Army: uploaded lists join units automatically where the list says so. A joined unit is one row ("Captain + Intercessors"). A leader who isn't joined gets a small "join…" menu of its eligible squads in that list. Both sides use the joined units for attacking and defending.
- The volley animation: the leader is a distinct figure at the back of the target group; shots land on the squad first and on the leader last (or first, with Precision).
The engine
All in app.js for the calculator, mirrored in ev.js for the comparator, tested against each other as today.
- The target becomes a list of groups instead of one statline:
[{ n, w, sv, inv, fnp, character, name }], in allocation order. A plain squad is one group, so today's behaviour is the one-group case and every existing test must still pass unchanged. - Toughness per weapon: the best squad Toughness while any squad model lives, else the leader's. Weapons fire in turn (
rollWeapons), so a later weapon can be wounding the leader at his own Toughness. - Saves: roll one die per wound for the whole weapon, sort them lowest first, and resolve each against the group in front at that moment (its Sv, invulnerable save and the weapon's AP). This replaces today's "roll each save against one target number". With one group it gives exactly the same odds.
- Damage: onto the wounded model in the front group if there is one, with that group's W and FNP, plus any unit-wide FNP still in play.
- Devastating Wounds: mortal wounds after normal damage, at most one model per crit, squad first (or the Precision character).
- Precision: puts the chosen character's group in front for that weapon's attacks.
- Keywords: Anti-X checks the joined unit's live keywords.
- Volley tracks: the existing
modelfield already says which model each hit lands on; it gains agroupso the animation can draw the leader. - Army vs Army's exact averages (
ev.jskillDP): the save dice can still be handled exactly. Walk the die faces 1 to 6 in order: the number of wounds showing each face is a binomial draw from those left. The state is (wounds left to resolve, group in front, models slain in it, wounds left on the model being hit): a few thousand states per weapon, fast enough for a whole army.
Tests: today's one-group numbers unchanged; a Captain in a squad survives until the squad dies; Precision kills the Captain first; toughness switches when the squad dies; lowest-first saves against a 2+/4++ leader in a 3+ squad match a hand-worked case; ev.js averages match the dice engine within noise for joined targets; importer tests for all three list formats.
Leader abilities (the data part)
Leader buffs ("while leading, the unit re-rolls hits of 1", "+1 to wound against the enemy closest") are only in GW's text. We don't host that, so like detachment rules (detfx.json) we write each one as a small dice effect in our own words: docs/data/leaderfx.json, keyed by BSData unit id, reusing detfx.js's effect vocabulary (hit, wound, crit, lethal, sustained, re-rolls, AP, damage, FNP, invulnerable save…). Conditional ones ("after charging") become a chip that's on by default and can be unticked, as detachment rules do now.
Order: the factions in the most winning lists first (Necrons, Custodes, Tyranids, T'au, Emperor's Children, Space Wolves, Mechanicus, Orks, Sisters, CSM), then the rest. A leader with no entry yet still joins and fights. Its buffs just aren't applied, and the card says so.
Build order (all built overnight in PR #58, waiting for Jordan)
- ✅
bsdata.js: theleads/supportslists (434 leaders, 55 support characters) andBSData.canJoin(). - ✅ Engine: groups, lowest-first saves, Toughness switch, Precision, Devastating Wounds.
- ✅ Calculator: + Leader / + Support on both sides, the leader line on the answer card, Precision as a modifier, joins kept in share links.
- ✅ Importer: GW app (English and French) and New Recruit text joins. 56 of the 80 winning lists now come joined. New Recruit JSON is still waiting on an example export.
- ✅ Army vs Army: joined rows, the Join menu, Split up, 👑 on each row.
ev.jssimulates joined targets with a fixed seed: there's no closed form once saves go lowest first. - ✅
leaderfx.json: 135 characters across 23 factions (the most-played first), then 225 more in T-147 (361 in all). The 63 Wahapedia doesn't list are T-151. - ✅ The volley draws the leader behind his squad, crowned.
Built differently from the plan:
- Army vs Army uses a seeded simulation, not an exact DP. Tracking every group's state through sorted save dice came to over a billion steps per matchup. The simulation matches the calculator's dice engine in tests.
- The calculator's leaders fire their datasheet's default wargear. There's no editing of a leader's weapons yet.
- The mathhammer guide gained a short section on joined units.
Simplifications
- Allocation order where the rules let the defender choose (two wounded squad groups, say): we pick the one that keeps the character alive longest, which is almost always what a player does.
- Wargear-dependent eligibility is merged, as above.
- "Visible" for Precision is assumed.
- Abilities that trigger on a model dying, or that move wounds around (bodyguard "Look out, sir"-style datasheet rules), come later via
leaderfx.jsonwhere they're dice effects at all. - A leader's buffs end when it dies in the calculator (T-148): the next save die or weapon meets the unit without them. Army vs Army keeps them for the whole attack.
- Unit-wide Feel No Pain covers every model of the joined unit.
- Added in T-148: a Save characteristic given to the unit (
sv), one model's own -1 Damage or halved Damage (selfDmgMinus,selfHalfDmg, also when the character is the target on its own), Feel No Pain against mortal wounds or psychic attacks only (fnpMortal,fnpPsychic; mortal wounds here are Devastating Wounds), ignoring the minuses to hit and to BS/WS, cover's among them (ignHitMods), and critical wounds on N+ (critWound).
Questions for Jordan
- Precision default: when the attacker has Precision and the target has a leader, aim at the leader automatically? (Recommended: yes, with the chip to switch it off.)
- A New Recruit JSON export of any list with a leader attached, so the importer can read joins from it too.
- Leader buffs: on by default when their condition is situational (like detachment rules), or off until ticked?