Files
Dune/tests/richese-reveal-foreign-movement-quote.test.ts
T
Quintine 605cb2a32f
Verify and publish checkpoint container / container (push) Canceled after 0s
Expand Basic Richese movement and worm interactions
2026-10-29 09:54:24 +11:00

85 lines
4.5 KiB
TypeScript

import assert from 'node:assert/strict';
import test from 'node:test';
import { createGame, newPlayer, type Game } from '../game/engine';
import { createRicheseNoField, deployRicheseNoField } from '../game/richese-no-field';
import { richeseRevealForeignMovementPending } from '../game/richese-reveal-foreign-movement';
/** Staged response packets exercise only the classifier. Native actions and
* actual reveal/continuation are covered by the separate runtime programme. */
function movement(kind: 'fremen' | 'ixians' | 'choam'): Game {
const richese = newPlayer('r', 'Richese', 'richese');
richese.noField = deployRicheseNoField(createRicheseNoField(['zero', 'three', 'five']), {
tokenId: 'zero', controller: 'r', location: { territory: 'polar_sink', sector: 0 },
});
const g = createGame('MOVEQUOTE', richese, false, ['choam', 'ix']);
const mover = newPlayer('m', 'Mover', kind === 'choam' ? 'emperor' : kind);
mover.forces = { 'arrakeen:10': 3 };
if (mover.elites) mover.elites.forces = { 'arrakeen:10': 1 };
const choam = newPlayer('c', 'CHOAM', 'choam');
choam.forces = { 'carthag:11': 1 };
g.players.push(mover, choam);
Object.assign(g, { status: 'playing', phase: 5, turn: 2, active: 'm', decision: null, response: null });
const order: NonNullable<Game['pendingIxMove']> = {
player: 'm', group: [['arrakeen:10', 3]], eliteGroup: { 'arrakeen:10': kind === 'ixians' ? 1 : 0 },
elite: kind === 'ixians' ? 1 : 0, origin: 'arrakeen', origins: ['arrakeen'],
total: 3, to: 'carthag', sector: 11, advisors: false, wantsFighters: false,
};
if (kind === 'fremen') {
g.pendingFremenMove = { turn: g.turn, move: 0, order };
g.response = { kind: 'fremenMovement', owner: 'm', location: 'carthag:11', amount: 3, passed: [] };
} else if (kind === 'ixians') {
g.pendingIxMove = order;
g.response = { kind: 'ixMovement', owner: 'm', location: 'carthag:11', amount: 3, elite: 1, passed: [] };
} else {
g.pendingChoamMove = order;
g.decision = { kind: 'choamMovement', player: 'c', mover: 'm', territory: 'carthag', sector: 11, amount: 3 };
}
return g;
}
void test('all three ordinary foreign-movement parents are classified without mutation', () => {
for (const [kind, expected] of [
['fremen', 'pendingFremenMove'], ['ixians', 'pendingIxMove'], ['choam', 'pendingChoamMove'],
] as const) {
const g = movement(kind), before = structuredClone(g);
assert.equal(richeseRevealForeignMovementPending(g), expected);
assert.deepEqual(g, before);
}
});
void test('foreign movement requires the original actor, source, response and no unrelated child', () => {
for (const kind of ['fremen', 'ixians', 'choam'] as const) {
const g = movement(kind);
const stale = structuredClone(g); stale.active = 'r';
assert.equal(richeseRevealForeignMovementPending(stale), null);
const child = structuredClone(g); child.pendingShipment = {} as NonNullable<Game['pendingShipment']>;
assert.equal(richeseRevealForeignMovementPending(child), null);
const wrongPhase = structuredClone(g); wrongPhase.phase = 4;
assert.equal(richeseRevealForeignMovementPending(wrongPhase), null);
const noMarker = structuredClone(g); noMarker.players[0].noField!.deployed = null;
assert.equal(richeseRevealForeignMovementPending(noMarker), null);
const lostForces = structuredClone(g); lostForces.players[1].forces = {};
assert.equal(richeseRevealForeignMovementPending(lostForces), null);
if (kind === 'choam') {
const wrong = structuredClone(g);
if (wrong.decision?.kind === 'choamMovement') wrong.decision.amount++;
assert.equal(richeseRevealForeignMovementPending(wrong), null);
} else {
const wrong = structuredClone(g); wrong.response!.location = 'arrakeen:10';
assert.equal(richeseRevealForeignMovementPending(wrong), null);
const duplicate = structuredClone(g); duplicate.response!.passed = ['r', 'r'];
assert.equal(richeseRevealForeignMovementPending(duplicate), null);
}
}
});
void test('marker identity and denomination are not needed to identify a foreign movement', () => {
const g = movement('fremen');
g.players[0].noField = deployRicheseNoField(createRicheseNoField(['zero', 'three', 'five']), {
tokenId: 'five', controller: 'r', location: { territory: 'arrakeen', sector: 10 },
});
assert.equal(richeseRevealForeignMovementPending(g), 'pendingFremenMove');
g.players[0].noField!.deployed!.controller = 'm';
assert.equal(richeseRevealForeignMovementPending(g), null);
});