Retain original selected modules, typed native returns, advisors and hidden No-Field recall. Preserve a full unspent Guild/Sapho opening, paid ordered arrivals, capped provisional No-Field destruction and native World Sneak withdrawal. Bind desert Aftermath to its original Moritani Cunning placement. Validation: frozen 16-file115/115, types/lint/build, eight finite original-action and GameTable SSR programmes, four minimal legal policies. Document provisional overlaps and corrected genuine recipes. No stored-game change or deployment; full combinations, comprehensive assurance and deployed acceptance remain required.
161 lines
8.4 KiB
TypeScript
161 lines
8.4 KiB
TypeScript
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import {
|
|
atomicsShipmentBlocked,
|
|
quoteMoritaniAtomics,
|
|
type AtomicsPlayer,
|
|
type AtomicsAftermath,
|
|
} from '../game/moritani-atomics';
|
|
import type { TerrorToken } from '../game/moritani-terror';
|
|
|
|
const token: TerrorToken = {
|
|
id: 'terror-opaque', kind: 'atomics', status: 'placed', location: 'arrakeen',
|
|
};
|
|
const moritani: AtomicsPlayer = {
|
|
id: 'm', faction: 'moritani', ally: null, forces: {}, tanks: 0,
|
|
handSize: 4, baseHandLimit: 4,
|
|
};
|
|
const quote = (players: readonly AtomicsPlayer[], aftermath: AtomicsAftermath | null = null) =>
|
|
quoteMoritaniAtomics({ token, territory: 'arrakeen', players,
|
|
moritaniId: 'm', turn: 3, aftermath });
|
|
|
|
void test('no ally at activation: all owners lose typed forces in the territory, not elsewhere', () => {
|
|
const players: AtomicsPlayer[] = [
|
|
{ ...moritani, forces: { 'arrakeen:10': 2, 'carthag:11': 3 } },
|
|
{ id: 'ix', faction: 'ixians', ally: null, forces: {
|
|
'arrakeen:10': 4, 'carthag:11': 2,
|
|
}, elites: { reserves: 0, tanks: 0, revived: 0,
|
|
forces: { 'arrakeen:10': 2 } }, tanks: 1,
|
|
handSize: 5, baseHandLimit: 4 },
|
|
{ id: 'bg', faction: 'beneGesserit', ally: null,
|
|
forces: { 'arrakeen:10': 1, 'carthag:11': 2 },
|
|
advisors: { arrakeen: {} }, tanks: 0, handSize: 4, baseHandLimit: 4 },
|
|
{ id: 'zero', faction: 'fremen', ally: null,
|
|
forces: { 'arrakeen:10': 0 }, tanks: 0,
|
|
handSize: 0, baseHandLimit: 4 },
|
|
];
|
|
const before = structuredClone(players);
|
|
const plan = quote(players);
|
|
assert.deepEqual(plan.casualties, [
|
|
{ playerId: 'm', location: 'arrakeen:10', normal: 2, elite: 0, advisor: 0 },
|
|
{ playerId: 'ix', location: 'arrakeen:10', normal: 2, elite: 2, advisor: 0 },
|
|
{ playerId: 'bg', location: 'arrakeen:10', normal: 1, elite: 0, advisor: 1 },
|
|
]);
|
|
assert.deepEqual(plan.handReductions,
|
|
[{ playerId: 'm', limit: 3, randomDiscards: 1 }]);
|
|
assert.deepEqual(plan.aftermath, { territory: 'arrakeen', turn: 3,
|
|
moritaniId: 'm', allyAtActivation: null, alliancePolicy: 'activation-targets' });
|
|
assert.deepEqual(players, before);
|
|
});
|
|
|
|
void test('activation ally is reduced with Moritani; counts exceed-one overflow without selecting cards', () => {
|
|
const plan = quote([
|
|
{ ...moritani, ally: 'choam', handSize: 6, baseHandLimit: 4 },
|
|
{ id: 'choam', faction: 'choam', ally: 'm', tanks: 0,
|
|
forces: { 'arrakeen:10': 1 }, handSize: 6, baseHandLimit: 5 },
|
|
{ id: 'other', faction: 'harkonnen', ally: null, tanks: 0,
|
|
forces: {}, handSize: 9, baseHandLimit: 8 },
|
|
]);
|
|
assert.deepEqual(plan.handReductions, [
|
|
{ playerId: 'm', limit: 3, randomDiscards: 3 },
|
|
{ playerId: 'choam', limit: 4, randomDiscards: 2 },
|
|
]);
|
|
});
|
|
|
|
void test('Aftermath blocks shipment by territory regardless of faction or sector, not other territories', () => {
|
|
const aftermath = quote([moritani]).aftermath;
|
|
assert.equal(atomicsShipmentBlocked(aftermath, 'arrakeen'), true);
|
|
assert.equal(atomicsShipmentBlocked(aftermath, 'carthag'), false);
|
|
assert.equal(atomicsShipmentBlocked(null, 'arrakeen'), false);
|
|
assert.equal(atomicsShipmentBlocked(aftermath, 'arrakeen:10'), false);
|
|
});
|
|
|
|
void test('placement, existing Aftermath, alliance inconsistency and malformed physical custody fail closed', () => {
|
|
const aftermath = quote([moritani]).aftermath;
|
|
assert.throws(() => quote([moritani], aftermath), /already on the board/);
|
|
for (const wrong of [
|
|
{ ...token, status: 'removed', location: null } as TerrorToken,
|
|
{ ...token, kind: 'robbery' } as TerrorToken,
|
|
{ ...token, location: 'hidden_mobile_stronghold' } as TerrorToken,
|
|
{ ...token, location: 'carthag' } as TerrorToken,
|
|
]) assert.throws(() => quoteMoritaniAtomics({ token: wrong,
|
|
territory: 'arrakeen', moritaniId: 'm', turn: 3,
|
|
aftermath: null, players: [moritani] }),
|
|
/still-placed token/);
|
|
assert.throws(() => quoteMoritaniAtomics({ token, territory: 'carthag',
|
|
moritaniId: 'm', turn: 3, aftermath: null, players: [moritani] }),
|
|
/still-placed token/);
|
|
assert.throws(() => quote([{ ...moritani, ally: 'missing' }]), /mutual ally/);
|
|
assert.throws(() => quote([{ ...moritani, forces: { 'arrakeen:11': 1 } }]),
|
|
/valid typed forces/);
|
|
assert.throws(() => quote([{ ...moritani, forces: { 'arrakeen:10': 1 },
|
|
elites: { reserves: 0, tanks: 0, revived: 0,
|
|
forces: { 'arrakeen:10': 2 } } }]), /valid typed forces/);
|
|
assert.throws(() => quote([{ ...moritani, noField: {
|
|
tokens: [{ id: 'zero', value: 0 }], lastShipped: null,
|
|
deployed: { tokenId: 'zero', controller: 'm',
|
|
location: { territory: 'arrakeen', sector: 10 } },
|
|
} }]), /concealed No-Field/);
|
|
});
|
|
|
|
void test('a token at a storm-exposed stronghold still quotes all occupied counters', () => {
|
|
const stormExposed = quoteMoritaniAtomics({
|
|
token: { ...token, location: 'tueks_sietch' }, territory: 'tueks_sietch', players: [
|
|
{ ...moritani, forces: { 'tueks_sietch:5': 1, 'arrakeen:10': 1 } },
|
|
], moritaniId: 'm', turn: 4, aftermath: null,
|
|
});
|
|
assert.deepEqual(stormExposed.casualties,
|
|
[{ playerId: 'm', location: 'tueks_sietch:5', normal: 1, elite: 0, advisor: 0 }]);
|
|
assert.equal(stormExposed.aftermath.territory, 'tueks_sietch');
|
|
});
|
|
|
|
void test('an authenticated Cunning desert placement destroys every typed sector and retains its grant in Aftermath', () => {
|
|
// The engine authenticates this original event against placement history;
|
|
// the pure effect consumes that authority without creating a placement.
|
|
const nexusPlacement = 'original-cunning-placement';
|
|
const players: AtomicsPlayer[] = [
|
|
{ ...moritani, forces: { 'wind_pass:14': 2, 'wind_pass:15': 1, 'arrakeen:10': 3 } },
|
|
{ id: 'e', faction: 'emperor', ally: null, tanks: 0, handSize: 4, baseHandLimit: 4,
|
|
forces: { 'wind_pass:14': 3, 'wind_pass:15': 2, 'carthag:11': 1 },
|
|
elites: { reserves: 0, tanks: 0, revived: 0,
|
|
forces: { 'wind_pass:14': 1, 'wind_pass:15': 2 } } },
|
|
{ id: 'bg', faction: 'beneGesserit', ally: null, tanks: 0, handSize: 0, baseHandLimit: 4,
|
|
forces: { 'wind_pass:15': 1 }, advisors: { wind_pass: {} } },
|
|
];
|
|
const before = structuredClone(players);
|
|
const plan = quoteMoritaniAtomics({ token: { ...token, location: 'wind_pass' },
|
|
territory: 'wind_pass', players, moritaniId: 'm', turn: 4, aftermath: null, nexusPlacement });
|
|
assert.deepEqual(plan.casualties, [
|
|
{ playerId: 'm', location: 'wind_pass:14', normal: 2, elite: 0, advisor: 0 },
|
|
{ playerId: 'm', location: 'wind_pass:15', normal: 1, elite: 0, advisor: 0 },
|
|
{ playerId: 'e', location: 'wind_pass:14', normal: 2, elite: 1, advisor: 0 },
|
|
{ playerId: 'e', location: 'wind_pass:15', normal: 0, elite: 2, advisor: 0 },
|
|
{ playerId: 'bg', location: 'wind_pass:15', normal: 1, elite: 0, advisor: 1 },
|
|
]);
|
|
assert.deepEqual(plan.aftermath, { territory: 'wind_pass', turn: 4, moritaniId: 'm',
|
|
allyAtActivation: null, alliancePolicy: 'activation-targets', nexusPlacement });
|
|
assert.deepEqual(plan.handReductions, [{ playerId: 'm', limit: 3, randomDiscards: 1 }]);
|
|
assert.equal(atomicsShipmentBlocked(plan.aftermath, 'wind_pass'), true);
|
|
assert.equal(atomicsShipmentBlocked(plan.aftermath, 'arrakeen'), false);
|
|
assert.deepEqual(players, before, 'Quoting changes neither board counters nor Tanks.');
|
|
});
|
|
|
|
void test('Cunning Atomics requires a grant for a printed non-stronghold and cannot admit another location kind', () => {
|
|
for (const nexusPlacement of [undefined, ''])
|
|
assert.throws(() => quoteMoritaniAtomics({ token: { ...token, location: 'wind_pass' },
|
|
territory: 'wind_pass', players: [moritani], moritaniId: 'm', turn: 3,
|
|
aftermath: null, nexusPlacement }), /original Nexus placement/);
|
|
for (const territory of ['homeworld:moritani', 'hidden_mobile_stronghold', 'jacurutu-sietch', 'not-a-territory'])
|
|
assert.throws(() => quoteMoritaniAtomics({ token: { ...token, location: territory },
|
|
territory, players: [moritani], moritaniId: 'm', turn: 3,
|
|
aftermath: null, nexusPlacement: 'original-cunning-placement' }), /original Nexus placement/);
|
|
});
|
|
|
|
void test('ordinary stronghold Aftermath keeps its original JSON shape even with supplied Cunning context', () => {
|
|
const ordinary = quote([moritani]);
|
|
const withGrant = quoteMoritaniAtomics({ token, territory: 'arrakeen', players: [moritani],
|
|
moritaniId: 'm', turn: 3, aftermath: null, nexusPlacement: 'original-cunning-placement' });
|
|
assert.deepEqual(withGrant, ordinary);
|
|
assert.equal(Object.hasOwn(withGrant.aftermath, 'nexusPlacement'), false);
|
|
});
|