Files
Dune/tests/leader-skill-cards.test.ts
T
Quintine 6e6fecc9d4 Prototype Leader Skills setup, battle disciplines and capture
Connect all fourteen physical skills and private setup to five card-role battle
modifiers, public posture, Harkonnen skilled capture, death return and optional
own-leader revival through controls, legal AI and persisted room actions.
Nine skill effects and combined-mode/publication acceptance remain unfinished.
Captured replacement entitlement and foreign ghola integration stay guarded.

Fresh publisher/designer research supports skilled-capture publicity through
its unique known physical card; this is recorded as a component inference.
Independent review found and fixed Voice/must-use-leader and invalid saved
assignment/offer cases. A pre-existing Discovery privacy assertion confused
card ID prefixes; the corrected deterministic physical-pair scenario was
independently reviewed. The first broad run was 4410/4411; corrected final
verification passes types/lint, all 4411 offline tests, build and 43 HTTP tests.
Final durations: check 149741ms, build 6659ms, integration 28274ms.
Source fingerprint: aef655ac1ebc0eb2f64294e13d3d27337167312cf8ab17568a166ee9c39f6a85.
Private final report: 20260913-leader-skills/checks-final/report.json.

Three seeded prototype games completed with no rejected bot candidates. Browser
setup/card inspection/refresh passed. Another user-reported power outage stopped
the server; backup and necessary restart restored the same private seat. All
292 games at restart and their original application records remain unchanged;
33 isolated HTTP-test rooms bring the current total to 325. No database reset.

Efficiency: one shared setup/lifecycle implementation supports five connected
role effects; selective existing agents handled catalog/UI/tests/research and
independent reviews. One repeated broad check was required by the reproduced
ID-prefix assertion failure. Aggregate goal counter rose from 65529766 to
67313673 through this checkpoint (1783907), including inherited context and
coordination; this is not a billing or savings estimate. Full goal remains active.
2026-09-13 17:31:37 +10:00

127 lines
4.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import test from 'node:test';
import assert from 'node:assert/strict';
import {
LEADER_SKILL_CARDS,
leaderSkillCard,
type LeaderSkillId,
} from '../game/leader-skill-cards';
const EXPECTED_IDS: readonly LeaderSkillId[] = [
'bureaucrat',
'spice-banker',
'diplomat',
'mentat',
'suk-graduate',
'rihani-decipherer',
'sandmaster',
'smuggler',
'planetologist',
'warmaster',
'master-of-assassins',
'swordmaster-of-ginaz',
'killer-medic',
'prana-bindu-adept',
];
void test('the canonical inventory has exactly fourteen unique physical identities', () => {
assert.equal(LEADER_SKILL_CARDS.length, 14);
assert.deepEqual(
LEADER_SKILL_CARDS.map((card) => card.id),
EXPECTED_IDS,
);
assert.equal(new Set(LEADER_SKILL_CARDS.map((card) => card.id)).size, 14);
assert.equal(new Set(LEADER_SKILL_CARDS.map((card) => card.name)).size, 14);
for (const card of LEADER_SKILL_CARDS) {
assert.deepEqual(Object.keys(card), ['id', 'name', 'normal', 'battle']);
assert.ok(card.normal.length > 0);
assert.ok(card.battle.length > 0);
assert.ok(card.normal.every((line) => line.length > 20));
assert.ok(card.battle.every((line) => line.length > 20));
assert.match(card.id, /^[a-z]+(?:-[a-z]+)*$/);
}
});
void test('lookup is total for canonical IDs and rejects unknown runtime input', () => {
for (const card of LEADER_SKILL_CARDS)
assert.equal(leaderSkillCard(card.id), card);
assert.throws(
() => leaderSkillCard('not-a-physical-skill' as LeaderSkillId),
/Unknown Leader Skill Card/,
);
});
void test('the four weapon and defense disciplines preserve their distinct roles and values', () => {
const roles = [
['master-of-assassins', 'Poison Weapon'],
['swordmaster-of-ginaz', 'Projectile Weapon'],
['killer-medic', 'Poison Defense'],
['prana-bindu-adept', 'Projectile Defense'],
] as const;
for (const [id, role] of roles) {
const card = leaderSkillCard(id);
assert.match(card.normal.join(' '), new RegExp(`Add 1.*other.*${role}`));
assert.match(card.battle.join(' '), new RegExp(`Add 3.*${role}`));
}
});
void test('named economic, custody and information effects retain their source numbers', () => {
assert.match(
leaderSkillCard('bureaucrat').normal.join(' '),
/per phase.*at least 5.*2.*Spice Bank/,
);
assert.match(
leaderSkillCard('spice-banker').normal.join(' '),
/per phase.*at least 4.*1 spice.*Mentat Pause/,
);
assert.match(
leaderSkillCard('spice-banker').battle.join(' '),
/1–3.*separate.*full strength/,
);
assert.match(
leaderSkillCard('rihani-decipherer').battle.join(' '),
/draw 2 Traitor Cards.*unused Traitor Card.*unkept drawn card/,
);
assert.match(
leaderSkillCard('mentat').normal.join(' '),
/specific weapon.*different Treachery Card.*does not require/,
);
});
void test('movement and battle-loss cards remain materially different', () => {
assert.match(
leaderSkillCard('planetologist').normal.join(' '),
/capped at 3.*two different territories.*one destination/,
);
assert.match(
leaderSkillCard('planetologist').battle.join(' '),
/green Special.*other than a Cheap Hero.*add 2.*Discard/,
);
assert.match(
leaderSkillCard('smuggler').normal.join(' '),
/off-planet.*empty territory.*additional force.*free/,
);
assert.match(
leaderSkillCard('sandmaster').normal.join(' '),
/into or through.*1 spice.*once per territory/,
);
assert.match(
leaderSkillCard('diplomat').battle.join(' '),
/lose.*undialed forces.*empty adjacent.*not a stronghold/,
);
assert.match(
leaderSkillCard('suk-graduate').normal.join(' '),
/return 1.*reserves.*instead of the Tleilaxu Tanks/,
);
assert.match(
leaderSkillCard('suk-graduate').battle.join(' '),
/up to 3.*leave 1.*return the rest/,
);
});
void test('Warmaster uses one Worthless card for either nonstacking band', () => {
const warmaster = leaderSkillCard('warmaster');
assert.match(warmaster.normal.join(' '), /Add 1.*other.*at least one/);
assert.match(warmaster.battle.join(' '), /Add 3.*at least one/);
assert.doesNotMatch(JSON.stringify(warmaster), /at least two|Add 4/);
});