Connect bounded Moritani Atomics Aftermath

This commit is contained in:
Quintine committed 2026-09-29 19:13:16 +10:00
1 parent b2603f6715
commit 66a427ab55
17 files changed
+1036 -68

No files matched your search

+71
View File
@@ -0,0 +1,71 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import { createElement } from 'react';
import { renderToStaticMarkup } from 'react-dom/server';
import { MoritaniEntry, MoritaniTerrorSupply } from '../components/moritani-terror';
import { TerrorBoardMarkers } from '../components/terror-board-markers';
import { createGame, joinGame, newPlayer, viewGame, type GameView } from '../game/engine';
import { createTerrorState, placeTerror, revealTerror } from '../game/moritani-terror';
function fixture() {
const game = createGame('ATOMICS-CONTROLS', newPlayer('m', 'Moritani', 'moritani'));
joinGame(game, newPlayer('a', 'Atreides', 'atreides'));
joinGame(game, newPlayer('f', 'Fremen', 'fremen'));
game.players.find((player) => player.id === 'm')!.ally = 'a';
game.players.find((player) => player.id === 'a')!.ally = 'm';
game.moritaniTerror = createTerrorState(() => 0);
const token = game.moritaniTerror.tokens.find((item) => item.kind === 'atomics')!;
game.moritaniTerror = placeTerror(game.moritaniTerror, token.id, 'arrakeen', 1);
return { game, token };
}
const supply = (view: GameView) =>
renderToStaticMarkup(createElement(MoritaniTerrorSupply, { game: view }));
const markers = (view: GameView) =>
renderToStaticMarkup(createElement('svg', null, createElement(TerrorBoardMarkers, {
tokens: view.moritaniTerror?.tokens ?? [], atomics: view.moritaniAtomics,
})));
void test('a placed hidden face stays private; only owner sees the Atomics reveal consequences', () => {
const { game } = fixture();
const owner = viewGame(game, 'm');
owner.decision = { kind: 'moritaniTerror', player: 'm', entrant: 'f', territory: 'arrakeen' };
owner.terrorEntry = {
entrant: 'f', territory: 'arrakeen', sector: 10, cause: 'shipment', stage: 'offer',
kind: 'atomics', canReveal: true, canOfferAlliance: false,
};
const offer = renderToStaticMarkup(createElement(MoritaniEntry, { game: owner, busy: false, act() {} }));
assert.match(offer, /Reveal Atomics/);
assert.match(offer, /every faction’s forces/);
assert.match(offer, /including Fremen reinforcements/);
assert.match(offer, /discards one random card per excess/);
const rival = viewGame(game, 'f');
assert.match(supply(rival), /Inspect Hidden Terror/);
assert.doesNotMatch(supply(rival), /Inspect Atomics|Atomics Aftermath/);
assert.match(markers(rival), /hidden Terror token in Arrakeen/);
assert.doesNotMatch(markers(rival), /Atomics Aftermath/);
assert.equal(renderToStaticMarkup(createElement(MoritaniEntry, { game: rival, busy: false, act() {} })), '');
});
void test('after revelation all seats see persistent Aftermath and its exact shipment and hand-limit consequences', () => {
const { game, token } = fixture();
game.moritaniTerror = revealTerror(game.moritaniTerror!, token.id);
game.moritaniAtomics = { territory: 'arrakeen', turn: 2, moritaniId: 'm',
allyAtActivation: 'a', alliancePolicy: 'unresolved' };
game.turn = 2;
game.players.find((player) => player.id === 'm')!.atomicsHandLimitPenalty = true;
game.players.find((player) => player.id === 'a')!.atomicsHandLimitPenalty = true;
for (const id of ['m', 'a', 'f']) {
const view = viewGame(game, id);
const notice = supply(view);
assert.match(notice, /Atomics Aftermath · Arrakeen/);
assert.match(notice, /From turn 2, no faction may ship forces/);
assert.match(notice, /including Fremen reinforcements/);
assert.match(notice, /ordinary movement and Sneak Attack remain possible/);
assert.match(notice, /Moritani and Atreides/);
assert.match(notice, /lasting hand-limit reduction of one/);
const board = markers(view);
assert.match(board, /Atomics Aftermath in Arrakeen: permanent shipment ban/);
assert.doesNotMatch(board, /hidden Terror token in Arrakeen/);
}
});