Files
Dune/tests/selected-independent-nexus-entry.test.ts
T
Quintine 8793e23d6b Compose selected E3 independent cards with Nexus rules
Selected Ecaz/Moritani decks with independent cards and Nexus now retain native Occupy, Advanced assassination, arrival ordering and shared Duke consumers, including original optional modules. Keep the trained E3/Harkonnen capture boundary and remaining individual Nexus effect limits explicit.

Validation: frozen 17-file 168/168, types/lint/build, seven finite original-action and table-SSR programmes, plus four minimal legal Cunning/expiry policies. Fixture corrections preserve the printed alliance draw gate and real paid placement affordability. No stored-game or production change; comprehensive and deployed acceptance remain open.
2026-10-18 10:59:57 +11:00

80 lines
6.0 KiB
TypeScript

import test from 'node:test';
import assert from 'node:assert/strict';
import { selectedIndependentNexusProfile, nexusCoreProfile } from '../game/faction-module-profile';
import { createTechTokens } from '../game/tech-tokens';
import { createSelectedIndependentEntry, type SelectedIndependentEntryOptions } from './fixture-selected-independent-nexus-entry';
const entries: SelectedIndependentEntryOptions[] = [
{expansions:['ix'], factions:['ixians','tleilaxu','atreides']},
{expansions:['choam'], factions:['richese','choam','guild'], advanced:true, skills:true, homeworlds:true, tech:true, strongholds:true},
{expansions:['ix','choam'], factions:['ixians','richese','harkonnen','atreides'], advanced:true, skills:true, homeworlds:true, tech:true, strongholds:true},
{expansions:['ix','choam'], factions:['atreides','guild']},
{expansions:['choam'], factions:['emperor','atreides'], advanced:true, homeworlds:true, strongholds:true},
{expansions:['ix'], factions:['tleilaxu','guild'], homeworlds:true},
{expansions:['ecaz'], factions:['ecaz','moritani']},
{expansions:['ecaz'], factions:['moritani','harkonnen'], advanced:true},
{expansions:['ecaz'], factions:['ecaz','moritani','guild'], advanced:true, skills:true, homeworlds:true, tech:true, strongholds:true},
{expansions:['ecaz','ix','choam'], factions:['ecaz','moritani','ixians','tleilaxu','choam','richese'], advanced:true, skills:true, homeworlds:true, tech:true, strongholds:true},
{expansions:['ecaz','ix','choam'], factions:['atreides','guild']},
{expansions:['ecaz','choam'], factions:['ecaz','emperor'], advanced:true, skills:true, strongholds:true},
];
for (const options of entries) void test(`normal independent Nexus entry ${options.expansions.join('+')}/${options.factions.join('+')} retains selected components`, () => {
const {lobby, setup, game} = createSelectedIndependentEntry(options);
assert.equal(lobby.lobbyOptions?.ecazTreachery, true);
assert.equal(lobby.nexusCards?.cards, null);
assert.equal(game.ecazTreachery, true);
assert.deepEqual(game.expansions, options.expansions);
assert.equal(nexusCoreProfile(game), true);
assert.equal(selectedIndependentNexusProfile(game), true);
assert.equal(!!game.leaderSkills, !!options.skills);
assert.equal(!!game.homeworlds, !!options.homeworlds);
assert.equal(!!game.techTokens, !!options.tech);
assert.equal(!!game.strongholdCards, !!options.strongholds);
assert.equal(game.phase, 0);
assert.equal(game.nexusCards!.cards!.deck.length, 12);
assert.ok(Object.values(game.nexusCards!.cards!.hands).every(card => card === null));
const ordinary = game.deck.length + game.discard.length + game.players.reduce((sum, player) => sum + player.hand.length, 0);
assert.equal(ordinary, options.expansions.includes('ix') ? 50 : options.expansions.includes('choam') ? 38 : 36);
assert.equal(game.richeseCache?.length ?? 0, options.factions.includes('richese') ? 10 : 0);
assert.equal(!!game.nexusIxianReplacementPreview, !options.factions.includes('ixians'));
assert.equal(!!game.nexusIxianBetrayalPreview, options.factions.includes('ixians'));
assert.equal(!!game.nexusHarkonnenBetrayalPreview, options.factions.includes('harkonnen'));
assert.equal(!!game.guildBetrayalPreview, options.factions.includes('guild'));
assert.equal(!!game.ecazOccupyPreview, options.factions.includes('ecaz'));
assert.equal(!!game.moritaniAssassinatePreview, !!options.advanced && options.factions.includes('moritani'));
assert.equal(!!game.moritaniAssassinate, !!options.advanced && options.factions.includes('moritani'));
if (options.skills) assert.ok(['skillTreachery','leaderSkills'].includes(setup.setupStage!),
'The original Ixian starting-card choice may precede skill training');
if (setup.techTokens) {
assert.ok(Object.values(lobby.techTokens!).every(token => token.owner === null));
assert.deepEqual(setup.techTokens, createTechTokens(setup.players),
'Original setup assigns each seated native its printed Tech token; missing natives wait for Storm');
}
if (game.strongholdCards) assert.ok(Object.values(game.strongholdCards.owners).every(owner => owner === null));
});
void test('selected E3 core retains native assassination stamps and the existing trained capture boundary', () => {
const native = {advanced:true, expansions:['ecaz'], players:[{faction:'moritani'},{faction:'harkonnen'}], nexusCards:true, ecazTreachery:true};
assert.equal(selectedIndependentNexusProfile(native), true);
assert.equal(selectedIndependentNexusProfile({...native, moritaniAssassinatePreview:true}), true);
assert.equal(selectedIndependentNexusProfile({...native, leaderSkills:true}), false);
assert.equal(selectedIndependentNexusProfile({...native, advanced:false, leaderSkills:true}), true);
assert.equal(selectedIndependentNexusProfile({...native, advanced:false, moritaniAssassinatePreview:true}), false);
assert.equal(selectedIndependentNexusProfile({...native, players:[{faction:'ecaz'},{faction:'emperor'}], moritaniAssassinatePreview:true}), false);
assert.equal(selectedIndependentNexusProfile({...native, expansions:['ix']}), false);
assert.equal(selectedIndependentNexusProfile({...native, expansions:['ecaz','ix','choam','ecaz']}), false);
assert.equal(selectedIndependentNexusProfile({...native, expansions:['ecaz','sandtrout']}), false);
});
void test('selected core capability requires real independent cards and selected registered seats', () => {
const base = {advanced:true, expansions:['ix'], players:[{faction:'ixians'},{faction:'atreides'}], nexusCards:true, ecazTreachery:true};
assert.equal(selectedIndependentNexusProfile(base), true);
for (const invalid of [
{...base, ecazTreachery:false}, {...base, expansions:['ix','ix']},
{...base, players:[{faction:'ixians'},{faction:'richese'}]},
{...base, players:[{faction:'ixians'},{faction:'ixians'}]},
{...base, techTokens:true}, {...base, advanced:false, strongholdCards:true},
{...base, discoveryEnabled:true}, {...base, semutaPreview:true},
]) assert.equal(selectedIndependentNexusProfile(invalid), false);
});