325 lines
21 KiB
TypeScript
325 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 { quoteSpiceCollection } from '../game/board-resolution-quote';
|
|
import { ownedTech, TECH_TOKENS } from '../game/tech-tokens';
|
|
import { assertAdvancedNativeCustody } from './fixture-advanced-native-skills';
|
|
import {
|
|
advanceRicheseSkillsTechToPhase, createRicheseSkillsTechFixture, finishRicheseSkillsTechBattle,
|
|
nextRicheseSkillsTechNativeStep, openRicheseSkillsTechBattle, payRicheseSkillsTechShipment,
|
|
richeseSkillsTechPlayer as player, type RicheseSkillsTechFixture,
|
|
} from './fixture-richese-skills-tech';
|
|
|
|
function reject(game: Game, actor: string, action: Action): void {
|
|
const before = structuredClone(game);
|
|
assert.throws(() => applyAction(game, actor, action));
|
|
assert.deepEqual(game, before);
|
|
}
|
|
function cards(game: Game): string[] {
|
|
return [...game.deck, ...game.discard, ...game.players.flatMap(p => p.hand),
|
|
...(game.richeseCache ?? []), ...(game.richeseRemoved ?? [])].map(c => c.id).sort();
|
|
}
|
|
function wallets(game: Game): Record<string, number> {
|
|
return Object.fromEntries(game.players.map(p => [p.id, p.spice]));
|
|
}
|
|
function changedTokens(before: Game, after: Game): string[] {
|
|
return TECH_TOKENS.filter(t => before.techTokens![t.id].owner !== after.techTokens![t.id].owner).map(t => t.id);
|
|
}
|
|
function reveal(fixture: RicheseSkillsTechFixture, hide = true): Game {
|
|
let game = openRicheseSkillsTechBattle(fixture, hide);
|
|
game = applyAction(game, fixture.owner, fixture.ownerPlan);
|
|
return applyAction(game, fixture.opponent, fixture.opponentPlan);
|
|
}
|
|
function calls(state: Game, callers: string[] = []): Game {
|
|
let game = state;
|
|
for (let n = 0; game.battle?.revealed && !game.decision && !game.response && n < 20; n++) {
|
|
const next = nextRicheseSkillsTechNativeStep(game);
|
|
if (next.action.type !== 'traitorCall') break;
|
|
next.action.call = callers.includes(next.actor);
|
|
game = applyAction(game, next.actor, next.action);
|
|
}
|
|
return game;
|
|
}
|
|
function reward(fixture: RicheseSkillsTechFixture, state: Game): Game {
|
|
const token = ownedTech(fixture.game.techTokens, fixture.opponent)[0];
|
|
assert.ok(token, 'Original three-seat first Storm gives the defeated seat one physical token.');
|
|
assert.deepEqual(changedTokens(fixture.game, state), [], 'Physical rescue/cleanup must precede token reward.');
|
|
const settled = finishRicheseSkillsTechBattle(state);
|
|
assert.deepEqual(changedTokens(fixture.game, settled), [token]);
|
|
assert.equal(settled.techTokens![token].owner, fixture.owner);
|
|
assert.equal(settled.pendingTech, null);
|
|
assert.deepEqual(cards(settled), cards(fixture.game));
|
|
assertAdvancedNativeCustody(settled);
|
|
return settled;
|
|
}
|
|
|
|
for (const advanced of [false, true]) for (const marker of [0, 3, 5] as const) {
|
|
void test(`${advanced ? 'Advanced' : 'Basic'} native Richese ${marker} marker reveals only after both plans, caps real Suk casualties, then rewards the original loser token`, () => {
|
|
const fixture = createRicheseSkillsTechFixture({ advanced, marker, ...(marker === 5 ? { reserves: 3 } : {}) });
|
|
const beforeShipment = fixture.beforeShipment!, shipped = fixture.afterShipment!;
|
|
assert.equal(player(shipped, fixture.owner).spice, player(beforeShipment, fixture.owner).spice - 1);
|
|
assert.equal(player(shipped, fixture.owner).reserves, player(beforeShipment, fixture.owner).reserves);
|
|
let game = openRicheseSkillsTechBattle(fixture);
|
|
const pool = marker === 5 ? 3 : marker;
|
|
reject(game, fixture.owner, { ...fixture.ownerPlan, dial: pool + 1, support: advanced ? pool + 1 : 0 });
|
|
game = applyAction(game, fixture.owner, fixture.ownerPlan);
|
|
assert.ok(player(game, fixture.owner).noField!.deployed);
|
|
assert.equal(player(game, fixture.owner).forces[fixture.key] ?? 0, 0);
|
|
assert.equal(viewGame(game, fixture.opponent).richeseNoField!.private, null);
|
|
game = applyAction(game, fixture.opponent, fixture.opponentPlan);
|
|
assert.equal(player(game, fixture.owner).noField!.deployed, null);
|
|
assert.equal(player(game, fixture.owner).forces[fixture.key] ?? 0, pool);
|
|
assert.equal(player(game, fixture.owner).reserves, (marker === 5 ? 3 : 20) - pool);
|
|
const before = player(fixture.game, fixture.owner).spice;
|
|
game = finishRicheseSkillsTechBattle(calls(game), 'skill');
|
|
assert.equal(game.lastBattleContext!.winner, fixture.owner);
|
|
assert.equal(player(game, fixture.owner).spice, before - (advanced ? pool : 0));
|
|
assert.equal(player(game, fixture.opponent).tanks, 6);
|
|
const losingCard = String(fixture.opponentPlan.weapon);
|
|
assert.equal(game.discard.filter(c => c.id === losingCard).length, 1);
|
|
if (pool) {
|
|
const d = game.decision;
|
|
assert.ok(d?.kind === 'sukRescue');
|
|
assert.equal(d.mode, 'skilled');
|
|
assert.equal(player(game, fixture.owner).tanks, 0, 'Do not pre-apply declared casualties before real rescue.');
|
|
const choice = d.options.findIndex(o => o.normal === pool && o.elite === 0 && o.kept?.key === fixture.key);
|
|
assert.ok(choice >= 0);
|
|
reject(game, fixture.opponent, { type: 'decision', event: d.event, choice });
|
|
reject(game, fixture.owner, { type: 'decision', event: `${d.event}-stale`, choice });
|
|
game = applyAction(game, fixture.owner, { type: 'decision', event: d.event, choice });
|
|
assert.equal(player(game, fixture.owner).forces[fixture.key], 1);
|
|
assert.equal(player(game, fixture.owner).reserves, (marker === 5 ? 3 : 20) - 1);
|
|
assert.equal(player(game, fixture.owner).tanks, 0);
|
|
assert.equal(game.lastBattleContext!.sukRescue!.completed, true);
|
|
} else {
|
|
assert.equal(player(game, fixture.owner).reserves, 20);
|
|
assert.equal(player(game, fixture.owner).tanks, 0);
|
|
assert.equal(player(game, fixture.owner).forces[fixture.key] ?? 0, 0);
|
|
}
|
|
reward(fixture, game);
|
|
});
|
|
}
|
|
|
|
for (const advanced of [false, true]) void test(`${advanced ? 'Advanced' : 'Basic'} normal Suk saves one actual three-marker casualty, never all declared losses`, () => {
|
|
const fixture = createRicheseSkillsTechFixture({ advanced, marker: 3, band: 'normal' });
|
|
let game = finishRicheseSkillsTechBattle(calls(reveal(fixture, false)), 'cleanup');
|
|
assert.equal(game.lastBattleContext!.winner, fixture.owner);
|
|
assert.equal(player(game, fixture.owner).forces[fixture.key] ?? 0, 0);
|
|
assert.equal(player(game, fixture.owner).tanks, 2);
|
|
assert.equal(player(game, fixture.owner).reserves, 18);
|
|
game = reward(fixture, game);
|
|
assert.equal(player(game, fixture.opponent).tanks, 6);
|
|
});
|
|
|
|
for (const advanced of [false, true]) for (const marker of [0, 3, 5] as const) {
|
|
void test(`${advanced ? 'Advanced' : 'Basic'} Smuggler adds one real companion to No-Field ${marker} without changing its one-marker fee or earning Banker`, () => {
|
|
const fixture = createRicheseSkillsTechFixture({ advanced, kind: 'shipment', marker });
|
|
const before = fixture.game, paid = payRicheseSkillsTechShipment(fixture);
|
|
const own = player(paid, fixture.owner);
|
|
assert.equal(own.spice, player(before, fixture.owner).spice - 1);
|
|
assert.equal(own.reserves, player(before, fixture.owner).reserves - 1);
|
|
assert.equal(own.forces[fixture.key], 1);
|
|
assert.equal(own.noField!.deployed!.tokenId, String(fixture.shipmentAction!.noField));
|
|
assert.equal(paid.techTokens!.heighliners.spice, 1);
|
|
assert.deepEqual(viewGame(paid, fixture.banker).spiceBankerIncome!.deferred, []);
|
|
assert.deepEqual(cards(paid), cards(before));
|
|
assertAdvancedNativeCustody(paid);
|
|
});
|
|
}
|
|
|
|
void test('a real qualifying revival earns Banker independently; No-Field companion triggers Heighliners and each collects at its original final leg exactly once', () => {
|
|
const fixture = createRicheseSkillsTechFixture({ kind: 'shipment', bankerPayment: true });
|
|
const before = fixture.game;
|
|
assert.deepEqual(viewGame(before, fixture.banker).spiceBankerIncome!.deferred, [{ owner: fixture.banker, amount: 1 }]);
|
|
const grants = before.spiceBankerIncome!.sources.filter(row => row.grant);
|
|
assert.deepEqual(grants.map(row => row.source.bankLegs), [[{ payer: fixture.opponent, amount: 4 }]]);
|
|
const paid = payRicheseSkillsTechShipment(fixture);
|
|
assert.equal(player(paid, fixture.owner).spice, player(before, fixture.owner).spice - 1);
|
|
assert.deepEqual(paid.spiceBankerIncome!.sources.filter(row => row.grant), grants);
|
|
const sources = structuredClone(paid.spiceBankerIncome!.sources);
|
|
const expected = Object.fromEntries(quoteSpiceCollection(paid).receipts.map(r => [r.player, r.balance]));
|
|
const techOwner = paid.techTokens!.heighliners.owner!;
|
|
expected[techOwner] += 1;
|
|
const collection = advanceRicheseSkillsTechToPhase(paid, 7);
|
|
assert.equal(collection.phase, 7, 'Empty Battle may correctly auto-skip from Movement to Collection.');
|
|
assert.deepEqual(wallets(collection), expected);
|
|
assert.equal(collection.techTokens!.heighliners.spice, 0);
|
|
assert.deepEqual(viewGame(collection, fixture.banker).spiceBankerIncome!.deferred, [{ owner: fixture.banker, amount: 1 }]);
|
|
const mentat = advanceRicheseSkillsTechToPhase(collection, 8);
|
|
expected[fixture.banker] += 1;
|
|
assert.deepEqual(wallets(mentat), expected);
|
|
assert.deepEqual(viewGame(mentat, fixture.banker).spiceBankerIncome!.deferred, []);
|
|
assert.deepEqual(mentat.spiceBankerIncome!.sources, sources, 'Neither Tech income nor Banker collection creates another bank-payment source.');
|
|
const next = nextRicheseSkillsTechNativeStep(mentat), continued = applyAction(mentat, next.actor, next.action);
|
|
assert.deepEqual(wallets(continued), expected);
|
|
assert.deepEqual(continued.spiceBankerIncome!.collections, mentat.spiceBankerIncome!.collections);
|
|
assertAdvancedNativeCustody(continued);
|
|
});
|
|
|
|
for (const advanced of [false, true]) for (const marker of [0, 3, 5] as const)
|
|
void test(`${advanced ? 'Advanced' : 'Basic'} mixed Smuggler and No-Field ${marker} battle seals a capped pool before real losses and Tech reward`, () => {
|
|
const fixture = createRicheseSkillsTechFixture({ advanced, kind: 'shipment', marker });
|
|
const paid = payRicheseSkillsTechShipment(fixture);
|
|
const own = player(paid, fixture.owner), enemy = player(paid, fixture.opponent);
|
|
// Explicit conserved scarcity and opponent positions, not played shipment claims.
|
|
own.forces['polar_sink:0'] = own.reserves - 2; own.reserves = 2;
|
|
enemy.reserves -= 6; enemy.forces[fixture.key] = 6;
|
|
const ready = advanceRicheseSkillsTechToPhase(paid, 6);
|
|
let game = openRicheseSkillsTechBattle({ ...fixture, game: ready });
|
|
const materialized = Math.min(marker, 2), pool = 1 + materialized;
|
|
assert.equal(viewGame(game, fixture.owner).battle!.mixedNoField, true);
|
|
assert.equal(viewGame(game, fixture.owner).battle!.ownForces!.normal, pool);
|
|
assert.equal(viewGame(game, fixture.opponent).battle!.mixedNoField, undefined);
|
|
assert.equal(viewGame(game, fixture.opponent).battle!.opponentForces, null);
|
|
const token = own.noField!.deployed!.tokenId;
|
|
reject(game, fixture.owner, { type: 'revealNoField', token, event: own.noFieldEvent });
|
|
reject(game, fixture.owner, { ...fixture.ownerPlan, dial: pool + 1, support: advanced ? pool + 1 : 0 });
|
|
const dial = Math.min(2, pool);
|
|
game = applyAction(game, fixture.owner, { ...fixture.ownerPlan, dial, support: advanced ? dial : 0 });
|
|
assert.equal(game.battle!.revealed, false);
|
|
assert.ok(player(game, fixture.owner).noField!.deployed);
|
|
assert.equal(player(game, fixture.owner).forces[fixture.key], 1);
|
|
assert.equal(player(game, fixture.owner).reserves, 2);
|
|
game = applyAction(game, fixture.opponent, fixture.opponentPlan);
|
|
assert.equal(game.battle!.revealed, true);
|
|
assert.equal(player(game, fixture.owner).noField!.deployed, null);
|
|
assert.equal(player(game, fixture.owner).forces[fixture.key], pool);
|
|
assert.equal(player(game, fixture.owner).reserves, 2 - materialized);
|
|
assertAdvancedNativeCustody(game);
|
|
game = finishRicheseSkillsTechBattle(calls(game));
|
|
const winner = game.lastBattleContext!.winner;
|
|
assert.ok(winner === fixture.owner || winner === fixture.opponent);
|
|
const ownerWon = winner === fixture.owner;
|
|
assert.equal(player(game, fixture.owner).forces[fixture.key] ?? 0, ownerWon ? pool - dial : 0);
|
|
assert.equal(player(game, fixture.owner).tanks, ownerWon ? dial : pool);
|
|
assert.equal(player(game, fixture.owner).reserves, 2 - materialized);
|
|
assert.equal(player(game, fixture.opponent).forces[fixture.key] ?? 0, ownerWon ? 0 : 6);
|
|
assert.equal(player(game, fixture.opponent).tanks, ownerWon ? 6 : 0);
|
|
const loser = ownerWon ? fixture.opponent : fixture.owner;
|
|
const tokenReward = ownedTech(ready.techTokens, loser)[0]; assert.ok(tokenReward);
|
|
assert.deepEqual(changedTokens(ready, game), [tokenReward]);
|
|
assert.equal(game.techTokens![tokenReward].owner, winner);
|
|
assert.deepEqual(cards(game), cards(ready));
|
|
assertAdvancedNativeCustody(game);
|
|
});
|
|
|
|
for (const mode of ['kill', 'ignore'] as const) void test(`cache-acquired Stone ${mode} uses real undialed forces and cleanup before awarding the actual winner an original loser-owned Tech Token`, () => {
|
|
const fixture = createRicheseSkillsTechFixture({ kind: 'stone' });
|
|
assert.ok(fixture.acquisition.before.richeseCache!.some(c => c.id === 'richese-stone-burner'));
|
|
assert.equal(fixture.acquisition.after.richeseCache!.some(c => c.id === 'richese-stone-burner'), false);
|
|
for (const p of fixture.acquisition.before.players) assert.equal(player(fixture.acquisition.after, p.id).spice, p.spice);
|
|
let game = reveal(fixture);
|
|
assert.ok(game.decision?.kind === 'stoneBurner');
|
|
game = applyAction(game, fixture.owner, { type: 'decision', event: game.decision.event, mode });
|
|
const paidBefore = player(fixture.game, fixture.owner).spice;
|
|
game = finishRicheseSkillsTechBattle(calls(game), 'skill');
|
|
assert.equal(game.lastBattleContext!.winner, fixture.owner, 'Eight minus two undialed counters beats four minus one, independent of leaders.');
|
|
for (const [actor, plan] of [[fixture.owner, fixture.ownerPlan], [fixture.opponent, fixture.opponentPlan]] as const) {
|
|
assert.equal(player(game, actor).leaders.find(l => l.id === plan.leader)!.dead, mode === 'kill');
|
|
}
|
|
const bounty = mode === 'kill' ? [fixture.ownerPlan, fixture.opponentPlan].reduce((sum, plan, i) =>
|
|
sum + player(fixture.game, i === 0 ? fixture.owner : fixture.opponent).leaders.find(l => l.id === plan.leader)!.strength, 0) : 0;
|
|
assert.equal(player(game, fixture.owner).spice, paidBefore - 2 + bounty);
|
|
if (mode === 'ignore') {
|
|
const d = game.decision;
|
|
assert.ok(d?.kind === 'sukRescue', 'Surviving hidden Suk trainer rescues actual Stone dialed casualties.');
|
|
const choice = d.options.findIndex(o => o.normal === 2 && o.elite === 0 && o.kept?.key === fixture.key);
|
|
assert.ok(choice >= 0);
|
|
game = applyAction(game, fixture.owner, { type: 'decision', event: d.event, choice });
|
|
assert.equal(player(game, fixture.owner).forces[fixture.key], 7);
|
|
assert.equal(player(game, fixture.owner).tanks, 0);
|
|
assert.equal(player(game, fixture.owner).reserves, 13);
|
|
assert.equal(game.lastBattleContext!.sukRescue!.completed, true);
|
|
} else {
|
|
assert.equal(game.lastBattleContext!.sukRescue, undefined, 'Killed skilled trainer cannot rescue.');
|
|
assert.equal(player(game, fixture.owner).forces[fixture.key], 6);
|
|
assert.equal(player(game, fixture.owner).tanks, 2);
|
|
}
|
|
assert.equal(player(game, fixture.opponent).tanks, 4);
|
|
const collection = quoteSpiceCollection(game).receipts.find(r => r.player === fixture.owner)!;
|
|
const settled = reward(fixture, game);
|
|
assert.ok(player(settled, fixture.owner).hand.some(c => c.id === 'richese-stone-burner'));
|
|
assert.equal(player(settled, fixture.owner).spice, paidBefore - 2 + bounty + collection.collected + collection.strongholds,
|
|
'Only real Stone support/bounty and independently quoted native Collection affect the wallet.');
|
|
});
|
|
|
|
for (const precedence of ['double-traitor', 'explosion'] as const) void test(`${precedence} takes precedence over actual cached Stone and gives no winner, no rescue and no Tech reward`, () => {
|
|
const fixture = createRicheseSkillsTechFixture({ kind: 'stone', precedence });
|
|
let game = reveal(fixture);
|
|
assert.ok(game.decision?.kind === 'stoneBurner');
|
|
game = applyAction(game, fixture.owner, { type: 'decision', event: game.decision.event, mode: 'kill' });
|
|
game = finishRicheseSkillsTechBattle(calls(game, precedence === 'double-traitor' ? [fixture.owner, fixture.opponent] : []));
|
|
assert.equal(game.lastBattleContext!.winner, null);
|
|
assert.deepEqual(changedTokens(fixture.game, game), []);
|
|
assert.equal(player(game, fixture.owner).tanks, 8);
|
|
assert.equal(player(game, fixture.opponent).tanks, 4);
|
|
assert.equal(game.discard.filter(c => c.id === 'richese-stone-burner').length, 1);
|
|
assert.deepEqual(cards(game), cards(fixture.game));
|
|
assertAdvancedNativeCustody(game);
|
|
});
|
|
|
|
void test('all four own-seat policies legally commit real capped marker plans, resolve skilled Suk and choose both-mode Stone controls without inventing token custody', () => {
|
|
const marker = createRicheseSkillsTechFixture({ marker: 5, reserves: 3 });
|
|
const stone = createRicheseSkillsTechFixture({ kind: 'stone' });
|
|
const pending = finishRicheseSkillsTechBattle(calls(reveal(marker)), 'skill');
|
|
assert.ok(pending.decision?.kind === 'sukRescue');
|
|
const choice = reveal(stone);
|
|
assert.ok(choice.decision?.kind === 'stoneBurner');
|
|
for (const difficulty of DIFFICULTIES) {
|
|
const plans = openRicheseSkillsTechBattle(marker);
|
|
const view = viewGame(plans, marker.owner); view.players.find(p => p.id === marker.owner)!.bot = difficulty;
|
|
const plan = botActions(view).find(a => a.type === 'battlePlan'); assert.ok(plan);
|
|
assert.ok(Number(plan.dial) <= 3 && Number(plan.support ?? 0) <= player(plans, marker.owner).spice);
|
|
const sealed = applyAction(plans, marker.owner, plan);
|
|
assert.ok(player(sealed, marker.owner).noField!.deployed);
|
|
assert.deepEqual(changedTokens(marker.game, sealed), []);
|
|
const rescueView = viewGame(pending, marker.owner); rescueView.players.find(p => p.id === marker.owner)!.bot = difficulty;
|
|
const rescue = botActions(rescueView)[0]; assert.ok(rescue?.type === 'decision');
|
|
const rescued = applyAction(pending, marker.owner, rescue);
|
|
assert.equal(player(rescued, marker.owner).forces[marker.key], 1);
|
|
reward(marker, rescued);
|
|
const stoneView = viewGame(choice, stone.owner); stoneView.players.find(p => p.id === stone.owner)!.bot = difficulty;
|
|
const mode = botActions(stoneView)[0]; assert.ok(mode.mode === 'kill' || mode.mode === 'ignore');
|
|
const chosen = applyAction(choice, stone.owner, mode);
|
|
assert.equal(chosen.battle!.stoneBurner![stone.owner], mode.mode);
|
|
const settled = finishRicheseSkillsTechBattle(calls(chosen));
|
|
assert.equal(settled.lastBattleContext!.winner, stone.owner);
|
|
assert.equal(ownedTech(settled.techTokens, stone.owner).length, 2);
|
|
assertAdvancedNativeCustody(settled);
|
|
}
|
|
});
|
|
|
|
void test('fresh authenticated original ready lobby retains IDs and seat order through actual marker payment, battle and original reward', () => {
|
|
const initial = createGame('AUTHRICH', newPlayer('human-enemy', 'Human Emperor', 'emperor'), true, ['choam']);
|
|
joinGame(initial, newPlayer('human-richese', 'Human Richese', 'richese'));
|
|
joinGame(initial, newPlayer('human-banker', 'Human Banker', 'atreides'));
|
|
const original = structuredClone(initial), identities = initial.players.map(p => ({ id: p.id, name: p.name, faction: p.faction }));
|
|
const fixture = createRicheseSkillsTechFixture({ initial, marker: 3 });
|
|
assert.deepEqual(initial, original);
|
|
assert.equal(fixture.owner, 'human-richese');
|
|
const completed = finishRicheseSkillsTechBattle(calls(reveal(fixture)));
|
|
assert.equal(completed.lastBattleContext!.winner, fixture.owner);
|
|
assert.deepEqual(completed.players.map(p => ({ id: p.id, name: p.name, faction: p.faction })), identities);
|
|
assert.equal(ownedTech(completed.techTokens, fixture.owner).length, 2);
|
|
assertAdvancedNativeCustody(completed);
|
|
});
|
|
|
|
void test('native Atreides dial prescience cannot disclose a concealed Richese marker with Skills and Tech', () => {
|
|
const fixture = createRicheseSkillsTechFixture({ marker: 5, opponent: 'atreides' });
|
|
let game = structuredClone(fixture.game);
|
|
const actor = game.active!;
|
|
game = applyAction(game, actor, { type: 'chooseBattle', territory: fixture.territory,
|
|
target: actor === fixture.owner ? fixture.opponent : fixture.owner });
|
|
for (let n = 0; game.battle?.preparation?.kind !== 'prescience' && n < 100; n++) {
|
|
const next = nextRicheseSkillsTechNativeStep(game); game = applyAction(game, next.actor, next.action);
|
|
}
|
|
assert.equal(game.battle!.preparation!.kind, 'prescience');
|
|
reject(game, fixture.opponent, { type: 'prescience', field: 'dial' });
|
|
game = applyAction(game, fixture.opponent, { type: 'declineBattlePower' });
|
|
assert.ok(player(game, fixture.owner).noField!.deployed);
|
|
assert.deepEqual(changedTokens(fixture.game, game), []);
|
|
assertAdvancedNativeCustody(game);
|
|
});
|