302 lines
21 KiB
TypeScript
302 lines
21 KiB
TypeScript
import assert from 'node:assert/strict';
|
|
import test from 'node:test';
|
|
import { applyAction, createGame, joinGame, newPlayer, viewGame, type Action, type Game } from '../game/engine';
|
|
import { botActions } from '../game/bots';
|
|
import { DIFFICULTIES } from '../game/bot-profiles';
|
|
import { presenceAt } from '../game/force-presence';
|
|
import { territory } from '../game/board';
|
|
import { quoteSpiceCollection } from '../game/board-resolution-quote';
|
|
import { reserveShipmentCost } from '../game/shipment-price';
|
|
import { isPlanetologistBattleSpecialCard } from '../game/leader-skill-combat';
|
|
import {
|
|
advancePairedChoamSkills, advancePairedChoamSkillsToPhase, allowPairedChoamSkills,
|
|
assertPairedChoamSkillsCustody as custody, completePairedChoamSkillsSetup,
|
|
createPairedChoamNexusSkillsFixture, finishPairedSkillsBattle, holdPairedSkillsCard,
|
|
initializePairedChoamNexusSkills, openPairedSkillsBattle, pairedSkillsBattlePlans,
|
|
pairedSkillsChoamCunningRequest, pairedSkillsClean, pairedSkillsPhysicalCards,
|
|
pairedSkillsPlayer as player, pairedSkillsRicheseCunningRequest, pairedSkillsSnapshot,
|
|
placePairedSkillsForce, revealPairedSkillsRichese, stagePairedSkillsPlanetologistRoute,
|
|
quotePairedSkillsBattle,
|
|
stepPairedChoamSkills, type PairedChoamNexusSkillsFixture,
|
|
} from './fixture-paired-choam-nexus-skills';
|
|
|
|
function reject(game: Game, actor: string, action: Action): void {
|
|
const before = pairedSkillsSnapshot(game);
|
|
assert.throws(() => applyAction(game, actor, action));
|
|
assert.deepEqual(game, before, 'Illegal composition preserves original spice, cards, markers, forces and skill custody');
|
|
}
|
|
function assertPair(f: PairedChoamNexusSkillsFixture, shipped: Game, faceup: number): void {
|
|
const request = pairedSkillsRicheseCunningRequest(f), before = player(f.game, f.richese), after = player(shipped, f.richese);
|
|
assert.equal(after.spice, before.spice - 1);
|
|
assert.equal(player(shipped, f.guild).spice, player(f.game, f.guild).spice + 1, 'One original one-marker Stronghold tariff, not eight counter invoices');
|
|
assert.equal(after.reserves, before.reserves - faceup);
|
|
assert.equal(after.forces[f.location] ?? 0, faceup);
|
|
assert.equal(after.noField!.deployed!.tokenId, request.noField);
|
|
assert.equal(after.noField!.lastShipped, request.noField);
|
|
assert.equal(presenceAt(after, 'habbanya_ridge_sietch'), faceup + 1, 'The second group is one concealed marker, not extra ordinary force identity');
|
|
assert.equal(shipped.nexusCards!.cards!.hands[f.richese], null);
|
|
assert.equal(shipped.nexusRicheseCunningLast!.stage, 'shipped');
|
|
assert.equal(after.shipped, true);
|
|
assert.deepEqual(shipped.leaderSkills!.assignments, f.game.leaderSkills!.assignments, 'Cunning neither borrows nor fabricates a companion skill');
|
|
assert.deepEqual(pairedSkillsPhysicalCards(shipped), pairedSkillsPhysicalCards(f.afterSetup));
|
|
custody(shipped);
|
|
}
|
|
|
|
for (const advanced of [false, true]) void test(`paired E2 ${advanced ? 'Advanced' : 'Basic'}: actual previous marker, all14 skill assignments, worm/alliance/closing draws and single physical pair invoice`, () => {
|
|
const f = createPairedChoamNexusSkillsFixture({ advanced });
|
|
assert.equal(f.initial.richeseCache!.length, 10);
|
|
assert.equal(pairedSkillsPhysicalCards(f.initial).length, 45, 'Original CHOAM35 plus separate Richese10, not canonical47 or a mixed deck');
|
|
for (const p of f.initial.players) assert.equal(p.hand.length, 1, 'Native starting cards are dealt before original skill offers');
|
|
assert.equal(f.afterSetup.leaderSkills!.assignments.length, 4);
|
|
assert.equal(f.beforeFirstMentat.turn, 1);
|
|
assert.equal(f.afterFirstMentat.turn, 2);
|
|
assert.equal(f.beforeAlliance.nexus, true);
|
|
assert.equal(player(f.beforeClosingDraw, f.guild).ally, f.observer);
|
|
assert.equal(player(f.beforeClosingDraw, f.observer).ally, f.guild);
|
|
for (const draw of f.closingDraws) {
|
|
assert.equal(draw.before.nexusCards!.cards!.hands[draw.step.actor], null);
|
|
assert.equal(draw.after.nexusCards!.cards!.hands[draw.step.actor], player(draw.after, draw.step.actor).faction);
|
|
}
|
|
const previous = player(f.game, f.richese).noField!.tokens.find(t => t.value === 0)!;
|
|
assert.equal(player(f.firstShipment.after, f.richese).spice, player(f.firstShipment.before, f.richese).spice - 1);
|
|
assert.equal(player(f.firstShipment.after, f.richese).reserves, player(f.firstShipment.before, f.richese).reserves);
|
|
assert.equal(player(f.game, f.richese).noField!.lastShipped, previous.id);
|
|
const request = pairedSkillsRicheseCunningRequest(f);
|
|
reject(f.game, f.richese, { ...request, noField: previous.id });
|
|
reject(f.game, f.richese, { ...request, revealedToken: previous.id });
|
|
reject(f.game, f.richese, { ...request, revealedToken: request.noField });
|
|
reject(f.game, f.richese, { ...request, smuggler: true });
|
|
reject(f.game, f.richese, { ...request, amount: 8 });
|
|
holdPairedSkillsCard(f.game, f.observer, card => card.effect === 'karama', f.staging);
|
|
const declared = applyAction(f.game, f.richese, request);
|
|
assert.equal(declared.response?.kind, 'richeseNoField');
|
|
assert.equal(declared.pendingShipment!.cost, 1);
|
|
assert.equal(declared.pendingShipment!.amount, 1);
|
|
assert.equal(declared.pendingShipment!.noFieldSkillProof, undefined, 'Original signed pair, not an invented ordinary skill proof');
|
|
assert.equal(declared.pendingShipment!.smugglerCompanion, undefined);
|
|
assert.deepEqual(player(declared, f.richese).forces, player(f.game, f.richese).forces);
|
|
assert.equal(player(declared, f.richese).spice, player(f.game, f.richese).spice);
|
|
const changed = pairedSkillsSnapshot(declared);
|
|
changed.pendingShipment!.cost = 0;
|
|
const responder = changed.players.find(p => !changed.response!.passed.includes(p.id))!.id;
|
|
reject(changed, responder, { type: 'passResponse' });
|
|
const shipped = allowPairedChoamSkills(pairedSkillsSnapshot(declared));
|
|
assertPair(f, shipped, 5);
|
|
reject(shipped, f.richese, request);
|
|
const continued = allowPairedChoamSkills(shipped);
|
|
assert.equal(player(continued, f.richese).spice, player(shipped, f.richese).spice, 'Settled response cannot repeat or refund the invoice');
|
|
assert.equal(player(continued, f.guild).spice, player(shipped, f.guild).spice);
|
|
});
|
|
|
|
void test('paired Richese immediate group is reserve-capped; voluntary reveal cannot invent concealed counters and next native shipment respects original marker history', () => {
|
|
const f = createPairedChoamNexusSkillsFixture({ advanced: false, reserveCap: 2 });
|
|
const shipped = allowPairedChoamSkills(applyAction(f.game, f.richese, pairedSkillsRicheseCunningRequest(f)));
|
|
assertPair(f, shipped, 2);
|
|
const revealed = revealPairedSkillsRichese(shipped, f.richese);
|
|
assert.equal(player(revealed, f.richese).noField!.deployed, null);
|
|
assert.equal(player(revealed, f.richese).forces[f.location], 2);
|
|
assert.equal(player(revealed, f.richese).reserves, 0);
|
|
const previous = player(revealed, f.richese).noField!.lastShipped!;
|
|
let next = advancePairedChoamSkills(revealed, g => g.turn === 3 && g.phase === 1 && pairedSkillsClean(g));
|
|
// Preserve already played discards; order only unused native land cards.
|
|
const land = next.spiceDeck.filter(c => 'territory' in c).slice(0, 2);
|
|
assert.equal(land.length, 2);
|
|
for (const [at, card] of land.entries()) next.spiceDeck.splice(at, 0, next.spiceDeck.splice(next.spiceDeck.indexOf(card), 1)[0]);
|
|
next = advancePairedChoamSkills(next, g => g.turn === 3 && g.phase === 5 && g.active === f.richese && pairedSkillsClean(g));
|
|
reject(next, f.richese, { type: 'ship', noField: previous, event: player(next, f.richese).noFieldEvent, territory: 'polar_sink', sector: 0 });
|
|
const zero = player(next, f.richese).noField!.tokens.find(t => t.value === 0)!;
|
|
const accepted = allowPairedChoamSkills(applyAction(next, f.richese, { type: 'ship', noField: zero.id, event: player(next, f.richese).noFieldEvent, territory: 'polar_sink', sector: 0 }));
|
|
assert.equal(player(accepted, f.richese).noField!.lastShipped, zero.id);
|
|
assert.equal(player(accepted, f.richese).noField!.deployed!.tokenId, zero.id);
|
|
assert.equal(player(accepted, f.richese).spice, player(next, f.richese).spice - 2);
|
|
assert.equal(player(accepted, f.richese).forces['polar_sink:0'], 18);
|
|
custody(accepted);
|
|
});
|
|
|
|
void test('paired skills Cunning stopped by physical Karama spends only Nexus; ordinary shipment remains a separate single payment', () => {
|
|
const f = createPairedChoamNexusSkillsFixture();
|
|
const karama = holdPairedSkillsCard(f.game, f.guild, c => c.kind === 'special' && c.effect === 'karama', f.staging);
|
|
const before = pairedSkillsSnapshot(f.game), request = pairedSkillsRicheseCunningRequest(f);
|
|
const declared = applyAction(f.game, f.richese, request);
|
|
const stopped = allowPairedChoamSkills(applyAction(declared, f.guild, { type: 'card', mode: 'cancel', card: karama.id }));
|
|
assert.equal(stopped.nexusRicheseCunningLast!.stage, 'stopped');
|
|
assert.equal(stopped.nexusCards!.cards!.hands[f.richese], null);
|
|
for (const p of before.players) assert.equal(player(stopped, p.id).spice, p.spice);
|
|
assert.deepEqual(player(stopped, f.richese).forces, player(before, f.richese).forces);
|
|
assert.equal(player(stopped, f.richese).reserves, player(before, f.richese).reserves);
|
|
assert.deepEqual(player(stopped, f.richese).noField, player(before, f.richese).noField);
|
|
assert.equal(player(stopped, f.richese).shipped, false);
|
|
assert.equal(stopped.discard.filter(c => c.id === karama.id).length, 1);
|
|
reject(stopped, f.richese, request);
|
|
const ordinary = allowPairedChoamSkills(applyAction(stopped, f.richese, { type: 'ship', amount: 1, territory: 'habbanya_ridge_sietch', sector: Number(f.location.split(':')[1]) }));
|
|
assert.equal(player(ordinary, f.richese).spice, player(stopped, f.richese).spice - 1);
|
|
assert.equal(player(ordinary, f.guild).spice, player(stopped, f.guild).spice + 1);
|
|
assert.equal(player(ordinary, f.richese).forces[f.location], 1);
|
|
assert.deepEqual(ordinary.leaderSkills!.assignments, before.leaderSkills!.assignments);
|
|
custody(ordinary);
|
|
});
|
|
|
|
void test('genuine unpaid Richese pair preserves both original markers, physical Nexus, reserves and skill assignment', () => {
|
|
const f = createPairedChoamNexusSkillsFixture();
|
|
const amount = player(f.game, f.richese).spice;
|
|
assert.ok(amount > 0);
|
|
const unpaid = applyAction(f.game, f.richese, { type: 'bribe', target: f.guild, amount });
|
|
assert.equal(player(unpaid, f.richese).spice, 0);
|
|
assert.equal(player(unpaid, f.guild).bribes, player(f.game, f.guild).bribes + amount, 'Wallet is depleted by a genuine native bribe, never assigned');
|
|
const before = pairedSkillsSnapshot(unpaid);
|
|
reject(unpaid, f.richese, pairedSkillsRicheseCunningRequest(f, unpaid));
|
|
assert.equal(unpaid.nexusCards!.cards!.hands[f.richese], 'richese');
|
|
assert.deepEqual(player(unpaid, f.richese).noField, player(before, f.richese).noField);
|
|
custody(unpaid);
|
|
});
|
|
|
|
for (const advanced of [false, true]) for (const trained of [false, true])
|
|
void test(`paired ${advanced ? 'Advanced' : 'Basic'} voluntary reveal → ordinary ${trained ? 'skilled' : 'normal'} Suk rescue; native CHOAM support income stays separate from Collection`, () => {
|
|
const f = createPairedChoamNexusSkillsFixture({ advanced });
|
|
let game = allowPairedChoamSkills(applyAction(f.game, f.richese, pairedSkillsRicheseCunningRequest(f)));
|
|
game = revealPairedSkillsRichese(game, f.richese);
|
|
assert.equal(player(game, f.richese).noField!.deployed, null);
|
|
assert.equal(player(game, f.richese).forces[f.location], 8);
|
|
assert.equal(player(game, f.richese).reserves, 12);
|
|
placePairedSkillsForce(game, f.guild, f.location, 3, f.staging);
|
|
placePairedSkillsForce(game, f.choam, `arrakeen:${territory('arrakeen').sectors[0]}`, 1, f.staging);
|
|
game = openPairedSkillsBattle(game, f.richese, f.guild, { band: trained ? 'skilled' : 'normal' });
|
|
const plans = pairedSkillsBattlePlans(game, f.richese, f.guild, 2, trained);
|
|
assert.equal(plans[0].action.leader === f.trainers.richese, trained);
|
|
for (const next of plans) game = allowPairedChoamSkills(stepPairedChoamSkills(game, next));
|
|
assert.ok(game.battle?.revealed);
|
|
const revealed = pairedSkillsSnapshot(game), quote = quotePairedSkillsBattle(game);
|
|
assert.equal(quote.result, 'normal');
|
|
assert.equal(quote.winner, f.richese);
|
|
assert.equal(quote.sukGraduate!.mode, trained ? 'skilled' : 'normal');
|
|
assert.equal(viewGame(game, f.richese).battle!.ownForces!.normal, 8, 'Both marker groups now have ordinary native physical force identity');
|
|
const payment = quote.payments.find(p => p.player === f.richese);
|
|
assert.equal(payment?.ownPayment ?? 0, advanced ? 2 : 0);
|
|
assert.equal(payment?.bankSupport ?? 0, 0);
|
|
if (advanced) {
|
|
assert.equal(quote.choamIncome!.owner, f.choam);
|
|
assert.ok(quote.choamIncome!.amount > 0);
|
|
} else assert.equal(quote.choamIncome, null);
|
|
game = finishPairedSkillsBattle(game);
|
|
assert.equal(game.lastBattleContext!.winner, f.richese);
|
|
assert.equal(game.lastBattleContext!.result, 'normal');
|
|
assert.equal(player(game, f.richese).forces[f.location], trained ? 7 : 6);
|
|
assert.equal(player(game, f.richese).reserves, 13, 'Skilled Suk keeps one rescued casualty on its original sector and returns the other; normal Suk returns one');
|
|
assert.equal(player(game, f.richese).tanks, trained ? 0 : 1);
|
|
assert.equal(player(game, f.guild).tanks, 3, 'Declining a traitor call still loses all opposing physical forces in an ordinary battle');
|
|
game = advancePairedChoamSkillsToPhase(game, 7);
|
|
const collection = quoteSpiceCollection({ ...game, players: game.players.map(p => ({ ...p, spice: 0 })) }).receipts;
|
|
assert.equal(collection.find(r => r.player === f.choam)!.strongholds, advanced ? 2 : 0, 'Native Arrakeen Collection is a separate later credit, not battle support income');
|
|
for (const p of revealed.players) {
|
|
const support = quote.payments.find(r => r.player === p.id)?.ownPayment ?? 0;
|
|
const income = quote.choamIncome?.owner === p.id ? quote.choamIncome.amount : 0;
|
|
const city = collection.find(r => r.player === p.id);
|
|
assert.equal(player(game, p.id).spice, p.spice - support + income + (city?.strongholds ?? 0) + (city?.collected ?? 0));
|
|
}
|
|
custody(game);
|
|
});
|
|
|
|
void test('CHOAM own Cunning converts one original Special into printed Kulon fuel once; ordinary Planetologist route consumes physical forces, not another card', () => {
|
|
const f = createPairedChoamNexusSkillsFixture({ advanced: false });
|
|
let game = advancePairedChoamSkills(f.movementStart, g => g.phase === 5 && g.active === f.choam && pairedSkillsClean(g));
|
|
const assignment = game.leaderSkills!.assignments.find(a => a.owner === f.choam)!;
|
|
assert.equal(assignment.skill, 'planetologist');
|
|
const fuel = holdPairedSkillsCard(game, f.choam, isPlanetologistBattleSpecialCard, f.staging);
|
|
holdPairedSkillsCard(game, f.guild, card => card.effect === 'karama', f.staging);
|
|
const route = stagePairedSkillsPlanetologistRoute(game, f.choam, f.staging);
|
|
const request = pairedSkillsChoamCunningRequest(game, f.choam, fuel.id), before = pairedSkillsSnapshot(game);
|
|
reject(before, f.choam, route.action);
|
|
reject(before, f.choam, { ...request, nexus: 'stale-original-offer' });
|
|
game = applyAction(game, f.choam, request);
|
|
assert.equal(game.response?.kind, 'choamWorthless');
|
|
assert.equal(game.nexusCards!.cards!.hands[f.choam], null);
|
|
assert.deepEqual(player(game, f.choam).hand.find(c => c.id === fuel.id), fuel, 'Original response retains printed fuel custody until resolution');
|
|
game = allowPairedChoamSkills(game);
|
|
assert.equal(game.nexusChoamLast!.stage, 'complete');
|
|
assert.equal(game.discard.filter(c => c.id === fuel.id).length, 1);
|
|
assert.equal(player(game, f.choam).hand.some(c => c.id === fuel.id), false);
|
|
assert.equal(game.choamMovement!.bonus, 1);
|
|
assert.deepEqual(game.leaderSkills!.assignments, before.leaderSkills!.assignments);
|
|
reject(game, f.choam, request);
|
|
reject(game, f.choam, { ...route.action, noField: 'not-an-ordinary-route' });
|
|
const moved = allowPairedChoamSkills(applyAction(game, f.choam, route.action));
|
|
assert.equal(player(moved, f.choam).forces[route.from] ?? 0, 0);
|
|
assert.equal(player(moved, f.choam).forces[route.to], 1);
|
|
assert.equal(player(moved, f.choam).moved, 1);
|
|
assert.equal(moved.discard.filter(c => c.id === fuel.id).length, 1);
|
|
for (const p of before.players) assert.equal(player(moved, p.id).spice, p.spice);
|
|
custody(moved);
|
|
});
|
|
|
|
void test('canceling actual CHOAM Cunning consumes Karama and Nexus, retains the original skill-eligible Special and gives no Kulon route', () => {
|
|
const f = createPairedChoamNexusSkillsFixture({ advanced: false });
|
|
let game = advancePairedChoamSkills(f.movementStart, g => g.phase === 5 && g.active === f.choam && pairedSkillsClean(g));
|
|
const fuel = holdPairedSkillsCard(game, f.choam, isPlanetologistBattleSpecialCard, f.staging);
|
|
const karama = holdPairedSkillsCard(game, f.guild, c => c.kind === 'special' && c.effect === 'karama' && c.id !== fuel.id, f.staging);
|
|
const route = stagePairedSkillsPlanetologistRoute(game, f.choam, f.staging), before = pairedSkillsSnapshot(game);
|
|
const request = pairedSkillsChoamCunningRequest(game, f.choam, fuel.id);
|
|
game = applyAction(game, f.choam, request);
|
|
game = allowPairedChoamSkills(applyAction(game, f.guild, { type: 'card', mode: 'cancel', card: karama.id }));
|
|
assert.equal(game.nexusChoamLast!.stage, 'canceled');
|
|
assert.equal(game.nexusCards!.cards!.hands[f.choam], null);
|
|
assert.deepEqual(player(game, f.choam).hand.find(c => c.id === fuel.id), fuel);
|
|
assert.equal(game.discard.some(c => c.id === fuel.id), false);
|
|
assert.equal(game.discard.filter(c => c.id === karama.id).length, 1);
|
|
assert.equal(game.choamMovement?.bonus ?? 0, 0);
|
|
assert.deepEqual(game.leaderSkills!.assignments, before.leaderSkills!.assignments);
|
|
reject(game, f.choam, route.action);
|
|
reject(game, f.choam, request);
|
|
for (const p of before.players) assert.equal(player(game, p.id).spice, p.spice);
|
|
custody(game);
|
|
});
|
|
|
|
void test('original authenticated setup preserves already-dealt native hands, saved offers and IDs through genuine native continuation', () => {
|
|
const lobby = createGame('AUTHPAIREDE2SKILLS', newPlayer('auth-r', 'R', 'richese'), true, ['choam']);
|
|
joinGame(lobby, newPlayer('auth-c', 'C', 'choam'));
|
|
joinGame(lobby, newPlayer('auth-g', 'G', 'guild'));
|
|
joinGame(lobby, newPlayer('auth-a', 'A', 'atreides'));
|
|
const setup = initializePairedChoamNexusSkills({ initial: lobby }), saved = pairedSkillsSnapshot(setup);
|
|
const f = createPairedChoamNexusSkillsFixture({ initial: setup });
|
|
assert.deepEqual(setup, saved);
|
|
assert.deepEqual(f.initial, saved, 'Actual initialized offers and first deal are not recreated');
|
|
for (const p of saved.players) assert.deepEqual(player(f.afterSetup, p.id).hand, p.hand);
|
|
assert.deepEqual([f.richese, f.choam, f.guild, f.observer], ['auth-r', 'auth-c', 'auth-g', 'auth-a']);
|
|
const richeseOffer = setup.leaderSkills!.offers[f.richese];
|
|
const absent = setup.leaderSkills!.deck.find(skill => !richeseOffer.cards.includes(skill))!;
|
|
assert.throws(() => createPairedChoamNexusSkillsFixture({ initial: setup, richeseSkill: absent }));
|
|
assert.deepEqual(setup, saved, 'An unavailable requested skill cannot replace saved offers');
|
|
for (const difficulty of DIFFICULTIES) {
|
|
const view = viewGame(f.game, f.richese);
|
|
view.players.find(p => p.id === f.richese)!.bot = difficulty;
|
|
const action = botActions(view)[0];
|
|
assert.ok(action?.type === 'ship' && action.noField && action.revealedToken, difficulty);
|
|
const shipped = allowPairedChoamSkills(applyAction(f.game, f.richese, action));
|
|
assert.equal(shipped.nexusRicheseCunningLast!.stage, 'shipped', difficulty);
|
|
assert.ok(typeof action.territory === 'string');
|
|
const cost = reserveShipmentCost({ faction: 'richese', halfRate: false }, territory(action.territory).type, 1);
|
|
assert.equal(player(shipped, f.richese).spice, player(f.game, f.richese).spice - cost);
|
|
assert.equal(shipped.nexusCards!.cards!.hands[f.richese], null);
|
|
custody(shipped);
|
|
}
|
|
});
|
|
|
|
void test('both original E2 natives actually deal and assign all14 custody under Basic/Advanced two-to-six-seat native starts', () => {
|
|
const additional = ['guild', 'atreides', 'emperor', 'harkonnen'] as const;
|
|
for (const advanced of [false, true]) for (const seats of [2, 3, 4, 5, 6]) {
|
|
const setup = initializePairedChoamNexusSkills({ advanced, factions: ['richese', 'choam', ...additional.slice(0, seats - 2)] });
|
|
const starts = pairedSkillsSnapshot(setup);
|
|
const game = completePairedChoamSkillsSetup(setup);
|
|
assert.equal(game.leaderSkills!.assignments.length, seats);
|
|
for (const p of starts.players) {
|
|
assert.deepEqual(player(game, p.id).hand, p.hand);
|
|
}
|
|
assert.equal(player(game, 'richese').noField!.deployed, null);
|
|
assert.equal(game.nexusCards!.cards!.deck.length, 12, 'Original Nexus cards are still physically undealt before the first real worm');
|
|
custody(game);
|
|
const firstStorm = advancePairedChoamSkillsToPhase(game, 1);
|
|
assert.equal(firstStorm.turn, 1);
|
|
assert.equal(firstStorm.phase, 1);
|
|
custody(firstStorm);
|
|
}
|
|
});
|