import test from 'node:test'; import assert from 'node:assert/strict'; import type { BoardContext } from '../game/board-resolution-quote'; import { fremenSpecialVictory } from '../game/fremen-victory'; import { VictoryProgressError } from '../game/victory-progress'; function fixture(advanced = false): BoardContext { return { advanced, storm: 18, order: ['x', 'f', 'e', 'r'], players: [ { id: 'f', faction: 'fremen', ally: 'e', forces: {} }, { id: 'e', faction: 'ecaz', ally: 'f', forces: {} }, { id: 'x', faction: 'beneGesserit', ally: null, forces: {} }, { id: 'r', faction: 'richese', ally: null, forces: {} }, ], }; } function seat(g: BoardContext, id: string) { return g.players.find((player) => player.id === id)!; } function place(g: BoardContext, id: string, key: string) { const player = seat(g, id); player.forces = { ...player.forces, [key]: 1 }; } void test('allied Ecaz co-occupation at Sietch Tabr permits Fremen victory in Basic and Advanced', () => { for (const advanced of [false, true]) { const g = fixture(advanced); place(g, 'f', 'sietch_tabr:14'); place(g, 'e', 'sietch_tabr:14'); const result = fremenSpecialVictory(g)!; assert.equal(result.player, 'f'); assert.deepEqual(result.members, ['f', 'e']); assert.equal(result.qualifies, true); assert.deepEqual(result.sietches, [ { territory: 'sietch_tabr', blockers: [], ecazCooccupation: true, }, { territory: 'habbanya_ridge_sietch', blockers: [], ecazCooccupation: false, }, ]); assert.deepEqual(result.tueksBlockers, []); } }); void test('a valid board without Fremen has no special-victory prospect', () => { const g = fixture(); seat(g, 'f').faction = 'atreides'; assert.equal(fremenSpecialVictory(g), null); }); void test('solitary, nonallied and third-party Ecaz presence do not receive the Tabr exception', () => { const solitary = fixture(); place(solitary, 'e', 'sietch_tabr:14'); assert.deepEqual(fremenSpecialVictory(solitary)!.sietches[0].blockers, ['e']); const nonallied = fixture(); seat(nonallied, 'f').ally = null; seat(nonallied, 'e').ally = null; place(nonallied, 'f', 'sietch_tabr:14'); place(nonallied, 'e', 'sietch_tabr:14'); assert.deepEqual(fremenSpecialVictory(nonallied)!.sietches[0].blockers, [ 'e', ]); const third = fixture(); place(third, 'f', 'sietch_tabr:14'); place(third, 'e', 'sietch_tabr:14'); place(third, 'r', 'sietch_tabr:14'); assert.deepEqual(fremenSpecialVictory(third)!.sietches[0].blockers, ['r']); assert.equal(fremenSpecialVictory(third)!.qualifies, false); }); void test('Basic allied Ecaz co-occupation at Habbanya permits Fremen victory', () => { const g = fixture(); place(g, 'f', 'habbanya_ridge_sietch:17'); place(g, 'e', 'habbanya_ridge_sietch:17'); const result = fremenSpecialVictory(g)!; assert.equal(result.sietches[1].ecazCooccupation, true); assert.deepEqual(result.sietches[1].blockers, []); assert.equal(result.qualifies, true); }); void test('both rulesets keep solitary Ecaz as a Habbanya blocker', () => { for (const advanced of [false, true]) { const g = fixture(advanced); place(g, 'f', 'habbanya_ridge_sietch:17'); place(g, 'e', 'habbanya_ridge_sietch:17'); assert.equal(fremenSpecialVictory(g)!.qualifies, true); seat(g, 'f').forces = {}; assert.deepEqual(fremenSpecialVictory(g)!.sietches[1].blockers, ['e']); } }); void test('nonallied Ecaz and third-party fighters still block Habbanya in both rulesets', () => { for (const advanced of [false, true]) { const nonallied = fixture(advanced); seat(nonallied, 'f').ally = null; seat(nonallied, 'e').ally = null; place(nonallied, 'f', 'habbanya_ridge_sietch:17'); place(nonallied, 'e', 'habbanya_ridge_sietch:17'); assert.deepEqual(fremenSpecialVictory(nonallied)!.sietches[1].blockers, ['e']); assert.equal(fremenSpecialVictory(nonallied)!.qualifies, false); const third = fixture(advanced); place(third, 'f', 'habbanya_ridge_sietch:17'); place(third, 'e', 'habbanya_ridge_sietch:17'); place(third, 'r', 'habbanya_ridge_sietch:17'); assert.deepEqual(fremenSpecialVictory(third)!.sietches[1].blockers, ['r']); assert.equal(fremenSpecialVictory(third)!.qualifies, false); } }); void test('accompanied BG advisors do not block, while mandatory lone-advisor release restores fighter presence', () => { const accompanied = fixture(true); place(accompanied, 'f', 'habbanya_ridge_sietch:17'); place(accompanied, 'x', 'habbanya_ridge_sietch:17'); seat(accompanied, 'x').advisors = { habbanya_ridge_sietch: {} }; assert.deepEqual(fremenSpecialVictory(accompanied)!.sietches[1].blockers, []); const released = fixture(true); place(released, 'x', 'sietch_tabr:14'); seat(released, 'x').advisors = { sietch_tabr: {} }; const before = structuredClone(released); assert.deepEqual(fremenSpecialVictory(released)!.sietches[0].blockers, ['x']); assert.deepEqual(released, before); }); void test('Tuek blockers preserve Basic and Advanced faction scope and never inspect marker secrets', () => { const basic = fixture(false); place(basic, 'r', 'tueks_sietch:5'); assert.deepEqual(fremenSpecialVictory(basic)!.tueksBlockers, []); const advanced = fixture(true); seat(advanced, 'r').noField = { deployed: { location: { territory: 'tueks_sietch', sector: 5 } }, }; Object.defineProperty(seat(advanced, 'r').noField!.deployed, 'value', { get() { throw new Error('Hidden marker denomination read'); }, }); for (const player of advanced.players) for (const key of ['hand', 'spice', 'leaders', 'prediction']) Object.defineProperty(player, key, { get() { throw new Error(`Private field read: ${key}`); }, }); const result = fremenSpecialVictory(advanced)!; assert.deepEqual(result.tueksBlockers, ['r']); assert.equal(result.qualifies, false); }); void test('members and blockers follow public order rather than player-array order', () => { const g = fixture(); g.order = ['r', 'e', 'x', 'f']; place(g, 'f', 'sietch_tabr:14'); place(g, 'e', 'sietch_tabr:14'); place(g, 'r', 'habbanya_ridge_sietch:17'); place(g, 'x', 'habbanya_ridge_sietch:17'); assert.deepEqual(fremenSpecialVictory(g), { player: 'f', members: ['e', 'f'], qualifies: false, sietches: [ { territory: 'sietch_tabr', blockers: [], ecazCooccupation: true, }, { territory: 'habbanya_ridge_sietch', blockers: ['r', 'x'], ecazCooccupation: false, }, ], tueksBlockers: [], }); }); void test('invalid shared board and alliance state is reported through VictoryProgressError', () => { const invalid = fixture(); seat(invalid, 'e').ally = null; assert.throws(() => fremenSpecialVictory(invalid), VictoryProgressError); const malformed = fixture(); seat(malformed, 'x').forces = { 'sietch_tabr:99': 1 }; assert.throws(() => fremenSpecialVictory(malformed), VictoryProgressError); });