Files
Dune/tests/independent-discovery-cards.test.ts
T
Quintine 677ad7aa3c Compose independent cards with Discovery and optional Nexus
Connect original selected-deck Discovery setup, optional Nexus/World/Skills/phase modules and physical card consumers through shared public capabilities. Retain Cunning revealed-location arrival, Sandtrout suppression, paid nested R/Harass and actual later Recruits revival. Preserve native trained-capture and unrelated-preview boundaries.

Validation: frozen 15-file118/118, types/lint/build and six finite original-action/rendered-control programmes; four minimal legal policies. External smoke corrections changed no application source. Existing persistent notices also rechecked23/23. Full combinations, assurance and deployed acceptance remain open; no saved-game, restart or deployment operation.
2026-10-18 11:48:45 +11:00

97 lines
7.1 KiB
TypeScript

import assert from 'node:assert/strict';
import test from 'node:test';
import { viewGame, type Game } from '../game/engine';
import { independentDiscoveryNexusProfile } from '../game/faction-module-profile';
import { validateDiscoveryState } from '../game/discoveries';
import { NEXUS_FACTIONS } from '../game/nexus-cards';
import {
createIndependentDiscoveryCardsFixture, completeIndependentDiscoveryCards,
completeIndependentDiscoverySandtrout, independentDiscoveryCardsPlayer as player,
} from './fixture-independent-discovery-cards';
function originalModules(game: Game): void {
assert.ok(independentDiscoveryNexusProfile(game));
assert.ok(game.ecazTreachery && game.discoveryEnabled && game.discoveries && game.nexusCards?.cards);
validateDiscoveryState(game.discoveries);
assert.equal(game.discoveries.tokens.length, 8);
const cards = game.nexusCards.cards;
assert.deepEqual([...cards.deck, ...cards.discard, ...Object.values(cards.hands).filter(card => card !== null)].sort(), [...NEXUS_FACTIONS].sort());
}
for (const modules of [false, true]) void test(`${modules ? 'Advanced six seats across three families with World/all14/phase' : 'Basic classic independent'} original Discovery reveal and next-turn entry precede native Fremen Cunning into its revealed nested location`, () => {
const fixture = createIndependentDiscoveryCardsFixture({ advanced: modules,
expansions: modules ? ['ix', 'choam', 'ecaz'] : [],
factions: modules ? ['fremen', 'guild', 'emperor', 'ixians', 'choam', 'ecaz'] : ['fremen', 'guild', 'emperor'],
skills: modules, homeworlds: modules, tech: modules, strongholds: modules });
originalModules(fixture.afterSetup);
assert.equal(!!fixture.afterSetup.homeworlds, modules);
assert.equal(!!fixture.afterSetup.leaderSkills, modules);
assert.equal(!!fixture.afterSetup.techTokens, modules);
assert.equal(!!fixture.afterSetup.strongholdCards, modules);
assert.equal(viewGame(fixture.beforeInspection, 'guild').discoveries!.tokens.find(token => token.id === fixture.token)!.face, null);
assert.equal(viewGame(fixture.inspected, 'guild').discoveries!.tokens.find(token => token.id === fixture.token)!.face, 'cistern');
assert.equal(viewGame(fixture.inspected, 'emperor').discoveries!.tokens.find(token => token.id === fixture.token)!.face, null);
assert.equal(viewGame(fixture.beforeInspection, 'fremen').discoveries!.tokens.find(token => token.id === fixture.token)!.face, 'cistern',
'Native Fremen already knows the Hiereg face.');
assert.equal(viewGame(fixture.revealed, 'fremen').discoveries!.tokens.find(token => token.id === fixture.token)!.face, 'cistern');
assert.equal(fixture.revealed.discoveries!.tokens.find(token => token.id === fixture.token)!.revealedTurn, 1);
assert.equal(fixture.game.turn, 2);
assert.equal(fixture.game.phase, 0);
assert.equal(fixture.game.decision?.kind, 'discoveryEntry');
const guild = fixture.shipments.find(shipment => shipment.actor === 'guild'); assert.ok(guild);
assert.equal(player(guild.after, 'guild').reserves, player(guild.before, 'guild').reserves - 3);
assert.equal(player(guild.after, 'guild').spice, player(guild.before, 'guild').spice - 3);
const done = completeIndependentDiscoveryCards(fixture);
assert.equal(player(done.entered, 'guild').forces['cistern:0'], 1);
assert.equal(player(done.entered, 'guild').forces[`${fixture.parent}:${fixture.parentSector}`], player(done.entryBefore, 'guild').forces[`${fixture.parent}:${fixture.parentSector}`] - 1);
assert.equal(player(done.entered, 'guild').spice, player(done.entryBefore, 'guild').spice);
assert.equal(player(done.entered, 'guild').moved, player(done.entryBefore, 'guild').moved);
assert.equal(player(done.entered, 'guild').shipped, player(done.entryBefore, 'guild').shipped);
assert.equal(done.drawn.nexusCards!.cards!.hands.fremen, 'fremen');
assert.equal(player(done.drawn, 'guild').ally, 'emperor');
assert.equal(player(done.drawn, 'fremen').ally, null);
assert.equal(done.occurrence.initiallyEmpty, true);
assert.equal(done.occurrence.territory, 'red_chasm');
assert.equal(done.occurrence.origin, 'natural');
assert.equal(done.beforeSpend.spice['red_chasm:7'], 8);
assert.equal(done.spent.nexusCards!.cards!.hands.fremen, null);
assert.equal(done.spent.nexusCards!.cards!.discard.filter(card => card === 'fremen').length, 1);
assert.equal(done.beforeRide.spice['red_chasm:7'] ?? 0, 0);
assert.equal(done.beforeRide.decision?.kind, 'nexusFremenCunningRide');
assert.equal(player(done.afterRide, 'fremen').forces['cistern:0'], 3);
assert.equal(player(done.afterRide, 'fremen').elites?.forces['cistern:0'] ?? 0, modules ? 2 : 0);
assert.equal(player(done.afterRide, 'fremen').forces[fixture.sourceKey] ?? 0, player(done.beforeRide, 'fremen').forces[fixture.sourceKey] - 3);
assert.deepEqual(player(done.afterRide, 'guild').forces, player(done.beforeRide, 'guild').forces);
assert.equal(player(done.afterRide, 'fremen').reserves, player(done.beforeRide, 'fremen').reserves);
assert.equal(player(done.afterRide, 'fremen').moved, player(done.beforeRide, 'fremen').moved);
assert.equal(player(done.afterRide, 'fremen').shipped, player(done.beforeRide, 'fremen').shipped);
assert.equal(player(done.afterRide, 'fremen').spice, player(done.beforeRide, 'fremen').spice);
assert.equal(done.afterRide.nexusFremenCunningRides!.find(ride => ride.occurrence.event === done.occurrence.event)!.stage, 'complete');
originalModules(done.game);
});
void test('actual selected Ix Sandtrout breaks the earned alliance and suppresses its next worm without losing independent Discovery or native Fremen card custody', () => {
const fixture = createIndependentDiscoveryCardsFixture({ expansions: ['ix'], factions: ['fremen', 'guild', 'emperor'] });
const done = completeIndependentDiscoverySandtrout(fixture);
assert.equal(player(done.before, 'guild').ally, 'emperor');
assert.equal(player(done.before, 'emperor').ally, 'guild');
assert.equal(done.before.nexusCards!.cards!.hands.fremen, 'fremen');
assert.ok(done.doubled.players.every(player => player.ally === null));
assert.equal(done.doubled.sandtrout, false);
assert.equal(done.doubled.nexus, false);
assert.equal(done.doubled.nexusFremenCunningOffer ?? null, null);
assert.equal(done.doubled.nexusFremenCunningRides?.length ?? 0, done.before.nexusFremenCunningRides?.length ?? 0);
assert.equal(done.doubled.spiceWindow!.amount, 12);
assert.deepEqual(done.doubled.spiceDiscard[0].slice(-3), [
{ sandtrout: true }, { worm: true, suppressed: true }, { territory: 'sihaya_ridge', amount: 6, sector: 9 },
]);
assert.equal(done.doubled.nexusCards!.cards!.hands.fremen, 'fremen');
assert.equal(done.doubled.nexusCards!.cards!.discard.filter(card => card === 'fremen').length, 0);
assert.deepEqual(player(done.doubled, 'fremen').forces, player(done.before, 'fremen').forces);
assert.equal(player(done.doubled, 'fremen').tanks, player(done.before, 'fremen').tanks);
assert.equal(done.doubled.spice['red_chasm:7'], done.before.spice['red_chasm:7']);
assert.equal(done.game.phase, 2);
assert.equal(done.game.nexusCards!.phase!.occurred, false);
assert.equal(done.game.spice['sihaya_ridge:9'], 12);
originalModules(done.game);
});