95 lines
3.0 KiB
TypeScript
95 lines
3.0 KiB
TypeScript
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { createGame, newPlayer, viewGame, type GameView } from '../game/engine';
|
|
import { nexusFaceDancersAction } from '../game/nexus-tleilaxu-options';
|
|
|
|
/** Explicit projected offers isolate presentation/consumer validation. These
|
|
* tests do not claim physical acquisition or replacement engine evidence. */
|
|
function fixture(): GameView {
|
|
const g = createGame(
|
|
'DANCERCONTROLS',
|
|
newPlayer('t', 'Tleilaxu', 'tleilaxu'),
|
|
);
|
|
g.players.push(
|
|
newPlayer('a', 'Atreides', 'atreides'),
|
|
newPlayer('h', 'Harkonnen', 'harkonnen'),
|
|
);
|
|
Object.assign(g, {
|
|
status: 'playing',
|
|
phase: 6,
|
|
turn: 2,
|
|
active: 'a',
|
|
order: ['t', 'a', 'h'],
|
|
});
|
|
const v = viewGame(g, 't');
|
|
v.nexusCards = {
|
|
card: 'tleilaxu',
|
|
deckCount: 11,
|
|
discardCount: 0,
|
|
held: { t: true, a: false, h: false },
|
|
turn: null,
|
|
choices: [],
|
|
waiting: [],
|
|
};
|
|
v.nexusTleilaxu = {
|
|
cunning: { event: 'dancer-event', count: 2, blocked: null },
|
|
secretAlly: null,
|
|
};
|
|
return v;
|
|
}
|
|
|
|
void test('Cunning binds owner, held card, event and server block while preserving anytime decision access', () => {
|
|
const v = fixture();
|
|
v.decision = { kind: 'nullentropy', player: 'a' };
|
|
v.automaticContinuationPending = true;
|
|
assert.ok(nexusFaceDancersAction(v, 'dancer-event'));
|
|
assert.equal(nexusFaceDancersAction(v, 'stale'), null);
|
|
v.nexusTleilaxu!.cunning!.blocked = 'Finish the current Nexus return first.';
|
|
assert.equal(nexusFaceDancersAction(v, 'dancer-event'), null);
|
|
v.nexusTleilaxu!.cunning!.blocked = null;
|
|
v.nexusCards!.card = 'harkonnen';
|
|
assert.equal(nexusFaceDancersAction(v, 'dancer-event'), null);
|
|
v.nexusCards!.card = 'tleilaxu';
|
|
v.me = 'a';
|
|
assert.equal(nexusFaceDancersAction(v, 'dancer-event'), null);
|
|
v.me = 't';
|
|
v.players[0].ally = 'a';
|
|
assert.equal(nexusFaceDancersAction(v, 'dancer-event'), null);
|
|
});
|
|
|
|
void test('Cunning controls honor Truth and exchange priority and never inspect hidden identities', () => {
|
|
const v = fixture();
|
|
for (const p of v.players)
|
|
for (const key of ['traitors', 'faceDancers', 'hand'])
|
|
Object.defineProperty(p, key, {
|
|
get() {
|
|
throw new Error('Read a hidden identity');
|
|
},
|
|
});
|
|
assert.ok(nexusFaceDancersAction(v, 'dancer-event'));
|
|
v.truthtrance = { stage: 'priority', queue: [], passed: [], question: null };
|
|
assert.equal(nexusFaceDancersAction(v, 'dancer-event'), null);
|
|
v.truthtrance = null;
|
|
v.nexusCards!.waiting = ['a'];
|
|
assert.equal(nexusFaceDancersAction(v, 'dancer-event'), null);
|
|
v.nexusCards!.waiting = [];
|
|
v.nexusTraitors = {
|
|
offer: null,
|
|
pending: {
|
|
event: 'exchange',
|
|
owner: 'h',
|
|
mode: 'cunning',
|
|
count: 1,
|
|
choices: [],
|
|
},
|
|
};
|
|
assert.equal(nexusFaceDancersAction(v, 'dancer-event'), null);
|
|
v.nexusTraitors = null;
|
|
for (const count of [0, -1, 1.5, NaN]) {
|
|
v.nexusTleilaxu!.cunning!.count = count;
|
|
assert.equal(nexusFaceDancersAction(v, 'dancer-event'), null);
|
|
}
|
|
v.nexusTleilaxu = null;
|
|
});
|
|
|