Files
Dune/tests/force-presence.test.ts
T
2026-09-08 07:37:52 +10:00

223 lines
6.8 KiB
TypeScript

import test from 'node:test';
import assert from 'node:assert/strict';
import {
presenceAt,
presenceByLocation,
type ForcePresence,
} from '../game/force-presence';
import {
forceCount,
fighterCount,
settleAdvisors,
arrivalAsAdvisor,
} from '../game/advisors';
import { createGame, newPlayer } from '../game/engine';
import {
createRicheseNoField,
deployRicheseNoField,
projectRicheseNoField,
revealRicheseNoField,
} from '../game/richese-no-field';
const markerLocation = { territory: 'imperial_basin', sector: 10 };
function token(value: 0 | 3 | 5) {
const state = createRicheseNoField(['opaque-a', 'opaque-b', 'opaque-c']);
return deployRicheseNoField(state, {
tokenId: state.tokens.find((t) => t.value === value)!.id,
controller: 'richese',
location: markerLocation,
});
}
void test('ordinary physical presence remains identical with absent, null or undeployed markers', () => {
for (const noField of [undefined, null, { deployed: null }]) {
const p: ForcePresence = {
forces: {
'imperial_basin:9': 2,
'imperial_basin:10': 3,
'arrakeen:10': 4,
},
noField,
};
assert.equal(presenceAt(p, 'imperial_basin'), 5);
assert.equal(presenceAt(p, 'arrakeen'), 4);
assert.equal(presenceAt(p, 'carthag'), 0);
assert.deepEqual(presenceByLocation(p), p.forces);
}
});
void test('all hidden denominations add exactly one and owner/public inputs produce the same result', () => {
for (const value of [0, 3, 5] as const) {
const noField = token(value);
const forces = {
'imperial_basin:9': 2,
'imperial_basin:10': 3,
'arrakeen:10': 4,
};
const own = { forces, noField, reserves: 0 };
const publicPlayer = {
forces,
noField: projectRicheseNoField(noField, false),
};
for (const p of [own, publicPlayer]) {
assert.equal(presenceAt(p, 'imperial_basin'), 6);
assert.equal(presenceAt(p, 'arrakeen'), 4);
assert.deepEqual(presenceByLocation(p), {
'imperial_basin:9': 2,
'imperial_basin:10': 4,
'arrakeen:10': 4,
});
assert.equal(forceCount(p, 'imperial_basin'), 6);
assert.equal(
fighterCount(
{ ...p, faction: 'richese', advisors: {} },
'imperial_basin',
),
6,
);
}
assert.deepEqual(own.forces, forces);
}
});
void test('a marker alone creates one public location without inventing physical forces', () => {
const forces: Record<string, number> = {};
const p = { forces, noField: token(0) };
assert.equal(presenceAt(p, 'imperial_basin'), 1);
assert.deepEqual(presenceByLocation(p), { 'imperial_basin:10': 1 });
assert.deepEqual(forces, {});
assert.equal(
Object.values(p.forces).reduce((sum, count) => sum + count, 0),
0,
);
});
void test('presence never inspects reserves, hidden token inventory, denomination or controller', () => {
const forbidden = () => {
throw new Error('Hidden field accessed');
};
const noField = {
get tokens() {
return forbidden();
},
get lastShipped() {
return forbidden();
},
deployed: {
get tokenId() {
return forbidden();
},
get controller() {
return forbidden();
},
get value() {
return forbidden();
},
location: markerLocation,
},
};
const p = {
forces: { 'imperial_basin:9': 2 },
noField,
get reserves() {
return forbidden();
},
};
assert.equal(presenceAt(p, 'imperial_basin'), 3);
assert.deepEqual(presenceByLocation(p), {
'imperial_basin:9': 2,
'imperial_basin:10': 1,
});
});
void test('location maps and public JSON restoration preserve sectors without mutating either input', () => {
const p = {
forces: { 'imperial_basin:9': 2 },
noField: projectRicheseNoField(token(3), false),
};
const before = JSON.stringify(p);
const restored: ForcePresence = JSON.parse(before);
const locations = presenceByLocation(restored);
locations['imperial_basin:9'] = 20;
locations['imperial_basin:10'] = 20;
assert.equal(presenceAt(restored, 'imperial_basin'), 3);
assert.equal(JSON.stringify(restored), before);
assert.equal(JSON.stringify(p), before);
});
void test('after reveal only actual materialized units count and zero reveal removes marker-only presence', () => {
for (const value of [0, 3, 5] as const) {
const noField = token(value);
const result = revealRicheseNoField(noField, {
tokenId: noField.deployed!.tokenId,
reserves: 2,
cause: 'battle',
});
const p = {
forces: { 'imperial_basin:10': result.forces },
noField: result.state,
};
assert.equal(presenceAt(p, 'imperial_basin'), Math.min(value, 2));
assert.equal(
presenceByLocation(p)['imperial_basin:10'],
Math.min(value, 2),
);
}
});
void test('Bene Gesserit advisor stance still suppresses fighters while force presence remains visible', () => {
const p = {
faction: 'beneGesserit' as const,
forces: { 'imperial_basin:9': 2 },
advisors: { imperial_basin: { lockedTurn: 2 } },
};
assert.equal(forceCount(p, 'imperial_basin'), 2);
assert.equal(fighterCount(p, 'imperial_basin'), 0);
assert.equal(fighterCount({ ...p, advisors: {} }, 'imperial_basin'), 2);
// Presence is structural, while engine ownership separately ensures Richese
// alone owns a No-Field inventory. Do not bypass stance for any marker shape.
assert.equal(fighterCount({ ...p, noField: token(5) }, 'imperial_basin'), 0);
});
void test('zero No-Field presence preserves advisors and permits an accompanying advisor arrival', () => {
const richese = newPlayer('r', 'Richese', 'richese');
const bg = newPlayer('b', 'Bene Gesserit', 'beneGesserit');
const g = createGame('PRESENCE', richese);
g.players.push(bg);
g.advanced = true;
richese.forces = {};
Object.assign(richese, { noField: token(0) });
bg.forces = { 'imperial_basin:9': 1 };
bg.advisors = { imperial_basin: { lockedTurn: 2 } };
settleAdvisors(g);
assert.deepEqual(bg.advisors?.imperial_basin, { lockedTurn: 2 });
assert.equal(
arrivalAsAdvisor(g, bg, 'imperial_basin', undefined, true),
true,
);
bg.forces = {};
bg.advisors = {};
assert.equal(
arrivalAsAdvisor(g, bg, 'imperial_basin', undefined, true),
true,
);
const zero = (
richese as typeof richese & { noField: ReturnType<typeof token> }
).noField;
Object.assign(richese, {
noField: revealRicheseNoField(zero, {
tokenId: zero.deployed!.tokenId,
reserves: 20,
cause: 'voluntary',
}).state,
});
bg.forces = { 'imperial_basin:9': 1 };
bg.advisors = { imperial_basin: { lockedTurn: 2 } };
settleAdvisors(g);
assert.equal(bg.advisors?.imperial_basin, undefined);
assert.equal(
arrivalAsAdvisor(g, bg, 'imperial_basin', undefined, true),
false,
);
});