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.
127 lines
4.1 KiB
TypeScript
127 lines
4.1 KiB
TypeScript
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/);
|
||
});
|