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.
80 lines
6.0 KiB
TypeScript
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);
|
|
});
|